Until today, extend was the only way to inject a representer into a model to render or consume representations. Many people criticised this approach as it adds methods to an object which should rather be treated immutuable. Besides that, using extend seems to affect the performance due to caching invalidation.

song = Song.find(1)
song.extend(SongRepresenter).to_json

As a result, people who actually liked the representer idea declined using it because they were worried about extend. No longer should this keep you from using roar and representable!

Use Decorator Over Extend.

A minor refactoring brought us Representable::Decorator.

class SongRepresenter < Representable::Decorator
  include Representable::JSON
 
  property :title
  property :track
end

Instead of a module, derive a class that exposes the very same DSL that you know from the “old” module representers. BTW, you can also include representer modules into Decorator classes.

SongRepresenter.new(song).to_json

Just pass the represented object into the constructor and let representable do the work. Your models won’t get hurt anymore – promised!

Use the :decorator option where you used :extend in a nested setup.

class AlbumRepresenter < Representable::Decorator
  include Representable::JSON
 
  collection :songs, decorator: SongRepresenter
end

In roar, you might use the Roar::Decorator base class. Pretty cool: roar-rails already supports decorators.

Speed Is Relative.

Several on-going discussions debate whether heavy usage of extend slows down your app. I did a quick benchmark myself where I render an Album instance containing 10,000 Song instances which results in 10,000 calls to extend when rendering the album.

Album.new(songs: 10000.times.collect { Song.new }).
  extend(AlbumRepresenter).
  to_json

I did the same with a Decorator class.

alb = Album.new(songs: 10000.times.collect { Song.new })
AlbumDecorator.new(alb).to_json

Here are the results. I leave it up to you to judge these metrics.

# time with extend   : 0.780000
# time with decorate : 0.600000

It looks as if Decorator is faster – go figure! ;)

Using Decorator With Hypermedia.

There’s a tiny pitfall when using a decorator on a hypermedia-consuming object in roar.

class SongRepresenter < Roar::Decorator
  include Roar::Representer::JSON::HAL
  include Roar::Decorator::HypermediaConsumer
 
  property :title
  link(:self) { song_url(self }
end

Be sure to include the Roar::Decorator::HypermediaConsumer module which will propagate parsed links to the represented object by calling it’s link= writer. You have to provide that as well.

class Song
  attr_accessor :links
end

Now, consuming an incoming representation will set links on the client object.

SongRepresenter.new(song).from_json("..hypermedia json")
 
song.links[:self] #=> "http:songs/1"

Conclusion

Go and use decorators and tell us how you like it. Never shall anyone anymore complain about dirty extendings!

6 Responses to “Use Roar’s New Decorator If You Dislike Extend!”


  1. Nell

    Beautiful cuddle

  2. Using it and loving it :-)

    Still would like to see this happen though:

    Instead of:

    class Fruit
      attr_accessor :taste
    end
     
    fruit = Fruit.new
    FruitRepresenter.new(fruit).from_json(...)

    Something like:

    class Fruit
      attr_reader :taste
     
      def initialize(taste = :lousy)
        @taste = taste
      end
    end
    FruitRepresenter.new(Fruit).from_json(...)

    The idea being that I’d like to be able to initialize decorated class any way I like without needed accessors/writers on exposed/decorated properties.

    Other than that, great work :-)
    Thanks for your time!


  3. Brian

    This is great, but it seems like respond_with in roar-rails is outputting differently than the decorators own to_json does? Decorator.to_json looks correct, but respond_with is missing things like links.

    Also, it seems like defining methods on the representers doesn’t work the way it did with modules, when you want to add a “virtual attribute” via representer, on modules you could call property and then define that property as a method in the module, but the same approach on Decorator’s raises an undefined method error.


  4. nick

    Hey Brian, please be sure to updated roar and roar-rails to the latest versions. #respond_with should work, if not, please file a ticket on github.

    The virtual attributes in the representer module don’t work anymore since we do not extend the represented object (I just did what you guys were asking for!!!!) and hence can’t add any methods.

    What you can do is use a :getter lambda.

    property :title, 
      getter: lambda { |*| title + suffix }

  5. Brian

    Thanks Nick, didn’t see info on getters, makes sense. Will try and confirm/find a repro you can do on respond_with, on latest version, maybe it’s something weird I’m doing. Appreciate all your work, great to have an alternative to extend!


  6. nick

    Vanja , do you mean the representing decorator should pass the attributes into the represented object’s constructor rather than using setters (when parsing)? We could introduce a strategy.

    How would the rendering work, then? Still by calling readers?

Leave a Reply