{{{
Happy New Year to all my fellow readers! I hope you had a good party!
In this post I wanna clarify why “representers in Roar”:https://github.com/apotonick/roar are much more versatile “than one might think at first sight”:http://nicksda.apotomo.de/2011/12/ruby-on-rest-introducing-the-representer-pattern/#comment-5346.
h3. Model != Resource
I know, Rails teaches us that every resource is a model with exactly one representation. This is wrong. Not only can a resource be just anything, ranging from simple 1-to-1 mappings of database rows to virtual models like a workflow but also can your business models be represented in completely different _contexts_.
Let’s get back to our fruity example to learn more about this.
orange = Fruit.find_by_title("Orange") orange.extend(FruitRepresenter).to_json #=> {"title":"Orange","colors":["orange"]}
“In the last discussion”:http://nicksda.apotomo.de/2011/12/ruby-on-rest-introducing-the-representer-pattern we had a @Fruit@ and a @Bowl@ model which were represented in their respective resources. While the fruit just contains some properties the bowl was a composition thereof. This is pretty “rails-conform” (I use this word with a negative undertone) and can simply be abstracted to hundreds of projects where people expose users, blog posts or events in their resources. This is what we call a *1-to-1 mapping of model to resource*.
h3. Let’s have some Booze!
We wrote a simple @FruitRepresenter@ for mapping apples, lemons or oranges to documents, and we had the @BowlRepresenter@ to provide documents for fruit collections. That was nice. But you can do more with fruits. Why not open a distillery and make some good booze from pears or grapes?
What that means for our REST API is that we have to represent fruits as ingredients. We also want to document what fruits are contained in a bottle of schnaps.
module IngredientRepresenter include Roar::Representer::JSON property :title collection :tastes_like end
The new @IngredientRepresenter@ looks a bit similar to our @FruitRepresenter@, however, it _represents_ a fruit in another _context_.
pear = Fruit.find_by_title("Pear") pear.extend(IngredientRepresenter).to_json #=> undefined method `tastes_like'
An exception. While the pear exposes a @#title@ method it does not have a @#tastes_like@ method. Why did I do that? I wanted to emphasize that the representer doesn’t guess anything from the represented object (the pear). It is up to the fruit to provide decent accessors like the @#tastes_like@ reader. No magic or implicit semantics in Roar. And this is a good thing.
h3. Another Context, Another Module.
We could easily push the missing methods into the @IngredientRepresenter@ itself, but we will learn in an upcoming post that it is better to provide additional accessors in a separate module.
module Ingredient def tastes_like TastesLike.query(title) # returns array. end def tastes_like=(*) end end
In the reader, we query the world-famous TastesLike™ web service. Since we don’t need a writer, the second method ain’t doing nothing.
pear.extend(Ingredient, IngredientRepresenter).to_json #=> {"title":"Pear","tastes_like":["pear","mango"]}
Cool, this is a fruit represented in a completely different context. What about the parsing, does that work, too?
apple = Fruit.new apple.extend(Ingredient, IngredientRepresenter) apple.from_json("{"title":"Apple", "tastes_like":["Pear","Apple","Mango"]}") #=> #
Yepp, parsing an ingredient and mapping it to a fruit also works fine.
h3. Fruits in a Bottle.
Let’s see how collections can be represented in different contexts.
module BottleRepresenter include Roar::Representer::JSON collection :fruits, :class => Fruit, :extend => [Ingredient, IngredientRepresenter] end
The bottle representer _knows_ that it’s composed of @Fruit@ instances. It does use the @IngredientRepresenter@ for parsing and rendering, though. BTW, if you don’t like the long @:extend@ assignment you are free to “hard-include” the modules directly in your models.
h3. Summary
So what we’re doing here is having one model used in two completely different contexts: the conventional “fruit” context and the “ingredient” context. Imagine you’d be doing this with @#as_json@ and different hash-parsing algorithms in your controllers – good luck.
}}}
This is really great.
:class, :extend option for collection is very helpfull
LikeLike
@Allen: This also works with
property
.LikeLike
This certainly addresses some of my questions, thanks!
Now if we could get everyone to realize that JSON and XML aren’t hypermedia…
LikeLike
@Steve: JSON and XML are document types that might contain hypermedia, right? Is that like a HTML page that might include links to other pages but doesn’t necessarily have to?
Isn’t hypermedia just the abstract concept of linking to other network endpoints along with giving each link a semantic (the “rel” attribute) and some additional infos? Great discussion, so valueable, thanks man!
LikeLike
Nope. They can’t be hypermedia by themselves; they have no standard linking semantics. You’d have to invent your own, and then you’re adding out-of-band information, which violates the ‘self-descriptive messages’ constraint.
LikeLike
Can you have one representation of two models? For example, my models are Apple & FruitStatistics. I want a single representation called AppleStatistics that combines the properties of the Apple and the statistics of the Fruit of type Apple.
LikeLike
@Michel: Of course, but you will need an intermediate model to wrap your composition of Apple and AppleStatistics:
So, the intermediate model delegates the properties to the proper models. Keep in mind that representers are dumb and you’re smart, so you provide them with the data they want to read/set! Ok?
LikeLike
@Steve: Ah ok, I start getting what you mean!!! JSON and XML do not have any linking semantics as they’re just stupid formats (like, say, CSV). HTML has the a tag which “is” a linking element. Now, what about something like HAL? It adds linking semantics to these formats. What you think of that, Steve?
LikeLike
@nick exactly! HAL is just fine.
The important part is that the media type itself defines the semantics; you have to serve HAL as application/hal+json, which indicates that you’re getting HAL. If you just serve it as application/json, then you’re relying on out-of-band information to deal with links, so you’re violating the self-descriptive messages constraint.
LikeLike
@Steve: 1. Does the comments subscription thing finally work? Do you get emails on updates?
2. I thought you even have to go deeper and have a media type like application/hal+json+iorder to make sure the consumer knows everything about the contained attributes, etc. Otherwise, HAL would just define hypermedia but says nothing about your representation format. Is that right?
LikeLike
Yep, I’ve been getting emails.
As always, it depends. Sticking with the most generic media type available is useful for a number of reasons. However, that means you can’t use additional semantics on top of the ones defined by HAL. The more specific you get, the less agents will be able to process your request. It’s a balance.
HAL is sort of a special case; in it, you’d use the type attribute to serve a more specific type within the embedded resource.
LikeLike
Commenting so I can get email updates…
LikeLike
Great idea! So, ROAR provides a way to describe the representation of objects, reuse and mix representations.
BTW, can I describe attribute types in ROAR, so that it will be accessible in JSON? In ActiveResource I can describe schema, so it knows the type of each attribute, despite JSON data for resource have no such information.
LikeLike
@Vlad: Can you make an example where you need to “type” your representation properties? Representers are designed to be as dumb as possible, so no conversion here since this should be the job of the hosting model. Please, provide an example, thx!!!
LikeLike
@Nick: Maybe I miss something, but what I expect from representers, is that after using to_json and then from_json to recreate model with data, it also preserves attribute types. Consider this example:
When I run it, it gives me these results:
See? The integer and string attributes are OK, but not the date. It became string after conversion, which is not what I’ve expected.
Does it makes sense?
LikeLike
@Nick: Oh, sorry, no auto-formatting for code here. Don’t know how to highlight my code example in comment, and can’t edit it.
LikeLike
@Vlad: Ok, cool. Currently, I urge users to the following solution:
Basically, it’s up to the user to format properties. However, this can easily be provided in a wrapping module that adds type semantics to properties.
LikeLike
@Nick: Yes, that will work, but I already have type semantics for my properties in models.
>> Currently, I urge users to the following solution
BTW, does “currently” means that you are thinking about adding type-awarenes to roar gem itself, so that it’ll preserve types?
LikeLike
Types are kinda silly. This is Ruby, after all.
LikeLike
Is there a way to control how collections are transformed to json? I would like to have collections serialized like rails: [ “model” : { props … }, “model” : {props…}]. Instead of { models : [ {props}, {props} …] }, which is what I have roar doing now.
LikeLike
Michel: What you’re requiring doesn’t look like valid JSON to me. What you probably want (definitely?!) is the new “Lonely Collections” feature. Check out representable, which is roar’s parent.
LikeLike