trailblazer / representable

Maps representation documents from and to Ruby objects. Includes JSON, XML and YAML support, plain properties and compositions.
http://trailblazer.to/2.1/docs/representable.html
MIT License
689 stars 108 forks source link

How To: Optionally wrap a lonely collection under a predefined key #135

Open jiggneshhgohel opened 9 years ago

jiggneshhgohel commented 9 years ago

I have following JSON decorator defined in my Rails 4 application for Event model.

require 'representable/json'

class Json::EventRepresentation < Representable::Decorator
  include Representable::JSON

  property :id
  property :data, render_nil: true
  property :recorded_time
  property :event_type_id
  property :created_at
  property :updated_at
end

I render a collection of Event objects using Json::EventRepresentation.for_collection.new(@events).as_json in my controller which emits following JSON array (which is a lonely collection)

[
  {
    "id": 49,
    "data": null,
    "recorded_time": "2015-04-02T21:23:16.509Z",
    "event_type_id": 11,
    "created_at": "2015-04-02T21:33:16.512Z",
    "updated_at": "2015-04-02T21:33:16.512Z",
  },
  {
    "id": 37,
    "data": null,
    "recorded_time": "2015-04-02T21:13:16.422Z",
    "event_type_id": 10,
    "created_at": "2015-04-02T21:33:16.424Z",
    "updated_at": "2015-04-02T21:33:16.424Z",
  }
]

However optionally at few places in my application I need to wrap the above shown lonely collection under key events so that following JSON is emitted:

{
  events: [
    {
      "id": 49,
      "data": null,
      "recorded_time": "2015-04-02T21:23:16.509Z",
      "event_type_id": 11,
      "device_id": 8,
      "created_at": "2015-04-02T21:33:16.512Z",
      "updated_at": "2015-04-02T21:33:16.512Z",
      "client_id": 7
    },
    {
      "id": 37,
      "data": null,
      "recorded_time": "2015-04-02T21:13:16.422Z",
      "event_type_id": 10,
      "device_id": 7,
      "created_at": "2015-04-02T21:33:16.424Z",
      "updated_at": "2015-04-02T21:33:16.424Z",
      "client_id": 7
    }
  ]
}

I am looking out for something like Json::EventRepresentation.for_collection.new(@events).as_json(wrap_in: :events) if its supported.

I looked through the gem's test suite and the readme but couldn't get a hint to achieve the desired behavior. Can anybody please guide me on how do I achieve the desired behavior?

Thanks.

summera commented 9 years ago

hey @jiggneshhgohel is Wrapping what you are looking for? You can pass a lambda to representation_wrap to make it dynamic.

class Json::EventRepresentation < Representable::Decorator
  self.representation_wrap = lambda do { |args| }
end
summera commented 9 years ago

sorry @jiggneshhgohel. just realized that will probably not give you what you are looking for to add a key to your array. For collections, something like the following may be an option:

class EventsRepresenter < Representable::Decorator

  collection :events,
    :exec_context => :decorator,
    :decorator => EventRepresenter

  def events    
    represented
  end
end

class EventRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :data, render_nil: true
  property :recorded_time
  property :event_type_id
  property :created_at
  property :updated_at
end
EventsRepresenter.prepare(@events).to_json

{
  events: [
    {
      "id": 49,
      "data": null,
      "recorded_time": "2015-04-02T21:23:16.509Z",
      "event_type_id": 11,
      "device_id": 8,
      "created_at": "2015-04-02T21:33:16.512Z",
      "updated_at": "2015-04-02T21:33:16.512Z",
      "client_id": 7
    },
    {
      "id": 37,
      "data": null,
      "recorded_time": "2015-04-02T21:13:16.422Z",
      "event_type_id": 10,
      "device_id": 7,
      "created_at": "2015-04-02T21:33:16.424Z",
      "updated_at": "2015-04-02T21:33:16.424Z",
      "client_id": 7
    }
  ]
}
jiggneshhgohel commented 9 years ago

@summera Thanks for the solution. It's really helpful.In fact I did thought of creating a separate representer for representing the lonely collection with few customizations for e.g using wrap option available on collection in a represented.

However I also thought that for such a trivial thing why should we be bothered about creating and maintaining a separate representer and there might be an alternative to achieve the desired results in a dynamic manner for e.g passing custom option like

Json::EventRepresentation.for_collection.new(@events).as_json(wrap_in: :events)

while rendering the json and using this option in Json::EventRepresentation in someway such that the given collection gets wrapped inside given key :events when the json gets rendered.@apotonick can you share your thoughts on this one?

@summera your solution is the way right now for me to continue and it also seems flexible enough for e.g when I need to add more data to the json which for now would be just containing events array wrapped under :events. For e.g

{ events: [ .....], custom_data_a: ..., custom_data_b: ..., ...}

But I really wish if there is out-of-the-box support for wrapping lonely collections under a given key , when needed, while rendering the json and hence avoiding the need to write and maintain a separate representer for the same.

Thanks, Jiggneshh

apotonick commented 9 years ago

Uhm, but isn't what you're asking for exactly what ::representation_wrap with a lambda does?

The problem here is that Representable tries hard to be a generic gem that explicitely does not solve everyone's requirements out-of-the-box, otherwise, this will end up like Rails.

I see your point, though, @jiggneshhgohel that it feels clunky to maintain two representers just to have a wrapping for the collections.

Maybe we should think about something as follows.

class Representer < ..
  property :title

  collection_representer do
    self.representation_wrap ...
  end
end

Here, you can configure specific items for the implicit collection representer. Cool?

jiggneshhgohel commented 9 years ago

@apotonick Thanks for such a prompt response. Using collection_representer in the manner you have suggested definitely looks like a good solution. I would try it out.

Closing this for now.

apotonick commented 9 years ago

Oh no, wait, this does not work, yet! :laughing: :rocket: