Use Roar’s New Decorator If You Dislike Extend!

{{{
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”:https://github.com/apotonick/roar and “representable”:https://github.com/apotonick/”roar”:https://github.com/apotonick/roar!

h3. 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”:https://github.com/apotonick/roar-rails/tree/e49e965badb03fe255d594a91b4ddcd937ebf4b9#using-decorators.

h3. Speed Is Relative.

Several “on-going discussions”:http://charlie.bz/blog/things-that-clear-rubys-method-cache debate whether heavy usage of @extend@ slows down your app. I did a “quick benchmark”:https://gist.github.com/apotonick/5351023 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! 😉

h3. 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"

h3. Conclusion

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

6 thoughts on “Use Roar’s New Decorator If You Dislike Extend!

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

    Like

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

    Like

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

    Like

  4. 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!

    Like

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

    Like

Leave a comment