Ruby On REST 3: One Model, Multiple Representations!

{{{
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.
}}}

Advertisement

22 thoughts on “Ruby On REST 3: One Model, Multiple Representations!

  1. @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!

    Like

  2. 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.

    Like

  3. 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.

    Like

  4. @Michel: Of course, but you will need an intermediate model to wrap your composition of Apple and AppleStatistics:

    module AppleStatisticsRepresenter
      include AppleRepresenter # inherit.
    
      property :quality_average
    end
    
    class AppleStatistics
      def title
        @apple.title
      end
    
      def quality_average
        @statistics.avg
      end
    end
    

    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?

    Like

  5. @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?

    Like

  6. @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.

    Like

  7. @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?

    Like

  8. 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.

    Like

  9. 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.

    Like

  10. @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!!!

    Like

  11. @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:

    require 'date'
    require 'roar/representer/json'
    
    class Thing
      def initialize
        @num = 123
        @str = "abc"
        @date_time = DateTime.now
      end
      attr_accessor :num, :str, :date_time
      
      def to_s
        "Thing: #{num.inspect}, #{str.inspect}, #{date_time.inspect}"
      end
    end
    
    module ThingRepresenter
      include Roar::Representer::JSON
    
      property :num
      property :str
      property :date_time
    end
    
    thing = Thing.new
    puts "Original #{thing}"
    
    thing_represented_in_json = thing.extend(ThingRepresenter).to_json
    puts "JSON: #{thing_represented_in_json}"
    
    restored_thing = Thing.new.extend(ThingRepresenter).from_json(thing_represented_in_json)
    puts "Restored #{restored_thing}"
    

    When I run it, it gives me these results:

    Original Thing: 123, "abc", #
    JSON: {"num":123,"str":"abc","date_time":"2012-01-20T13:20:47+03:00"}
    Restored Thing: 123, "abc", "2012-01-20T13:20:47+03:00"
    

    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?

    Like

  12. @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.

    Like

  13. @Vlad: Ok, cool. Currently, I urge users to the following solution:

      property :date_time_formatted, :from => :date_time
    
      def date_time_formatted
        date_time
      end
    
      def date_time_formatted=(v)
         self.date_time = Date.parse(v)
      end
    

    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.

    Like

  14. @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?

    Like

  15. 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.

    Like

  16. 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.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s