Merge pull request #1403 from beauby/conditional-attributes

Conditional attributes/associations (if/unless).
This commit is contained in:
Lucas Hosseini 2016-01-13 07:50:59 +01:00
commit 53c59a1510
8 changed files with 139 additions and 23 deletions

View File

@ -16,6 +16,7 @@ Breaking changes:
Features:
- [#1403](https://github.com/rails-api/active_model_serializers/pull/1403) Add support for if/unless on attributes/associations (@beauby)
- [#1248](https://github.com/rails-api/active_model_serializers/pull/1248) Experimental: Add support for JSON API deserialization (@beauby)
- [#1378](https://github.com/rails-api/active_model_serializers/pull/1378) Change association blocks
to be evaluated in *serializer* scope, rather than *association* scope. (@bf4)

View File

@ -88,6 +88,7 @@ module ActiveModel
Enumerator.new do |y|
self.class._reflections.each do |reflection|
next if reflection.excluded?(self)
key = reflection.options.fetch(:key, reflection.name)
next unless include_tree.key?(key)
y.yield reflection.build_association(self, instance_options)

View File

@ -1,13 +1,25 @@
require 'active_model/serializer/field'
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
# Holds all the meta-data about an attribute as it was specified in the
# ActiveModel::Serializer class.
#
# @example
# class PostSerializer < ActiveModel::Serializer
# attribute :content
# attribute :name, key: :title
# attribute :email, key: :author_email, if: :user_logged_in?
# attribute :preview do
# truncate(object.content)
# end
#
# def user_logged_in?
# current_user.logged_in?
# end
# end
#
class Attribute < Field
end
end
end

View File

@ -17,6 +17,7 @@ module ActiveModel
def attributes(requested_attrs = nil, reload = false)
@attributes = nil if reload
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
next if attr.excluded?(self)
next unless requested_attrs.nil? || requested_attrs.include?(key)
hash[key] = attr.value(self)
end
@ -54,7 +55,7 @@ module ActiveModel
# end
def attribute(attr, options = {}, &block)
key = options.fetch(:key, attr)
_attributes_data[key] = Attribute.new(attr, block)
_attributes_data[key] = Attribute.new(attr, options, block)
end
# @api private

View File

@ -0,0 +1,56 @@
module ActiveModel
class Serializer
# Holds all the meta-data about a field (i.e. attribute or association) as it was
# specified in the ActiveModel::Serializer class.
# Notice that the field block is evaluated in the context of the serializer.
Field = Struct.new(:name, :options, :block) do
# Compute the actual value of a field for a given serializer instance.
# @param [Serializer] The serializer instance for which the value is computed.
# @return [Object] value
#
# @api private
#
def value(serializer)
if block
serializer.instance_eval(&block)
else
serializer.read_attribute_for_serialization(name)
end
end
# Decide whether the field should be serialized by the given serializer instance.
# @param [Serializer] The serializer instance
# @return [Bool]
#
# @api private
#
def excluded?(serializer)
case condition_type
when :if
!serializer.public_send(condition)
when :unless
serializer.public_send(condition)
else
false
end
end
private
def condition_type
@condition_type ||=
if options.key?(:if)
:if
elsif options.key?(:unless)
:unless
else
:none
end
end
def condition
options[condition_type]
end
end
end
end

View File

@ -1,18 +1,24 @@
require 'active_model/serializer/field'
module ActiveModel
class Serializer
# Holds all the meta-data about an association as it was specified in the
# ActiveModel::Serializer class.
#
# @example
# class PostSerializer < ActiveModel::Serializer
# class PostSerializer < ActiveModel::Serializer
# has_one :author, serializer: AuthorSerializer
# has_many :comments
# has_many :comments, key: :last_comments do
# object.comments.last(1)
# end
# end
# has_many :secret_meta_data, if: :is_admin?
#
# def is_admin?
# current_user.admin?
# end
# end
#
# Notice that the association block is evaluated in the context of the serializer.
# Specifically, the association 'comments' is evaluated two different ways:
# 1) as 'comments' and named 'comments'.
# 2) as 'object.comments.last(1)' and named 'last_comments'.
@ -21,20 +27,13 @@ module ActiveModel
# # [
# # HasOneReflection.new(:author, serializer: AuthorSerializer),
# # HasManyReflection.new(:comments)
# # HasManyReflection.new(:comments, { key: :last_comments }, #<Block>)
# # HasManyReflection.new(:secret_meta_data, { if: :is_admin? })
# # ]
#
# So you can inspect reflections in your Adapters.
#
Reflection = Struct.new(:name, :options, :block) do
# @api private
def value(instance)
if block
instance.instance_eval(&block)
else
instance.read_attribute_for_serialization(name)
end
end
class Reflection < Field
# Build association. This method is used internally to
# build serializer's association by its reflection.
#

View File

@ -238,6 +238,29 @@ module ActiveModel
end
end
end
def test_conditional_associations
serializer = Class.new(ActiveModel::Serializer) do
belongs_to :if_assoc_included, if: :true
belongs_to :if_assoc_excluded, if: :false
belongs_to :unless_assoc_included, unless: :false
belongs_to :unless_assoc_excluded, unless: :true
def true
true
end
def false
false
end
end
model = ::Model.new
hash = serializable(model, serializer: serializer).serializable_hash
expected = { if_assoc_included: nil, unless_assoc_included: nil }
assert_equal(expected, hash)
end
end
end
end

View File

@ -4,7 +4,7 @@ module ActiveModel
class Serializer
class AttributeTest < ActiveSupport::TestCase
def setup
@blog = Blog.new({ id: 1, name: 'AMS Hints', type: 'stuff' })
@blog = Blog.new(id: 1, name: 'AMS Hints', type: 'stuff')
@blog_serializer = AlternateBlogSerializer.new(@blog)
end
@ -95,6 +95,29 @@ module ActiveModel
assert_equal(expected, hash)
end
def test_conditional_attributes
serializer = Class.new(ActiveModel::Serializer) do
attribute :if_attribute_included, if: :true
attribute :if_attribute_excluded, if: :false
attribute :unless_attribute_included, unless: :false
attribute :unless_attribute_excluded, unless: :true
def true
true
end
def false
false
end
end
model = ::Model.new
hash = serializable(model, serializer: serializer).serializable_hash
expected = { if_attribute_included: nil, unless_attribute_included: nil }
assert_equal(expected, hash)
end
end
end
end