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 if/unless options to attribute #1266

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def self.digest_caller_file(caller_line)
self._attributes_keys ||= {}
class_attribute :_links # @api private : links definitions, @see Serializer#link
self._links ||= {}

class_attribute :_attributes_conditions # @api private : maps condition of attributes
self._attributes_conditions ||= {}
serializer.class_attribute :_cache # @api private : the cache object
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
Expand All @@ -76,6 +77,7 @@ def self.inherited(base)
base._attributes = _attributes.dup
base._attributes_keys = _attributes_keys.dup
base._links = _links.dup
base._attributes_conditions = _attributes_conditions.dup
base._cache_digest = digest_caller_file(caller_line)
super
end
Expand All @@ -95,10 +97,11 @@ def self.link(name, value = nil, &block)
# class AdminAuthorSerializer < ActiveModel::Serializer
# attributes :id, :name, :recent_edits
def self.attributes(*attrs)
options = attrs.last.class == Hash ? attrs.pop : {}
attrs = attrs.first if attrs.first.class == Array

attrs.each do |attr|
attribute(attr)
attribute(attr, options)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should support any options for attributes. That's always just a list. We use attribute for an attribute with options

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You want to use

attribute :id, if: :admin?
attribute :titile, if: :admin?

instead of attributes :id, :title, if: :admin? ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@bf4 I'm not sure I understand why we would not want to support options for attributes. Could you elaborate?

Copy link
Member

Choose a reason for hiding this comment

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

value to end-user is minimal, cost to library is relatively high

Copy link
Contributor

Choose a reason for hiding this comment

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

I would usually agree, but since we already support variable number of arguments and array of symbols as parameters to attributes, I don't believe the added cost is that high.

end
end

Expand All @@ -115,6 +118,17 @@ def self.attribute(attr, options = {})
_attributes_keys[attr] = { key: key } if key != attr
_attributes << key unless _attributes.include?(key)

[:if, :unless].each do |switch|
next unless options.key?(switch)
condition = [switch, options]

if _attributes_conditions[condition]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not simply store a pair [Symbol, <Symbol, Proc>] for each key?

_attributes_conditions[condition] << key
else
_attributes_conditions[condition] = [key]
end
end

ActiveModelSerializers.silence_warnings do
define_method key do
object.read_attribute_for_serialization(attr)
Expand Down Expand Up @@ -247,7 +261,9 @@ def json_key
# Return the +attributes+ of +object+ as presented
# by the serializer.
def attributes(requested_attrs = nil)
self.class._attributes.each_with_object({}) do |name, hash|
attributes = self.class._attributes - excluded_attribute_keys

attributes.each_with_object({}) do |name, hash|
next unless requested_attrs.nil? || requested_attrs.include?(name)
if self.class._fragmented
hash[name] = self.class._fragmented.public_send(name)
Expand All @@ -263,8 +279,36 @@ def links
self.class._links
end

def excluded_attribute_keys
self.class
._attributes_conditions
.each_with_object([]) do |((switch, condition), keys), excluded_keys|
case switch
when :if
excluded_keys << keys unless eval_attribute_condition(condition[:if])
when :unless
excluded_keys << keys if eval_attribute_condition(condition[:unless])
else
excluded_keys
end
end.flatten
end

protected

attr_accessor :instance_options

private

def eval_attribute_condition(condition)
case
when condition.is_a?(Symbol)
send(condition)
when condition.respond_to?(:call)
instance_eval(&condition)
else
warn 'The condition is invalid'
end
end
end
end
16 changes: 16 additions & 0 deletions test/serializers/attribute_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ def id

assert_equal('custom', hash[:blog][:id])
end

def test_attributes_conditions
serializer_class = Class.new(ActiveModel::Serializer) do
attribute :id
attributes :name, :title, if: :admin?
attribute :type, unless: proc { true }

def admin?
false
end
end
serializer = serializer_class.new(@blog)

assert_equal([:name, :title, :type], serializer.excluded_attribute_keys)
assert_equal({ id: 1 }, serializer.attributes)
end
end
end
end