Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Already implemented in our fork but could be useful / better implemented :) #110

Open
DanielShuey opened this issue Aug 18, 2014 · 4 comments

Comments

@DanielShuey
Copy link

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

@apotonick
Copy link
Member

@fredwu
Copy link

fredwu commented Aug 18, 2014

👏

@johncarney
Copy link

👍 Might be useful to split this into separate issues.

@apotonick
Copy link
Member

Yeah, that is awesome, let's go through it, discuss it here and then split out issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants