Pass forced options on nested Grape::Entity

1,390 views
Skip to first unread message

Reynard

unread,
Apr 6, 2014, 9:36:56 PM4/6/14
to ruby-...@googlegroups.com
If I have a model like this

class User::Entity < Grape::Entity
  expose :name
  expose :children, with: User::Entity, if: lambda { |instance, options| options[:include_children] }
end

and then on the controller I do

present users, with: User::Entity, include_children: true

It will include the children's children, and so on, nested infinitely. If I just want one level deep for children inclusion, I have to create another entity for the children. 

class UserMini::Entity < Grape::Entity
  expose :name
end

class User::Entity < Grape::Entity
  expose :name
  expose :children, with: UserMini::Entity, if: lambda { |instance, options| options[:include_children] }
end

Is there a way I can do this without creating a duplicate model that doesn't include the nested entity? 
I wish I can just pass options on the entity during expose, like this: 

class User::Entity < Grape::Entity
  expose :name
  expose :children, with: User::Entity, options: {include_children: false}
                    if: lambda { |instance, options| options[:include_children] }
end


- reynard

Daniel Doubrovkine

unread,
Apr 7, 2014, 7:14:49 AM4/7/14
to ruby-...@googlegroups.com
I think what this is saying is that you have two different ways of retrieving a User: one directly, another through a parent user. I would borrow the rules from Hypermedia and not do that. The list of children should be links to top-level user URLs.

We've done exactly what you describe in our application and have been paying for it for 3 years now. We had to invent a simpler declarative protocol for doing this (https://github.com/dblock/mongoid-cached-json) and had to do more complex cache binding at API level (https://github.com/artsy/garner). Frankly, most of that could have been avoided with a better API design.




--
You received this message because you are subscribed to the Google Groups "Grape Framework Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-grape+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

dB. | Moscow - Geneva - Seattle - New York
code.dblock.org - @dblockdotorg - artsy.net - github/dblock

justin

unread,
Apr 7, 2014, 10:36:12 AM4/7/14
to ruby-...@googlegroups.com
In our API we have a "mini" version of all our entities, and the full version extends it. Every time one is embedded we use the mini version:


class UserRef < Grape::Entity
    expose :name
    expose :url
end

class User < UserRef
    expose :children, with: UserRef
end

It makes for easier documentation, too, as any particular model will consistently be one of two representations.

--justin




--
You received this message because you are subscribed to the Google Groups "Grape Framework Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-grape+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reynard Hilman

unread,
Apr 7, 2014, 11:16:15 AM4/7/14
to ruby-...@googlegroups.com
I could have /users/:id/children to get the children, but for convenience sometime you want to include the association, right? I see a lot of example where API returns results with the associated data in one endpoint. Let's use this example

class User::Entity < Grape::Entity
  expose :name
  expose :friends, with: User::Entity, if: lambda {|obj, opt|  opt[:include_friends] }
end
class Comment::Entity < Grape::Entity
  expose :body
  expose :author, with: User::Entity
end

when you want to render comments, you don't want the author to include all their friends, or some other association that you might define for the user. And it's not practical to ask the API client to make another request to retrieve each author of a comment (if we make API response to never include association). So you might do something like this

present comments, with: Comment::Entity, include_friends: false

So while that works, it is kind of confusing, why comments should have include_friends option. 
Note: I just realized that with that example you don't need to include_friends because it's by default false. But my point is that currently the options passed to present the object, is globally applied to all the nested entities and there is no way to define a local options that override the global ones.  So I think allowing options to be defined on expose has a lot of use case. 

Creating multiple entities for the same object with different nested exposure will get complicated quickly once you have multiple associations that you want to expose depending on context. This is not my use case right now, but imagine having these representation:  
UserMini
UserWithChildren
UserWithParents
UserWithChildrenAndParents

But with localized options you can have just one entity representer and always set any combination like this: 

expose :author, with: User::Entity, options: {mini: true, include_friends: false, include_parents: false}

I hope I make the case clear for the need of localized option :)

- reynard


--
You received this message because you are subscribed to a topic in the Google Groups "Grape Framework Discussion" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ruby-grape/DhQrOxBQo1E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ruby-grape+...@googlegroups.com.

Daniel Doubrovkine

unread,
Apr 7, 2014, 1:26:48 PM4/7/14
to ruby-...@googlegroups.com
I'll put the debate of whether this is a good idea aside :)

We have three types of JSON: all, public and short. So each model decides which type of JSON it wants to return, but must obey the following rules of thumb:

1) All is everything, deep.
2) Public is the default, and can only include short JSONs in nested relationships.
3) Short cannot include any relationships.

This way your option is type: :short or something like that and you never run into circular JSON issues.

Reynard Hilman

unread,
Apr 7, 2014, 6:11:49 PM4/7/14
to ruby-grape

So are you saying that using the 3 types of JSON (all, public, and short) is the solution for this kind of problem? Or is that what you are still paying for 3 years? :)

Do you think the solution in Grape is to just define multiple entities (like what Justin described) then? 

- reynard

Daniel Doubrovkine

unread,
Apr 7, 2014, 7:43:34 PM4/7/14
to ruby-...@googlegroups.com
Both! :)

I like our solution of 3 types of JSON and a lot of discipline. But I would go hypermedia and not embed any relationship in a JSON if I were building a new API.
Reply all
Reply to author
Forward
0 new messages