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

Add inline syntax to override attributes/associations. #1262

Closed
wants to merge 3 commits into from

Conversation

beauby
Copy link
Contributor

@beauby beauby commented Oct 9, 2015

This PR brings the following syntax for overriding attributes/associations and declaring virtual attributes/associations in a serializer:

class PostSerializer < ActiveModel::Serializer
  attribute :name do
    "#{object.first_name} #{object.last_name}"
  end

  has_many :comments do
    object.comments.limit(10)
  end
end

that is currently equivalent to the usual

class PostSerializer < ActiveModel::Serializer
  attribute :name
  def name
    "#{object.first_name} #{object.last_name}"
  end

  has_many :comments
  def comments
    object.comments.limit(10)
  end
end

Note that this change is not only cosmetic, as it is a prerequisite to move away from the old way (defining methods on the serializer, which caused many issues when the attribute name was that of a reserved method name (ref #1261)), and refactor the way attributes are handled in a safer way.

Update: added overriding of associations as well + factoring out of the attributes logic.

@bf4
Copy link
Member

bf4 commented Oct 9, 2015

Nice notion! But it increases complexity for a solved problem. If you want to prove the problem isn't solved, and this fixes it, use attributes named 'test' and 'select' that fail before the change and work after.

i.e

attribute :select
def select
  object.banana
end

vs.

attribute :select do
  object.banana
end

@beauby
Copy link
Contributor Author

beauby commented Oct 9, 2015

@bf4 You're right, in the current state of things, this does not solve the aforementioned problem. However, in order to solve it, we need to get rid of users defining a virtual/overridden attribute as a method on the serializer, which this PR offers a way to.
The next PR in this series will extract attributes handling into a mixin and store the attributes' values into a hash instead of defining a method on the serializer.

@bf4
Copy link
Member

bf4 commented Oct 9, 2015

I still don't see how this helps in any way. Show me! ;)

B mobile phone

On Oct 9, 2015, at 1:18 PM, Lucas Hosseini notifications@github.com wrote:

@bf4 You're right, in the current state of things, this does not solve the aforementioned problem. However, in order to solve it, we need to get rid of users defining a virtual/overridden attribute as a method on the serializer, which this PR offers a way to.
The next PR in this series will extract attributes handling into a mixin and store the attributes' values into a hash instead of defining a method on the serializer.


Reply to this email directly or view it on GitHub.

@beauby
Copy link
Contributor Author

beauby commented Oct 10, 2015

@bf4 See #1263. Once we remove the public_send in attribute_value, no more clash with existing methods.

@bf4
Copy link
Member

bf4 commented Oct 10, 2015

Well, I still want to hold on this

B mobile phone

On Oct 10, 2015, at 8:32 AM, Lucas Hosseini notifications@github.com wrote:

@bf4 See #1263. Once we remove the public_send in attribute_value, no more clash with existing methods.


Reply to this email directly or view it on GitHub.

@beauby
Copy link
Contributor Author

beauby commented Oct 10, 2015

@bf4 Sure, let's talk about it when you're back.

@bf4 bf4 added the S: Hold label Oct 12, 2015
end

def test_virtual_attribute_block
post = PostWithVirtualAttribute.new(first_name: 'Lucas', last_name: 'Hosseini')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case, you might want to test post.attributes since that's the immediate case.. tests less layers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see this too.

@beauby beauby changed the title Add inline syntax to override attributes. Add inline syntax to override attributes/associations. Oct 19, 2015
end
end

def attribute(attr, options = {}, &block)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the &block param.

@bf4
Copy link
Member

bf4 commented Oct 22, 2015

I'm still very 👎 on this. Sorry. If you weren't a collaborator I would close it.

@beauby
Copy link
Contributor Author

beauby commented Oct 22, 2015

@bf4 Any specific reason?

@bf4
Copy link
Member

bf4 commented Oct 22, 2015

I don't think it's good for AMS.

@beauby
Copy link
Contributor Author

beauby commented Oct 22, 2015

Would you care to give

any specific reason?

@beauby
Copy link
Contributor Author

beauby commented Oct 22, 2015

Because as you can imagine, I do think it's good for AMS, so it's kind of a Mexican standoff.

@bf4
Copy link
Member

bf4 commented Oct 22, 2015 via email

_attributes << key unless _attributes.include?(key)

ActiveModelSerializers.silence_warnings do
define_method key do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this absolutely needs to be document above the method signature with some examples.
I am a HUUUGE fan of metaprogramming, but it can be a pain in the butt if people aren't expecting it. So, having some example syntax in a method yardoc would be fabulous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, although this code is already in AMS. I just added the if block_given?; ...; else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never hurts to add documentation. Code pre-existing isn't an excuse to not add docs. :-\

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, as I said, I agree. I was just making clear that I didn't just throw loads of metaprogramming magic without comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh :-) 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. we should be conservative in adding new interfaces because then we need to support it
  2. if we need this feaure, why? If we don't need it, what are the trade offs of adding it? Not adding it?
  3. how does it affect ease of understand usage? Ease of maintenaince? Complexity of either?
  4. is it extending existing behavior or modifying it? Could it bd done another way?
  5. how do you see this feature evolving? What are its limitations? Can it be easily customized? Eg adapter no longer need to be in thd ams namespace or even inherit the base. So, it's easy to extend (though the boundaries could be clearer and the interfaces more consitent. Am::s should probably be called a resource and implement the serializers::json interface
    (Writing in phone; apologies for anything unclear)

@NullVoxPopuli
Copy link
Contributor

ok, so --

I am a fan of, especially for the json api, how there are numerous things that can be configured via blocks. I think that's very consistent, and doesn't leave our users guessing as to which syntax to use.

I do agree that the way module inheritance has been going has been kinda sketchy, but I believe that is a bigger overall architecture discussion for another time.

@beauby, you have my 👍

@bf4, do have any specific concerns about this for AMS?

I do know some static analysis tools will complain if there is too much code at the class level, and prefer things to be done in methods (as they are more easily testable, stubbable, greppable etc)

@beauby
Copy link
Contributor Author

beauby commented Oct 25, 2015

@bf4:

  1. we should be conservative in adding new interfaces because then we need to support it

I agree. We should also provide the best possible interfaces to our users.

  1. if we need this feaure, why? If we don't need it, what are the trade offs of adding it? Not adding it?

This feature has two main benefits:

  • When overriding attributes/associations (or declaring virtual attributes/associations), it forces the user to have the redefinition next to the definition, thus encouraging code clarity.
  • If we want to move away from dynamically declaring methods on the serializer in order to access the object's properties (which causes clashes with existing method names, and hard to understand errors) , we need to provide a different way to override attributes/associations (which this feature does).
  1. how does it affect ease of understand usage? Ease of maintenaince? Complexity of either?

As stated above, I believe it improves ease of usage. Complexity and ease of maintenance are hardly increased.

  1. is it extending existing behavior or modifying it? Could it bd done another way?

Currently, it is extending existing behavior. It could be done in an other way.

  1. how do you see this feature evolving? What are its limitations? Can it be easily customized?

I'm not sure about what you mean by evolving, limitations and customization in this setting.

Eg adapter no longer need to be in thd ams namespace or even inherit the base. So, it's easy to extend (though the boundaries could be clearer and the interfaces more consitent.

Not sure I understand. This PR does not deal with adapters in any way.

Am::s should probably be called a resource

Sounds sensible

and implement the serializers::json interface

The ActiveModel::Serializers::JSON interface is just as_json/from_json. What do you mean?

@bf4
Copy link
Member

bf4 commented Oct 27, 2015

like I said, it's pretty similar, but I don't like passing the block all over the place. I'd like a more surgical change.

@bf4
Copy link
Member

bf4 commented Oct 27, 2015

Also, what I described solves a problem. It's more than just sugar

@beauby
Copy link
Contributor Author

beauby commented Oct 28, 2015

@bf4 As I stated in the PR description, this is only part 1 of a 2 part PR:

Note that this change is not only cosmetic, as it is a prerequisite to move away from the old way (defining methods on the serializer, which caused many issues when the attribute name was that of a reserved method name (ref #1261)), and refactor the way attributes are handled in a safer way.

Plus, at the end of the day, this PR is passing a proc around, not a block.

@beauby beauby mentioned this pull request Oct 28, 2015
@beauby
Copy link
Contributor Author

beauby commented Oct 28, 2015

So to sum up my vision of things here:

  • I'd like to introduce this inline block syntax to override attributes and associations (and declare virtual ones)
  • Because it promotes cleaner serializers for the users (all the logic for one attribute/association kept in one place of the file)
  • And also so that we can remove this nasty dynamic method generation for each attribute/association, which leads to very surprising errors (c.f. mentioned issues)
  • So that we're left with clean serializer classes, on which all the methods defined are AMS ones, and possibly some user-defined which could be used for
    • conditionally including/excluding attributes/associations (the proposed include_if / exclude_if options, which would only take symbols (referring to user-defined methods), no lambdas)
    • conditionally chosing the serializer for an association (the proposed extension of the serializer options which could take a String, a Constant, or a Symbol (referring to a user-defined method).

@bf4
Copy link
Member

bf4 commented Oct 29, 2015

passing a proc around, not a block.

I'm not sure I understand the significance of the distinction. I don't follow. It's a callable object. Proc, Lambda, Method are all objects that are all callable.

@beauby
Copy link
Contributor Author

beauby commented Oct 29, 2015

You said

I don't like passing the block all over the place

I said

this PR is passing a proc around, not a block.

@bf4
Copy link
Member

bf4 commented Oct 29, 2015

In any case, I think the change in this PR could be much smaller, solve the problem ( no need for #1263 ), and actually improve the design and clarity of the code, as I discussed in #1262 (comment)

Still needs work, IMHO. Let's focus this PR on defining/encapsulating attribute conversions at the class level. Let me know if you want to pair.

@bf4
Copy link
Member

bf4 commented Nov 10, 2015

Where are we on this? want me to submit a PR to your branch?

@beauby
Copy link
Contributor Author

beauby commented Nov 11, 2015

Will update soon.

@bf4
Copy link
Member

bf4 commented Nov 11, 2015

awesomes

@beauby
Copy link
Contributor Author

beauby commented Nov 13, 2015

@bf4 See 1311 which covers half of this PR (the associations part).

class << base
attr_accessor :_reflections
end
base.class_attribute :_reflections
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you intend to allow instance reader/writers? did you intend to make it inheritable?

bf4 added a commit to bf4/active_model_serializers that referenced this pull request Nov 30, 2015
beauby added a commit to bf4/active_model_serializers that referenced this pull request Nov 30, 2015
@bf4
Copy link
Member

bf4 commented Nov 30, 2015

@beauby I put together something somewhat minimal based on your work here #1356 (which includes your commits)

bf4 added a commit to bf4/active_model_serializers that referenced this pull request Dec 1, 2015
beauby added a commit to bf4/active_model_serializers that referenced this pull request Dec 1, 2015
bf4 added a commit to bf4/active_model_serializers that referenced this pull request Dec 1, 2015
beauby added a commit to bf4/active_model_serializers that referenced this pull request Dec 1, 2015
bf4 added a commit to bf4/active_model_serializers that referenced this pull request Dec 2, 2015
beauby added a commit to bf4/active_model_serializers that referenced this pull request Dec 2, 2015
@bf4
Copy link
Member

bf4 commented Dec 14, 2015

Closed by #1356

@bf4 bf4 closed this Dec 14, 2015
joaomdmoura pushed a commit to joaomdmoura/active_model_serializers that referenced this pull request Jan 19, 2016
joaomdmoura pushed a commit to joaomdmoura/active_model_serializers that referenced this pull request Jan 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants