representable
representable copied to clipboard
Already implemented in our fork but could be useful / better implemented :)
Hey Nick,
Just letting you know about some features that I've implemented (in a really really ugly way :P), due to time constraints I haven't had a lot of time to look at the code and needed it done asap. I will fix the code up later when I have time and possibly pull-request.
Basically we are trying to use Representable to populate our models by passing in an extremely large and complex JSON/XML document that I cannot mention due to privacy reasons. Just think Enterprise + Legacy scale :P.
Please find some time to look at our fork (Locomote).
After you've vented about how bad my code is :P, hopefully you can think of much better ways of implementing features that cater to the same requirements if that is in your interest. :)
1. Post-processing after
hook and :eval
parameter
Due to the complex nature of the document we are passing in, we needed a way to build relationships up between objects post-process.
The current params like parse-strategy, required too much boilerplate for what we wanted to do, we needed something more simple and needed to use it for almost half the properties.
The after hook is a method->block that is called as a last resort for linking objects together and doing post-processing on an object.
Example:
class LibraryRepresenter
after do
users.each do |user|
user.books = books.select { |book| book.user_id == user.id }
end
end
collection :books do
property :user_id
end
collection :users do
property :id
end
end
The eval hook is done specifically in context of the property, also post-process.
collection :users
collection :books, eval: -> { books.select { |book| book.user_id == user.id } }
end
Better example and reasoning is shown in the next section.
2. parent
attribute
Child collections and properties can access the parent representer object during post-process. Again this is due to the "spidery" nature of the documents we receive, we need to re-arrange everything into something that actually looks organised.
We literally get documents where should-be nested objects are instead in a what-is-this-i-don't-even random location. Not only this, but we have to access these objects in order to link other randomly placed objects together.
I shit you not this is what its like (replace theme with what we are actually dealing with)
class LibraryRepresenter
collection :book_names
property :name
property :book_ref
end
# Why this wrapping appears I don't know
nested :book_data
#The actual books collection for model because most of the stuff lies here and is most reliable
collection :books, as: :book_segment do
property :book_number
property :key, eval: -> { book_number[3..-1] }
property :name, eval: -> { parent.book_names.detect { |bn| bn.book_ref == ref } }
collection :infos, eval: -> { parent.infos.select {|info| info.segment_ref == ref} }
property :pricing, eval: -> { parent.pricings.detect { |p| p.key == infos.book_pricing_ref } }
property :issue_dates, eval: -> { infos.map {|info| info.try(:issue_date) } }
# + lots of other stuff we desperately need
end
collection :pricings, as: :book_pricing do
property :base_price
property :key
end
end
# This links pricings to book, sometimes they don't always appear so we can't use this for the main collection.
collection :infos, as: :book_info do
property :book_pricing_ref
property :book_segment_ref
# Sometimes doesn't appear so we have to conditionally set this
property :issue_date
property :you_get_the_idea
end
end
Multiply this x10 and now you know why the current features require too much boilerplate for what we want to do. :P
3. Default class
We didn't want to have to declare class: OpenStruct
for every... single... collection for which we have about 20 or so and possibly more in the future. Setting a default class in the Config would be nice.
Example:
collection :libaries
Is the same as
collection :libaries, class: OpenStruct
4. Magic Class instantiation
For the same reason as above, we have lots of collections. We have Representable automagically find which Representable it belongs to. Kind of like how some Ruby database gems do it.
Example:
collection :libraries
Is the same as
collection :libaries, extends: LibraryRepresenter
This behaviour however does not happen if the collection is inlined as a matter of design.
5. Type-cast on parse (symbol or class constant)
We want to be able to use symbols because of the auto-load nature of rails but also we wanted the type casting to happen immediately and not during render.
Example:
property :book_name, type: BookName
property :book_name, type: :book_name
book = OpenStruct.extend(BookRepresenter).from_json({book_name: 'A Song of Ice and Fire'}.to_json)
book.book_name.class
$ => BookName
6. Always return [] and force conversion to array for collections
Due to the inconsistent nature of the documents we are receiving we needed collections to always return [] if the fragment doesn't exist and always be parsed/set as an array, no matter what even if it is sent in as a singular (even in JSON).
Single items are wrapped in [] before being parsed if Representable knows it should be a collection.
7. :set
parameter
During parsing we needed some properties to trigger the evaluation of another property without it getting set itself. Saves use from using try
or conditional statements everywhere.
Either that or the combination of some sort of :ignore
parameter and :eval
.
In the works
1. :ignore
or :persist
parameter
An :ignore
parameter to capture but not ultimately set or persist the property in the model is in the works.
2. Depluralize collection names to find fragment
When finding fragment and no :as
specified, try finding using non-pluralized name, and if the fragment is not found, try again using pluralized name.
This is to get around all the boilerplate revolving things like
collection :books, as: :book
3. Nested collections need to be set default
As said in other posted issue
Obviously up to you how you decide to go about these. These are features that we are currently using so if you got better ideas or advice on how to implement them that would be good too.
Looking forward to your reply
Cheers
https://twitter.com/apotonick/status/501196534759706625 :smile:
:clap:
:+1: Might be useful to split this into separate issues.
Yeah, that is awesome, let's go through it, discuss it here and then split out issues.