-
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
Simplify attributes handling. #1370
Changes from all commits
ee0283c
1d4b27f
7d24cbf
a586a45
77095f2
ccb05f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module ActiveModel | ||
class Serializer | ||
Attribute = Struct.new(:name, :block) do | ||
def value(serializer) | ||
if block | ||
serializer.instance_eval(&block) | ||
else | ||
serializer.read_attribute_for_serialization(name) | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,63 +1,32 @@ | ||||||||||||||||
module ActiveModel | ||||||||||||||||
class Serializer | ||||||||||||||||
module Attributes | ||||||||||||||||
# @api private | ||||||||||||||||
class Attribute | ||||||||||||||||
delegate :call, to: :reader | ||||||||||||||||
|
||||||||||||||||
attr_reader :name, :reader | ||||||||||||||||
|
||||||||||||||||
def initialize(name) | ||||||||||||||||
@name = name | ||||||||||||||||
@reader = :no_reader | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
def self.build(name, block) | ||||||||||||||||
if block | ||||||||||||||||
AttributeBlock.new(name, block) | ||||||||||||||||
else | ||||||||||||||||
AttributeReader.new(name) | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
# @api private | ||||||||||||||||
class AttributeReader < Attribute | ||||||||||||||||
def initialize(name) | ||||||||||||||||
super(name) | ||||||||||||||||
@reader = ->(instance) { instance.read_attribute_for_serialization(name) } | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
# @api private | ||||||||||||||||
class AttributeBlock < Attribute | ||||||||||||||||
def initialize(name, block) | ||||||||||||||||
super(name) | ||||||||||||||||
@reader = ->(instance) { instance.instance_eval(&block) } | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
extend ActiveSupport::Concern | ||||||||||||||||
|
||||||||||||||||
included do | ||||||||||||||||
with_options instance_writer: false, instance_reader: false do |serializer| | ||||||||||||||||
serializer.class_attribute :_attribute_mappings # @api private : maps attribute key names to names to names of implementing methods, @see #attribute | ||||||||||||||||
self._attribute_mappings ||= {} | ||||||||||||||||
serializer.class_attribute :_attributes_data # @api private | ||||||||||||||||
self._attributes_data ||= {} | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @beauby For anyone looking back at this, would mind jotting down the advantage of the name change? I know that part of it is that
- serializer.class_attribute :_attribute_mappings # @api private : maps attribute key names to names to names of implementing methods, @see #attribute
+ serializer.class_attribute :_attributes_data # @api private def attributes(requested_attrs = nil, reload = false)
@attributes = nil if reload
- @attributes ||= self.class._attribute_mappings.each_with_object({}) do |(key, attribute_mapping), hash|
+ @attributes ||= self.class._attributes_data.values.each_with_object({}) do |attr, hash| def attribute(attr, options = {}, &block)
- _attribute_mappings[key] = Attribute.build(attr, block)
+ _attributes_data[attr] = Attribute.new(attr, key, block) - # names of attribute methods
+ # keys of attributes
# @see Serializer::attribute
def _attributes
- _attribute_mappings.keys
+ _attributes_data.values.map(&:key) def _attributes_keys
- _attribute_mappings
+ _attributes_data.values
- .each_with_object({}) do |(key, attribute_mapping), hash|
+ .each_with_object({}) do |attr, hash|
- next if key == attribute_mapping.name
+ next if attr.key == attr.name
- hash[attribute_mapping.name] = { key: key }
+ hash[attr.name] = { key: attr.key } + Attribute = Struct.new(:name, :key, :block) do
- (name, block) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, I'd rather have it called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote above:
So, the difference in the data structure is that given a serializer containing attribute :object_attribute
attribute :aliased_attribute, :attribute_alias
attribute :dynamic_attribute { 'much_dynamic' } The difference in _mappings vs. _data key/value pairs would be
both of which resolve to: {
object_attribute: object.send(:object_attribute),
attribute_alias: object.send(:aliased_attribute),
dynamic_attribute: 'dynamic_attribute'
} by applying _attributes to either the _mappings or _data where they are:
which means that the definition of I think this is why in every case I see, the That makes sense? That's the heart of what I was really asking about. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see. Thing is, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you're using a hash to ensure unique attributes via its lookup keys, but otherwise only using the values? Interesting. I believe that's what Set does internally, or functionally similar. What do you see as the downside of just declaring the key in the serializer's data structure and let the attribute just be the value (ie previous vs current)? It sounds like we were both considered having the So, I think whether an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think it maps the serialization's attribute key to its value (or something that can get its value). But, I won't hold the PR for that. Too bad no one else is commenting 😞 - _attribute_mappings[key] = Attribute.new(attr, block) # @api private : maps attribute key names to names to names of implementing methods, @see #attribute
+ _attributes_data[key] = Attribute.new(attr, block) # @api private Code looks good. Just:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed on both, will update later today. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those two remarks were taken care of today. I believe the only thing left is naming? I'd like for other people to chip in as well, but it does not seem like it's going to happen... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm willing to revert the naming back to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
extend ActiveSupport::Autoload | ||||||||||||||||
autoload :Attribute | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or should it be auto-loaded at all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel strongly about this but I followed what was in Associations. However, I didn't put the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a different autoload from the one in ruby. I forget what it adds. Autoload is now thread safe in our supported rubies B mobile phone
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||
|
||||||||||||||||
# Return the +attributes+ of +object+ as presented | ||||||||||||||||
# by the serializer. | ||||||||||||||||
def attributes(requested_attrs = nil, reload = false) | ||||||||||||||||
@attributes = nil if reload | ||||||||||||||||
@attributes ||= self.class._attribute_mappings.each_with_object({}) do |(key, attribute_mapping), hash| | ||||||||||||||||
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash| | ||||||||||||||||
next unless requested_attrs.nil? || requested_attrs.include?(key) | ||||||||||||||||
hash[key] = attribute_mapping.call(self) | ||||||||||||||||
hash[key] = attr.value(self) | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
module ClassMethods | ||||||||||||||||
def inherited(base) | ||||||||||||||||
super | ||||||||||||||||
base._attribute_mappings = _attribute_mappings.dup | ||||||||||||||||
base._attributes_data = _attributes_data.dup | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
# @example | ||||||||||||||||
|
@@ -85,25 +54,25 @@ def attributes(*attrs) | |||||||||||||||
# end | ||||||||||||||||
def attribute(attr, options = {}, &block) | ||||||||||||||||
key = options.fetch(:key, attr) | ||||||||||||||||
_attribute_mappings[key] = Attribute.build(attr, block) | ||||||||||||||||
_attributes_data[key] = Attribute.new(attr, block) | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
# @api private | ||||||||||||||||
# names of attribute methods | ||||||||||||||||
# keys of attributes | ||||||||||||||||
# @see Serializer::attribute | ||||||||||||||||
def _attributes | ||||||||||||||||
_attribute_mappings.keys | ||||||||||||||||
_attributes_data.keys | ||||||||||||||||
end | ||||||||||||||||
|
||||||||||||||||
# @api private | ||||||||||||||||
# maps attribute value to explict key name | ||||||||||||||||
# @see Serializer::attribute | ||||||||||||||||
# @see Adapter::FragmentCache#fragment_serializer | ||||||||||||||||
def _attributes_keys | ||||||||||||||||
_attribute_mappings | ||||||||||||||||
.each_with_object({}) do |(key, attribute_mapping), hash| | ||||||||||||||||
next if key == attribute_mapping.name | ||||||||||||||||
hash[attribute_mapping.name] = { key: key } | ||||||||||||||||
_attributes_data | ||||||||||||||||
.each_with_object({}) do |(key, attr), hash| | ||||||||||||||||
next if key == attr.name | ||||||||||||||||
hash[attr.name] = { key: key } | ||||||||||||||||
end | ||||||||||||||||
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.
I feel better about this l, since it appears to be a pattern we're using all over. However, I still don't have a good sense if we'll want to keep this style, and would like to mark the object as part of the internal api.
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.
Agreed. Every reference to an
Attribute
object is currently marked as@api private
.