diff --git a/CHANGELOG.md b/CHANGELOG.md index f886d3b28..8307c9222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Breaking changes: - [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Default key case for the JsonApi adapter changed to dashed. (@remear) Features: +- [#1699](https://github.com/rails-api/active_model_serializers/pull/1699) String/Lambda support for conditional attributes/associations (@mtsmfm) - [#1687](https://github.com/rails-api/active_model_serializers/pull/1687) Only calculate `_cache_digest` (in `cache_key`) when `skip_digest` is false. (@bf4) - [#1647](https://github.com/rails-api/active_model_serializers/pull/1647) Restrict usage of `serializable_hash` options to the ActiveModel::Serialization and ActiveModel::Serializers::JSON interface. (@bf4) diff --git a/docs/general/serializers.md b/docs/general/serializers.md index b166d9d0a..7b96fe98d 100644 --- a/docs/general/serializers.md +++ b/docs/general/serializers.md @@ -80,6 +80,9 @@ end ```ruby has_one :blog, if: :show_blog? +# you can also use a string or lambda +# has_one :blog, if: 'scope.admin' +# has_one :blog, if: -> (serializer) { serializer.scope.admin? } def show_blog? scope.admin? diff --git a/lib/active_model/serializer/field.rb b/lib/active_model/serializer/field.rb index 35e6fe263..b8ac9099e 100644 --- a/lib/active_model/serializer/field.rb +++ b/lib/active_model/serializer/field.rb @@ -27,9 +27,9 @@ def value(serializer) def excluded?(serializer) case condition_type when :if - !serializer.public_send(condition) + !evaluate_condition(serializer) when :unless - serializer.public_send(condition) + evaluate_condition(serializer) else false end @@ -37,6 +37,19 @@ def excluded?(serializer) private + def evaluate_condition(serializer) + case condition + when Symbol + serializer.public_send(condition) + when String + serializer.instance_eval(condition) + when Proc + condition.call(serializer) + else + fail ArgumentError + end + end + def condition_type @condition_type ||= if options.key?(:if) diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index ed5ce1e05..3cf11d7bd 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -239,7 +239,7 @@ def test_associations_namespaced_resources end end - def test_conditional_associations + def test_symbol_conditional_associations serializer = Class.new(ActiveModel::Serializer) do belongs_to :if_assoc_included, if: :true belongs_to :if_assoc_excluded, if: :false @@ -261,6 +261,36 @@ def false assert_equal(expected, hash) end + + def test_string_conditional_associations + serializer = Class.new(ActiveModel::Serializer) do + belongs_to :if_assoc_included, if: 'object.true' + belongs_to :if_assoc_excluded, if: 'object.false' + belongs_to :unless_assoc_included, unless: 'object.false' + belongs_to :unless_assoc_excluded, unless: 'object.true' + end + + model = ::Model.new(true: true, false: false) + hash = serializable(model, serializer: serializer).serializable_hash + expected = { if_assoc_included: nil, unless_assoc_included: nil } + + assert_equal(expected, hash) + end + + def test_lambda_conditional_associations + serializer = Class.new(ActiveModel::Serializer) do + belongs_to :if_assoc_included, if: -> (s) { s.object.true } + belongs_to :if_assoc_excluded, if: -> (s) { s.object.false } + belongs_to :unless_assoc_included, unless: -> (s) { s.object.false } + belongs_to :unless_assoc_excluded, unless: -> (s) { s.object.true } + end + + model = ::Model.new(true: true, false: false) + hash = serializable(model, serializer: serializer).serializable_hash + expected = { if_assoc_included: nil, unless_assoc_included: nil } + + assert_equal(expected, hash) + end end end end diff --git a/test/serializers/attribute_test.rb b/test/serializers/attribute_test.rb index b4a441c69..6a5421409 100644 --- a/test/serializers/attribute_test.rb +++ b/test/serializers/attribute_test.rb @@ -96,7 +96,7 @@ def test_virtual_attribute_block assert_equal(expected, hash) end - def test_conditional_attributes + def test_symbol_conditional_attributes serializer = Class.new(ActiveModel::Serializer) do attribute :if_attribute_included, if: :true attribute :if_attribute_excluded, if: :false @@ -118,6 +118,36 @@ def false assert_equal(expected, hash) end + + def test_string_conditional_attributes + serializer = Class.new(ActiveModel::Serializer) do + attribute :if_attribute_included, if: 'object.true' + attribute :if_attribute_excluded, if: 'object.false' + attribute :unless_attribute_included, unless: 'object.false' + attribute :unless_attribute_excluded, unless: 'object.true' + end + + model = ::Model.new(true: true, false: false) + hash = serializable(model, serializer: serializer).serializable_hash + expected = { if_attribute_included: nil, unless_attribute_included: nil } + + assert_equal(expected, hash) + end + + def test_lambda_conditional_attributes + serializer = Class.new(ActiveModel::Serializer) do + attribute :if_attribute_included, if: -> (s) { s.object.true } + attribute :if_attribute_excluded, if: -> (s) { s.object.false } + attribute :unless_attribute_included, unless: -> (s) { s.object.false } + attribute :unless_attribute_excluded, unless: -> (s) { s.object.true } + end + + model = ::Model.new(true: true, false: false) + hash = serializable(model, serializer: serializer).serializable_hash + expected = { if_attribute_included: nil, unless_attribute_included: nil } + + assert_equal(expected, hash) + end end end end