-
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
Attribute filtering for 0.10.x #1141
Conversation
@@ -23,6 +23,11 @@ def test_attributes_with_fields_option | |||
@profile_serializer.attributes(fields: [:name])) | |||
end | |||
|
|||
def test_attributes_with_fields_method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you add a couple more tests with a couple more scenarios?
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like, what happens when there are no fields on the serializer?, etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fields
method case could be more explicitly tested. There is already a case that covers that behaviour, though (https://github.com/lautis/active_model_serializers/blob/fields-filtering/test/serializers/attributes_test.rb#L39).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test cases to cover no overridden fields and combination of fields method and option.
afa0361
to
63459f7
Compare
end | ||
|
||
def test_attributes_with_fields_option_and_method | ||
@profile_serializer.define_singleton_method(:fields) do |keys| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because you wrote the method fields, you should test how that plays with various seriolizers. This test is testing the overridden method, which wouldn't touch the new code you wrote.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I'm quite following you. Granted, I'm not very familiar with the code base so I could be missing something.
There are tests verify the behavior of the introduced method method fields
. This is pretty much covered by existing tests. Also the output of attributes
without overwritten method and options is tested. I'm quite sure that mutations would be catched by the tests.
Is there serializer classes that would overwrite the default behaviour? Or some cases where you'd think an integration test would be needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just mean that your test isn't testing https://github.com/rails-api/active_model_serializers/pull/1141/files#diff-5624f9cf35be4dc254412d979fd293c4R129 or https://github.com/rails-api/active_model_serializers/pull/1141/files#diff-5624f9cf35be4dc254412d979fd293c4R140 but is testing only https://github.com/rails-api/active_model_serializers/pull/1141/files#diff-be102247b73584d61072b4ac379b6e5fR32
But I see what you're saying. I'm going to leave it up to someone else to decide whether or not this is ready for merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I am concerned, I would only care about thoroughly testing the output of attributes
depending on the definition of fields
. Once that's ensured, there is no difference on the rest of the pipeline.
It might be worth making it possible to specify the fields when calling |
@beauby, supporting |
I think I'm good with this. Anybody else good with this? :-) |
👍 |
However I agree with @beauby on all of his points so if we think that's something that should be done, I think we should tackle it in this PR. |
@@ -125,6 +125,10 @@ def json_key | |||
@root || object.class.model_name.to_s.underscore | |||
end | |||
|
|||
def fields(keys) | |||
keys | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to narrow the Serializer interface rather than broaden it. Every method we add has the potential to interfere with an attribute on the model.
Also, with respect to this change, couldn't you just use the key option?
class FooSerializer < AM::Serializer
attribute :name, key: title
end
FooSerializer.new(foo).attributes
FooSerializer._attributes_keys
FooSerializer._attributes
If you want to select which attributes to use, it really needs to be when the serializer is instantiated, because you don't have control of its rendering from the controller. I'm also against merging this until the Also, see my comment above #1141 (comment) |
I'd also rather use the rails convention of |
If fields is removed, how could the serialisation be customised? In a simple case, the same functionality could be achieved by implementing class methods and using those in controller ( |
fields is already sent to the adapter, and used by jsonapi in ActiveModel::Serializer::Fieldset, does that help? def resource_object_for(serializer, options = {})
options[:fields] = fieldset && fieldset.fields_for(serializer)
attributes = serializer.attributes(options).except(:id) and in the serializer def attributes(options = {})
attributes =
if options[:fields]
self.class._attributes & options[:fields]
else
self.class._attributes.dup
end so, basically, this is already available |
That is a very good start, but I was more concerned how to make that manageable. My use case is that sometimes – likely in few different places – it is necessary to return a subset of the full JSON representation. This would likely involve multiple serializers with relations. With There seems to be some recent work to make that easy in #1158. #1157 might also help. While it is the responsibility of controller to pass the full list of included/excluded attributes, it isn't very different to have |
#1193 brings in an even cooler syntax for nested serializers. |
@beauby Do you plan on implementing the example |
@al3x-edge I'm currently working on a |
I changed the method name to |
👍 |
@lautis could you maybe add a little something to the readme for how to use this? thanks! |
6b54a08
to
ec22333
Compare
@NullVoxPopuli added documentation, but it's a bit inconvenient being the first mention about serializer scope / |
Totally agreed - and as far as I know, it's on all our radars. :-) |
Add a method named include_attribute that by default returns all fields. Actual serialiser implementations can override this method to dynamically filter the list of attributes to be serialized.
ec22333
to
2111cee
Compare
All good. Wasn't sure if this was being abandoned for another approach. Thanks for the hard work from everyone and the follow-ups. 👍 |
I my projects, based on user's permission role I would return different representation of my model (higher the role, the more attributes user would get). That was really easy using only: option in 0.9.x serializers. I guess you are trying to implement the same thing here. Ideally I would like to be able to filter attributes on embedded associations too. So if I request a video which has a user model embedded, I would like to not only filter which video attributes I want but also which user attributes I want. Would that be possible ? I am pretty sure JSONAPI specs supports and embraces that pattern. |
@vasilakisfil JSON API does not state much about that. Currently, you could override the instance PostSerializer < ActiveModel::Serializer
def attributes(*)
default_attributes = super
... do your stuff about filtering your attributes ...
end
end |
@beauby the thing is that we should opt for filtering the attributes from outside the serializers, that is, from the constructor just as it used to be in 0.9.x with the only: option. Not only due to permission issue but also exactly because JSONAPI embraces the 'fields' query in which client is able to ask specific attributes of a resource we should be able to specify which attributes should be allowed from the constructor. Having said that, ideally you should be able to filter the attributes on the requested embedded resources. Although not explicitly clarified in the JSONAPI I think it's a best practice anyway and an implementation should take into account this potential feature. |
There are two things here:
|
else | ||
included - [:author] | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a lot of work just to be able to
def attributes(*)
super.tap do |attrs|
# not sure where current_user is coming from..
# is it an attribute on the object?
# an accessor on the serializer?
attrs.delete(:author) unless current_user.admin?
end
I think the better approach would be just OO
class PostSerializer < AM::S
attributes :id, :body
end
class AdminPostSerializer < PostSerializer
attributes: author
end
I really just don't think this is a problem that needs to be solved in AMS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty 👎 on this till I'm convinced it's necessary, helpful, domain-appropriate, and doesn't introduce unnecessary complexity to AMS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a lot of work just to be able to
def attributes(*) super.tap do |attrs| # not sure where current_user is coming from.. # is it an attribute on the object? # an accessor on the serializer? attrs.delete(:author) unless current_user.admin? end
The PR is different as it allows serializer to skip the computation of the attributes. Overriding attributes
does not work when you're concerned about performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the OO approach, how could that be easily used with has_many
? Is it possible to change the serializer for relationships dynamically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example plz
I'm going to close this for now until we can get an implementation that I think is an improvement on what AMS already does. This isn't a knock on OP, just that, as a maintainer, I'm conservative about what to let in. basically, the current state of this PR
Ref: |
Understand your position on this as a maintainer and haven't tried the overrides. Will the overrides become the default way to override attributes? Will changes to attributes be considered public API so we aren't surprised by a change later? Bummed on this change and doesn't seem pragmatic. |
The With the current fields implementation, you can have a method and use that to generate list of attributes Regarding the focus on Serializers, I don't quite understand how adding dynamic attributes for serialisers could not be focused on Serializer. That is, after all, the level where the attributes are introduced in the first place. Maybe the tests could focus more on full use case instead of unit tests, but unit tests seem to be how the current tests are written. |
The PR at the time I closed it either wouldn't work or would provide redundant features depending on what you're trying to do. A compromise could be something like if any attributes have the value Besides that, none of the use-cases I've seen require a change in AMS |
My personal feeling on this is that the user should be able to provide to the adapter a |
@lautis wrote:
not sure what you mean. You want to supply a list of attributes and then remove some instead of just specify the ones you want?
I mean passing in arguments to |
No. I'd hope to have a way of dynamically ignoring some attributes without having to reimplement serialiser structures. This could be accomplished at least with
No arguments on the use of arguments for attributes, but that is how the existing functionality is tested. I'd suggest fixing the existing tests to not have confusing examples. |
I agree with @lautis a lot. I am not sure how it should be implemented but as a user of this gem I would like to have an option to inject from the constructor which attributes I want to allow to serialize from an object. Ideally I should be able to specify a list of attributes in the associations too. Just like how strong params work. There was an option for that in 0.8 and 0.9. |
To be clear, we just need an example of something you can't do now.
I would distinguish between public methods and how to use the gem. Eventually I intend to port my RSpec serializer tests into this lib, and it will test attributes. BUT, calling Believe me that I've hacked this library, too, but please believe me that what is in the PR won't get what the PR intends, which is why I closed it. I also want to update the docs so that how AMS works is clearer. Wanna help? :) And again, we just need an example of something you can't do now. Then we can work backwards to an implementation. |
I believe what's not currently doable is to dynamically specify the attributes of an associated resource (which JSON API makes possible by specifying attributes per-type). |
This is something I've (ab)used in 0.8.x quite frequently. Adds a method named filter that by default returns all fields. Actual serialised implementations can override this method to dynamically filter the list of attributes to be serialized.
Implementation based on #1058.