Skip to content

Commit 6370e5c

Browse files
committed
Fix read_attribute_for_serialization not seeing parent serializer methods
Fixes #1653, #1658, #1660 Define "scope_name" on instance singleton, not all instances
1 parent 0e82f6b commit 6370e5c

File tree

4 files changed

+84
-41
lines changed

4 files changed

+84
-41
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ Features:
4141
- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby)
4242

4343
Fixes:
44+
- [#1661](https://github.com/rails-api/active_model_serializers/pull/1661) Fixes `read_attribute_for_serialization` not
45+
seeing methods defined in serialization superclass (#1653, #1658, #1660), introduced in #1650. (@bf4)
4446
- [#1651](https://github.com/rails-api/active_model_serializers/pull/1651) Fix deserialization of nil relationships. (@NullVoxPopuli)
4547
- [#1480](https://github.com/rails-api/active_model_serializers/pull/1480) Fix setting of cache_store from Rails configuration. (@bf4)
46-
Fix uninentional mutating of value in memory cache store. (@groyoh)
48+
Fix unintentional mutating of value in memory cache store. (@groyoh)
4749
- [#1622](https://github.com/rails-api/active_model_serializers/pull/1622) Fragment cache changed from per-record to per-serializer.
4850
Now, two serializers that use the same model may be separately cached. (@lserman)
4951
- [#1478](https://github.com/rails-api/active_model_serializers/pull/1478) Cache store will now be correctly set when serializers are

lib/active_model/serializer.rb

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,6 @@ def self.get_serializer_for(klass)
9696
end
9797
end
9898

99-
# rubocop:disable Style/ClassVars
100-
def self.method_added(method_name)
101-
@@_serializer_instance_methods ||= Hash.new { |h, k| h[k] = Set.new }
102-
@@_serializer_instance_methods[self] << method_name
103-
end
104-
105-
def self._serializer_instance_method_defined?(name)
106-
@_serializer_instance_methods ||= (ActiveModel::Serializer.public_instance_methods - Object.public_instance_methods).to_set
107-
@_serializer_instance_methods.include?(name) ||
108-
@@_serializer_instance_methods[self].include?(name)
109-
end
110-
# rubocop:enable Style/ClassVars
111-
11299
attr_accessor :object, :root, :scope
113100

114101
# `scope_name` is set as :current_user by default in the controller.
@@ -122,9 +109,7 @@ def initialize(object, options = {})
122109

123110
scope_name = instance_options[:scope_name]
124111
if scope_name && !respond_to?(scope_name)
125-
self.class.class_eval do
126-
define_method scope_name, lambda { scope }
127-
end
112+
define_singleton_method scope_name, lambda { scope }
128113
end
129114
end
130115

@@ -189,7 +174,7 @@ def json_key
189174
end
190175

191176
def read_attribute_for_serialization(attr)
192-
if self.class._serializer_instance_method_defined?(attr)
177+
if respond_to?(attr)
193178
send(attr)
194179
elsif self.class._fragmented
195180
self.class._fragmented.read_attribute_for_serialization(attr)

test/action_controller/serialization_scope_name_test.rb

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
require 'test_helper'
22

33
module SerializationScopeTesting
4-
def self.undef_serializer_dynamic_scope_methods
5-
[PostSerializer, PostViewContextSerializer]. each do |serializer_class|
6-
instance_method_cache = serializer_class.instance_variable_get(:@_serializer_instance_methods)
7-
class_method_cache = serializer_class.class_variable_get(:@@_serializer_instance_methods)[serializer_class]
8-
[:view_context, :current_user].each do |scope_method|
9-
serializer_class.send(:undef_method, scope_method) if serializer_class.method_defined?(scope_method)
10-
instance_method_cache && instance_method_cache.delete(scope_method)
11-
class_method_cache && class_method_cache.delete(scope_method)
12-
end
13-
end
14-
end
154
class User < ActiveModelSerializers::Model
165
attr_accessor :id, :name, :admin
176
def admin?
@@ -81,10 +70,6 @@ def comments
8170
class DefaultScopeTest < ActionController::TestCase
8271
tests PostTestController
8372

84-
teardown do
85-
SerializationScopeTesting.undef_serializer_dynamic_scope_methods
86-
end
87-
8873
def test_default_serialization_scope
8974
assert_equal :current_user, @controller._serialization_scope
9075
end
@@ -135,10 +120,6 @@ def serializer
135120
end
136121
tests PostViewContextTestController
137122

138-
teardown do
139-
SerializationScopeTesting.undef_serializer_dynamic_scope_methods
140-
end
141-
142123
def test_defined_serialization_scope
143124
assert_equal :view_context, @controller._serialization_scope
144125
end
@@ -206,10 +187,6 @@ def new_post
206187
end
207188
tests PostViewContextTestController
208189

209-
teardown do
210-
SerializationScopeTesting.undef_serializer_dynamic_scope_methods
211-
end
212-
213190
def test_nil_serialization_scope
214191
assert_nil @controller._serialization_scope
215192
end
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require 'test_helper'
2+
3+
module ActiveModel
4+
class Serializer
5+
class ReadAttributeForSerializationTest < ActiveSupport::TestCase
6+
# https://github.com/rails-api/active_model_serializers/issues/1653
7+
class Parent < ActiveModelSerializers::Model
8+
attr_accessor :id
9+
end
10+
class Child < Parent
11+
attr_accessor :name
12+
end
13+
class ParentSerializer < ActiveModel::Serializer
14+
attributes :$id
15+
16+
define_method(:$id) do
17+
object.id
18+
end
19+
end
20+
class ChildSerializer < ParentSerializer
21+
attributes :name
22+
end
23+
24+
def test_child_serializer_calls_dynamic_method_in_parent_serializer
25+
parent = ParentSerializer.new(Parent.new(id: 5))
26+
child = ChildSerializer.new(Child.new(id: 6, name: 'Child'))
27+
assert_equal 5, parent.read_attribute_for_serialization(:$id)
28+
assert_equal 6, child.read_attribute_for_serialization(:$id)
29+
end
30+
31+
# https://github.com/rails-api/active_model_serializers/issues/1658
32+
class ErrorResponse < ActiveModelSerializers::Model
33+
attr_accessor :error
34+
end
35+
class ApplicationSerializer < ActiveModel::Serializer
36+
attributes :status
37+
38+
def status
39+
object.try(:errors).blank? && object.try(:error).blank?
40+
end
41+
end
42+
class ErrorResponseSerializer < ApplicationSerializer
43+
attributes :error
44+
end
45+
class ErrorResponseWithSuperSerializer < ApplicationSerializer
46+
attributes :error
47+
48+
def success
49+
super
50+
end
51+
end
52+
53+
def test_child_serializer_with_error_attribute
54+
error = ErrorResponse.new(error: 'i have an error')
55+
serializer = ErrorResponseSerializer.new(error)
56+
serializer_with_super = ErrorResponseWithSuperSerializer.new(error)
57+
assert_equal false, serializer.read_attribute_for_serialization(:status)
58+
assert_equal false, serializer_with_super.read_attribute_for_serialization(:status)
59+
end
60+
61+
def test_child_serializer_with_errors
62+
error = ErrorResponse.new
63+
error.errors.add(:invalid, 'i am not valid')
64+
serializer = ErrorResponseSerializer.new(error)
65+
serializer_with_super = ErrorResponseWithSuperSerializer.new(error)
66+
assert_equal false, serializer.read_attribute_for_serialization(:status)
67+
assert_equal false, serializer_with_super.read_attribute_for_serialization(:status)
68+
end
69+
70+
def test_child_serializer_no_error_attribute_or_errors
71+
error = ErrorResponse.new
72+
serializer = ErrorResponseSerializer.new(error)
73+
serializer_with_super = ErrorResponseWithSuperSerializer.new(error)
74+
assert_equal true, serializer.read_attribute_for_serialization(:status)
75+
assert_equal true, serializer_with_super.read_attribute_for_serialization(:status)
76+
end
77+
end
78+
end
79+
end

0 commit comments

Comments
 (0)