-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Avoid DB hits for JSONAPI relationships #1325
Comments
You could use the |
This would work, and the upcoming |
@richmolj the |
@beauby I agree with you. I think I did not communicate the problem with the And not only easier, but possibly more correct. If you're specifying This all indicates to me that it should be easier to avoid always hitting the DB when a serializer has a relationship specified. |
I do not understand what you mean by As far as "links-only" relationships are concerned, this will be dealt with in the "relationship-level links" PR, which will provide a mechanism for not specifying the |
By
Lets say I make the request:
My controller would have to do something like: ALL_POSSIBLE_INCLUDES = [:comments, :author]
# within #index
includes = params[:include].split(',')
not_included = ALL_POSSIBLE_INCLUDES - includes
# Not totally sure what the API is here once 'filter' is applied
fields = MySerializerClass.all_possible_fields
render json, posts: posts, fields: fields In addition to just being cumbersome, I'm not sure how this logic would work for nested includes, where One way to make this all easily avoidable is something like an has_many :comments, default: false # Only load this relationship when specifically 'included' The "relationship-level links" PR could use this same flag, since in that case you would want the same behavior - don't hit the DB for relationship IDs since links are provided. A flag saying we should only process relationship IDs when |
Ok, that's what I thought. Thing is, the JSON API spec does not state that, when explicitly not including a resource, the corresponding relationships should not be present. It is perfectly compliant (and I believe intended) to return resources with relationships referencing (by The behavior you are describing is not part of the spec (plus it is not well defined, as you could possibly have different inclusion constraints referencing the same relationship fields, think of a |
Yep, we totally agree on what the spec does and does not support. AMS is definitely compliant here as far as JSONAPI is concerned. I believe this is more about how the internals of AMS operate. Allowing something like
I believe your last point would work with existing AMS functionality: |
I just ran into this in my app and it caused a surprise N+1 query for me. I would really like to be able to have the serializer just create the JSON without reaching out to the DB. |
@richmolj asks:
and @natesholland asks similarly:
I answer: This isn't surprising. If your serializer maps However, I do think we might want to reconsider the behavior of the JSON API adapter to only serialize relationships when included, i.e. to treat serialized attributes differently. That would be a change in how it works now, where relationships are treated (more or less) as attributes, but probably is consistent with the spirit of the JSON API spec. I think I'd support a PR for that. As @beauby wrote:
This would work even without any change in how we treat relationships, except that it would be pretty inconvenient to exclude attributes via the URL, since, in practice, that means specifying all of them except the excluded one. Excluding relationships in the controller would require manipulating the However, also with any changes, you could customize a serializer by overwriting DEFAULT_INCLUDE_TREE = ActiveModel::Serializer::IncludeTree.from_string('*')
# @param [IncludeTree] include_tree (defaults to all associations when not provided)
# @return [Enumerator<Association>]
#
def associations(include_tree = DEFAULT_INCLUDE_TREE)
return unless object class PostSerializer
has_many :comments
+ def associations(include_tree = {})
+ super(include_tree)
+ end
end (@beauby can correct any details in what I said above. I'm still foggy on what IncludeTree means, does, inputs, or outputs) |
@bf4 thanks for the detailed response. I agree with most of what you've said, except the last bit on Please correct me if I'm misunderstanding, but:
This sounds good, but keep in mind the scenario where you do want to serialize relationship |
Looks like that will solve my problem 👍 |
I admit I am not going to help ayone with my comment because, even if everyone agrees with me, it is too late do change things. I don't mean to sound ungrateful, as I am using AMS freely, but I am quite unhappy with it. IMHO, there is a paradox concerning how AMS is meant to be used and why AMS is used at all. The how part regards its conception: AMS was built with the model in mind, so it was modeled like a model, with class methods like "has_one", "attributes" and so forth. The why part regards its usage: AMS is used to produce representations of a model, which may vary under the circuntances. The paradox resides in offering an API meant for a whole-thing instead of a particular view of a thing. The API offered for producing a particular view of such whole-thing is cumbersome and feels difficult to grasp. To name a few: "scope", " instance_options", "scope_name", the recent "if" option for the class method "attribute" and so forth. IMHO, these things make clear that the tools for producing particular views of a whole-thing are secondary, accessory. The reason why AMS is used should have defined how it is supposed to be used. AMS should have been modeled after controllers, not models, because controllers are responsible for handling out particular views of whole-things. For index actions, a few attributes should suffice. For show actions, most attributes are needed. For editing, private-viewing, even more attributes. Some actions require the relationships of a whole-thing, some don't. So, instead of a UserSerializer, I would like to have a UsersSerializer, with actions matching a the ones of a controller. In practice, I ended up having a serializer class for action for every controller because modeling the data is too cumbersome. |
@phcoliveira I'm sort of surprised by your comment. Are you responding to something here? Is there an issue we can address? This history of AMS is around serializing models aka resources. https://github.com/rails-api/active_model_serializers/blob/master/CHANGELOG.md#first-commit-as-rails-serializers-001 Now, if you have a RESTful API, the url represents the resource, and controller handles the action at that url, at some point you need to define how you represent the resource. Since resources in a CRUD app are models/records, it makes sense to base the serialization on the model/record. Please open a new issue if you'd like to discuss. |
@richmolj closing as it appears this issue is resolved. Use something like has_many :comments do
object.comments.loaded ? object.comments : Comment.none
end |
Yep think this fixes my issue 👍 |
For anyone following this, the suggestion above won't work since the has_many :comments, if: :comments_loaded?
def comments_loaded?
object.comments.loaded?
end will work |
This doesn't solve it for me as the comments are not loaded at that point until I call object.comments and actually load them. So I guess passing the include option doesn't load them? |
@boobooninja that's why I'm trying to get the |
@richmolj @boobooninja And also I found a way to ensure loading associations. Firstly, transform the Then use this At last , call the serializer. This proc ensures that associations required by client will always be loaded, also N+1 issue is avoided. I think in this way, server will have no need to concern client data requirement in different views of the same model(define different serializers of the same model). Client should pass the correct |
@richmolj your solution worked for me, Thanks |
I've been searching through the issues, and this may be relevant to bringing
filter
to 0.10.x, but I couldn't find anything on this specifically.If I use JSONAPI adapter, and put
has_many :comments
in myPostSerializer
, there will be a DB hit to fill out all the ids for the commentrelationships
. Even when noinclude
is specified.I thought of avoiding this by something like
Unfortunately the key
comments
will still appear in the response. This means if my client (Ember) has already loaded the post with its comments, then hits this separate endpoint, it will think all the comments for the post have been deleted (versus just not included in the response).Currently I've been creating a base serializer for every model, then subclassing and adding relationships for each specific endpoint. This not only gets really tedious, but every time I add a relationship I am also specifying
includes
, which makes me think the default should be to not load relationships unlessinclude
is specified. Finally, it makes it quite hard to support an API like/posts/1?include=comments
since I need to conditionally select a serializer that actually includes the requested relationships.Is the correct solution here to wait for
filter
, do the subclassing-style, or something else? Thanks!The text was updated successfully, but these errors were encountered: