Representable 2.0 comes with a bunch of cool new features. I’d like to thank the community for coming up with ideas, working with me on features and trying out new stuff. Speaking of new stuff – have you seen my favicon? It’s new, and I’m a bit proud of it.
Let’s quickly go through the changes.
Changes?
Well, if you’ve been a cautious person and were only using representable’s public API, nothing will break! Even when you’re one of the “power users”, you shouldn’t have too much trouble getting up-to-date, again.
Partial Inheritance.
Inheriting properties from other representer has been working fine for a long time, either by including modules into other modules or decorator classes, or by deriving decorators.
It is now possible to partially override inherited properties.
Suppose we have the following base representer (this could be a module or a class, doesn’t matter). For simplicity, I leave away the noisy includes.
module SongRepresenter property :title, as: :Title property :band do property :name end end
Very simple. Now imagine a HitRepresenter
needed to inherit all the above plus add a property to the inline band block. This is where :inherit
enters the stage.
module HitRepresenter include SongRepresenter property :band, inherit: true do property :location end
This will add the location
property to the band’s representer without destroying the original representer.
You can also use this directive with scalar properties that don’t have a block. This will add the options you provide.
property :title, inherit: true, type: String
The coercion option :type
will be added to the existing options, preserving :as
and whatever else you specified.
Having this new inheritance “fine tuning” makes representable really powerful and maximises reusability of representers across your project.
Modules In Decorators.
One thing I found not working and fixed in representable 2.0 was when including modules with inline representers into decorators – this works all fine now.
class SongDecorator include SongRepresenter property :label end
This will work as expected and inherit all properties from SongRepresenter
into the decorator – and add the label.
Automatic Collections.
Many users have been asking for this for a long time, and @timoschilling was the power user who submitted a draft more than a year ago! Sorry for the delay. I was busy drinking Australian Beer™.
Representable comes with a feature called lonely collection which allows you to represent an array of objects.
module SongsRepresenter include Representable::JSON::Collection items extend: SongRepresenter, class: Song end
As you can see, every item will be represented (rendered or parsed) with the specified SongRepresenter
(which could also be inline, BTW).
[song, song].extend(SongsRepresenter).to_json
Now, this is explicit and needs to be defined for every collection. I personally am a big fan of explicit code, but lots of users don’t want that boilerplate code.
That’s why you can now use ::for_collection
to let representable create this lovely lonely collection for you.
module SongRepresenter property :title end
Assuming you use this simple singular representer to render and parse songs, here’s how you’d render collections with it.
[song, song].extend(SongRepresenter.for_collection). to_json
Note how I use the singular representer! Representable will internally create a lonely collection representer and render the collection.
For parsing, it needs to know a bit know, e.g. the class to deserialize the songs to. You need to specify that in the singular representer, again.
module SongRepresenter property :title collection_representer class: Song end
::collection_representer
lets you configure the – surprise! – collection representer. Everything you pass to this method will be forwarded to the ::items
call when creating the collection representer.
[].extend(SongRepresenter.for_collection). from_json("[{title: ..]")
Automaticer Collections.
If that’s still too much code for you, go ::represent
fo’ real!!
SongRepresenter.represent(song).to_json #=> 1 SongRepresenter.represent([song, ..]).to_json #=> n
This method figures out if it’s working with an array or a singular object. It’s a matter of days until this will make its way into Roar and roar-rails and simplify your user code there.
Filters!
If you ever needed to massage a string before it gets rendered, or a fragment after it got parsed… you’re lucky. Representable now gives you :render_filter
and :parse_filter
.
property :title, render_filter: lambda { |value, doc, *args| value.markdown }
This is run right before the fragment is inserted into the document.
property :title, parse_filter: lambda { |value, doc, *args| Markdown.parse(value) }
And this lambda is invoked just after the fragment got picked from the incoming document when parsing.
In case you need multiple filters: those two options are implemented using Representable::Pipeline
, you can add lambdas later, the results will be passed on from filter to filter.
Apparently, this feature already made its way into grape-roar.
Ditch Lambdas For Callable.
I always disliked the verbose, clumsy lambda syntax in my properties. Representable 2.0 lets you specify “callable” objects in favour of lambdas.
property :title, render_filter: Sanitizer.new
Now, how does it know that Sanitizer
instance is invokable? Here’s the trick.
class Sanitizer include Uber::Callable def call(representer, fragment, doc, *args) fragment.sanitize end end
All the object needs is a #call
method that gets – admittedly – a bunch of parameters. In that, you can do whatever you need. Including Uber::Callable
marks this instance as a callable one – no magic here.
I Need More Data!
Another new option helps you retrieving all the possible information you might need in your lambdas (or, cooler, callable objects). Let’s say you need to know the property options, whether :extend
is set, or not. :pass_options
can help.
property :title, pass_options: true, parse_filter: lambda { |value, doc, options| options.binding[:extend] options.user_options options.represented }
The options
object keeps references to all stakeholder objects involved at that point of representing. This :pass_options
is definitely an advanced option, but some peeps might find it helpful. I use it all across my gems.
Enjoy your day and keep smiling!
Nice work, I use Roar/Representer all the time, great to see new features.
LikeLike