active_model_serializers 0.10.4 → 0.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -3
  3. data/CHANGELOG.md +25 -1
  4. data/README.md +4 -4
  5. data/active_model_serializers.gemspec +1 -1
  6. data/appveyor.yml +9 -3
  7. data/docs/general/logging.md +7 -0
  8. data/docs/general/serializers.md +3 -3
  9. data/docs/howto/add_pagination_links.md +13 -13
  10. data/docs/howto/add_relationship_links.md +24 -21
  11. data/docs/howto/outside_controller_use.md +3 -2
  12. data/docs/howto/serialize_poro.md +46 -5
  13. data/docs/howto/upgrade_from_0_8_to_0_10.md +1 -1
  14. data/lib/active_model/serializer/version.rb +1 -1
  15. data/lib/active_model_serializers.rb +8 -0
  16. data/lib/active_model_serializers/model.rb +108 -30
  17. data/lib/active_model_serializers/test/schema.rb +2 -2
  18. data/lib/generators/rails/resource_override.rb +1 -1
  19. data/test/action_controller/adapter_selector_test.rb +11 -2
  20. data/test/action_controller/json_api/fields_test.rb +15 -6
  21. data/test/action_controller/json_api/transform_test.rb +11 -3
  22. data/test/action_controller/namespace_lookup_test.rb +14 -8
  23. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  24. data/test/action_controller/serialization_test.rb +1 -1
  25. data/test/active_model_serializers/model_test.rb +122 -2
  26. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  27. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +37 -19
  28. data/test/adapter/attributes_test.rb +2 -5
  29. data/test/adapter/json/has_many_test.rb +10 -2
  30. data/test/adapter/json_api/fields_test.rb +11 -3
  31. data/test/adapter/json_api/has_many_test.rb +10 -2
  32. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +22 -5
  33. data/test/adapter/json_api/linked_test.rb +3 -3
  34. data/test/adapter/json_api/links_test.rb +1 -1
  35. data/test/adapter/json_api/relationship_test.rb +1 -1
  36. data/test/adapter/json_api/transform_test.rb +11 -3
  37. data/test/cache_test.rb +100 -28
  38. data/test/collection_serializer_test.rb +23 -10
  39. data/test/fixtures/active_record.rb +45 -10
  40. data/test/fixtures/poro.rb +115 -176
  41. data/test/generators/serializer_generator_test.rb +1 -0
  42. data/test/grape_test.rb +20 -2
  43. data/test/serializers/associations_test.rb +28 -7
  44. data/test/serializers/attribute_test.rb +4 -2
  45. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  46. data/test/serializers/options_test.rb +17 -6
  47. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  48. data/test/serializers/serialization_test.rb +2 -2
  49. data/test/serializers/serializer_for_test.rb +6 -6
  50. data/test/serializers/serializer_for_with_namespace_test.rb +6 -5
  51. data/test/support/rails_app.rb +2 -0
  52. data/test/test_helper.rb +12 -0
  53. metadata +13 -7
@@ -1,51 +1,129 @@
1
- # ActiveModelSerializers::Model is a convenient
2
- # serializable class to inherit from when making
3
- # serializable non-activerecord objects.
1
+ # ActiveModelSerializers::Model is a convenient superclass for making your models
2
+ # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
3
+ # that satisfies ActiveModel::Serializer::Lint::Tests.
4
4
  module ActiveModelSerializers
5
5
  class Model
6
- include ActiveModel::Model
7
6
  include ActiveModel::Serializers::JSON
7
+ include ActiveModel::Model
8
8
 
9
- attr_reader :attributes, :errors
9
+ # Declare names of attributes to be included in +sttributes+ hash.
10
+ # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
11
+ # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
12
+ #
13
+ # @overload attribute_names
14
+ # @return [Array<Symbol>]
15
+ class_attribute :attribute_names, instance_writer: false, instance_reader: false
16
+ # Initialize +attribute_names+ for all subclasses. The array is usually
17
+ # mutated in the +attributes+ method, but can be set directly, as well.
18
+ self.attribute_names = []
10
19
 
11
- def initialize(attributes = {})
12
- @attributes = attributes && attributes.symbolize_keys
13
- @errors = ActiveModel::Errors.new(self)
14
- super
20
+ # Easily declare instance attributes with setters and getters for each.
21
+ #
22
+ # All attributes to initialize an instance must have setters.
23
+ # However, the hash turned by +attributes+ instance method will ALWAYS
24
+ # be the value of the initial attributes, regardless of what accessors are defined.
25
+ # The only way to change the change the attributes after initialization is
26
+ # to mutate the +attributes+ directly.
27
+ # Accessor methods do NOT mutate the attributes. (This is a bug).
28
+ #
29
+ # @note For now, the Model only supports the notion of 'attributes'.
30
+ # In the tests, there is a special Model that also supports 'associations'. This is
31
+ # important so that we can add accessors for values that should not appear in the
32
+ # attributes hash when modeling associations. It is not yet clear if it
33
+ # makes sense for a PORO to have associations outside of the tests.
34
+ #
35
+ # @overload attributes(names)
36
+ # @param names [Array<String, Symbol>]
37
+ # @param name [String, Symbol]
38
+ def self.attributes(*names)
39
+ self.attribute_names |= names.map(&:to_sym)
40
+ # Silence redefinition of methods warnings
41
+ ActiveModelSerializers.silence_warnings do
42
+ attr_accessor(*names)
43
+ end
15
44
  end
16
45
 
17
- # Defaults to the downcased model name.
18
- def id
19
- attributes.fetch(:id) { self.class.name.downcase }
46
+ # Opt-in to breaking change
47
+ def self.derive_attributes_from_names_and_fix_accessors
48
+ unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
49
+ prepend(DeriveAttributesFromNamesAndFixAccessors)
50
+ end
20
51
  end
21
52
 
22
- # Defaults to the downcased model name and updated_at
23
- def cache_key
24
- attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" }
53
+ module DeriveAttributesFromNamesAndFixAccessors
54
+ def self.included(base)
55
+ # NOTE that +id+ will always be in +attributes+.
56
+ base.attributes :id
57
+ end
58
+
59
+ # Override the +attributes+ method so that the hash is derived from +attribute_names+.
60
+ #
61
+ # The the fields in +attribute_names+ determines the returned hash.
62
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
63
+ # the actual values in the model.
64
+ def attributes
65
+ self.class.attribute_names.each_with_object({}) do |attribute_name, result|
66
+ result[attribute_name] = public_send(attribute_name).freeze
67
+ end.with_indifferent_access.freeze
68
+ end
25
69
  end
26
70
 
27
- # Defaults to the time the serializer file was modified.
28
- def updated_at
29
- attributes.fetch(:updated_at) { File.mtime(__FILE__) }
71
+ # Support for validation and other ActiveModel::Errors
72
+ # @return [ActiveModel::Errors]
73
+ attr_reader :errors
74
+
75
+ # (see #updated_at)
76
+ attr_writer :updated_at
77
+
78
+ # The only way to change the attributes of an instance is to directly mutate the attributes.
79
+ # @example
80
+ #
81
+ # model.attributes[:foo] = :bar
82
+ # @return [Hash]
83
+ attr_reader :attributes
84
+
85
+ # @param attributes [Hash]
86
+ def initialize(attributes = {})
87
+ attributes ||= {} # protect against nil
88
+ @attributes = attributes.symbolize_keys.with_indifferent_access
89
+ @errors = ActiveModel::Errors.new(self)
90
+ super
30
91
  end
31
92
 
32
- def read_attribute_for_serialization(key)
33
- if key == :id || key == 'id'
34
- attributes.fetch(key) { id }
35
- else
36
- attributes[key]
93
+ # Defaults to the downcased model name.
94
+ # This probably isn't a good default, since it's not a unique instance identifier,
95
+ # but that's what is currently implemented \_('-')_/.
96
+ #
97
+ # @note Though +id+ is defined, it will only show up
98
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
99
+ # such as <tt>attributes[:id] = 5</tt>.
100
+ # @return [String, Numeric, Symbol]
101
+ def id
102
+ attributes.fetch(:id) do
103
+ defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
37
104
  end
38
105
  end
39
106
 
40
- # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
- # :nocov:
42
- def self.human_attribute_name(attr, _options = {})
43
- attr
107
+ # When not set, defaults to the time the file was modified.
108
+ #
109
+ # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
110
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
111
+ # such as <tt>attributes[:updated_at] = Time.current</tt>.
112
+ # @return [String, Numeric, Time]
113
+ def updated_at
114
+ attributes.fetch(:updated_at) do
115
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
116
+ end
44
117
  end
45
118
 
46
- def self.lookup_ancestors
47
- [self]
119
+ # To customize model behavior, this method must be redefined. However,
120
+ # there are other ways of setting the +cache_key+ a serializer uses.
121
+ # @return [String]
122
+ def cache_key
123
+ ActiveSupport::Cache.expand_cache_key([
124
+ self.class.model_name.name.downcase,
125
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
126
+ ].compact)
48
127
  end
49
- # :nocov:
50
128
  end
51
129
  end
@@ -60,11 +60,11 @@ module ActiveModelSerializers
60
60
  attr_reader :document_store
61
61
 
62
62
  def controller_path
63
- request.filtered_parameters[:controller]
63
+ request.filtered_parameters.with_indifferent_access[:controller]
64
64
  end
65
65
 
66
66
  def action
67
- request.filtered_parameters[:action]
67
+ request.filtered_parameters.with_indifferent_access[:action]
68
68
  end
69
69
 
70
70
  def schema_directory
@@ -4,7 +4,7 @@ require 'rails/generators/rails/resource/resource_generator'
4
4
  module Rails
5
5
  module Generators
6
6
  class ResourceGenerator
7
- hook_for :serializer, default: true, boolean: true
7
+ hook_for :serializer, default: true, type: :boolean
8
8
  end
9
9
  end
10
10
  end
@@ -3,6 +3,15 @@ require 'test_helper'
3
3
  module ActionController
4
4
  module Serialization
5
5
  class AdapterSelectorTest < ActionController::TestCase
6
+ class Profile < Model
7
+ attributes :id, :name, :description
8
+ associations :comments
9
+ end
10
+ class ProfileSerializer < ActiveModel::Serializer
11
+ type 'profiles'
12
+ attributes :name, :description
13
+ end
14
+
6
15
  class AdapterSelectorTestController < ActionController::Base
7
16
  def render_using_default_adapter
8
17
  @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
@@ -15,7 +24,7 @@ module ActionController
15
24
  end
16
25
 
17
26
  def render_skipping_adapter
18
- @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
27
+ @profile = Profile.new(id: 'render_skipping_adapter_id', name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
19
28
  render json: @profile, adapter: false
20
29
  end
21
30
  end
@@ -46,7 +55,7 @@ module ActionController
46
55
 
47
56
  def test_render_skipping_adapter
48
57
  get :render_skipping_adapter
49
- assert_equal '{"name":"Name 1","description":"Description 1","comments":"Comments 1"}', response.body
58
+ assert_equal '{"id":"render_skipping_adapter_id","name":"Name 1","description":"Description 1"}', response.body
50
59
  end
51
60
  end
52
61
  end
@@ -5,7 +5,16 @@ module ActionController
5
5
  class JsonApi
6
6
  class FieldsTest < ActionController::TestCase
7
7
  class FieldsTestController < ActionController::Base
8
- class PostSerializer < ActiveModel::Serializer
8
+ class AuthorWithName < Author
9
+ attributes :first_name, :last_name
10
+ end
11
+ class AuthorWithNameSerializer < AuthorSerializer
12
+ type 'authors'
13
+ end
14
+ class PostWithPublishAt < Post
15
+ attributes :publish_at
16
+ end
17
+ class PostWithPublishAtSerializer < ActiveModel::Serializer
9
18
  type 'posts'
10
19
  attributes :title, :body, :publish_at
11
20
  belongs_to :author
@@ -14,19 +23,19 @@ module ActionController
14
23
 
15
24
  def setup_post
16
25
  ActionController::Base.cache_store.clear
17
- @author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones')
26
+ @author = AuthorWithName.new(id: 1, first_name: 'Bob', last_name: 'Jones')
18
27
  @comment1 = Comment.new(id: 7, body: 'cool', author: @author)
19
28
  @comment2 = Comment.new(id: 12, body: 'awesome', author: @author)
20
- @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1',
21
- author: @author, comments: [@comment1, @comment2],
22
- publish_at: '2020-03-16T03:55:25.291Z')
29
+ @post = PostWithPublishAt.new(id: 1337, title: 'Title 1', body: 'Body 1',
30
+ author: @author, comments: [@comment1, @comment2],
31
+ publish_at: '2020-03-16T03:55:25.291Z')
23
32
  @comment1.post = @post
24
33
  @comment2.post = @post
25
34
  end
26
35
 
27
36
  def render_fields_works_on_relationships
28
37
  setup_post
29
- render json: @post, serializer: PostSerializer, adapter: :json_api, fields: { posts: [:author] }
38
+ render json: @post, serializer: PostWithPublishAtSerializer, adapter: :json_api, fields: { posts: [:author] }
30
39
  end
31
40
  end
32
41
 
@@ -5,9 +5,17 @@ module ActionController
5
5
  class JsonApi
6
6
  class KeyTransformTest < ActionController::TestCase
7
7
  class KeyTransformTestController < ActionController::Base
8
- class Post < ::Model; end
9
- class Author < ::Model; end
10
- class TopComment < ::Model; end
8
+ class Post < ::Model
9
+ attributes :title, :body, :publish_at
10
+ associations :author, :top_comments
11
+ end
12
+ class Author < ::Model
13
+ attributes :first_name, :last_name
14
+ end
15
+ class TopComment < ::Model
16
+ attributes :body
17
+ associations :author, :post
18
+ end
11
19
  class PostSerializer < ActiveModel::Serializer
12
20
  type 'posts'
13
21
  attributes :title, :body, :publish_at
@@ -3,10 +3,16 @@ require 'test_helper'
3
3
  module ActionController
4
4
  module Serialization
5
5
  class NamespaceLookupTest < ActionController::TestCase
6
- class Book < ::Model; end
7
- class Page < ::Model; end
8
- class Chapter < ::Model; end
9
- class Writer < ::Model; end
6
+ class Book < ::Model
7
+ attributes :id, :title, :body
8
+ associations :writer, :chapters
9
+ end
10
+ class Chapter < ::Model
11
+ attributes :title
12
+ end
13
+ class Writer < ::Model
14
+ attributes :name
15
+ end
10
16
 
11
17
  module Api
12
18
  module V2
@@ -80,7 +86,7 @@ module ActionController
80
86
  book = Book.new(title: 'New Post', body: 'Body')
81
87
 
82
88
  # because this is a string, ruby can't auto-lookup the constant, so otherwise
83
- # the looku things we mean ::Api::V2
89
+ # the lookup thinks we mean ::Api::V2
84
90
  render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2'
85
91
  end
86
92
 
@@ -88,12 +94,12 @@ module ActionController
88
94
  book = Book.new(title: 'New Post', body: 'Body')
89
95
 
90
96
  # because this is a string, ruby can't auto-lookup the constant, so otherwise
91
- # the looku things we mean ::Api::V2
97
+ # the lookup thinks we mean ::Api::V2
92
98
  render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2'
93
99
  end
94
100
 
95
101
  def invalid_namespace
96
- book = Book.new(title: 'New Post', body: 'Body')
102
+ book = Book.new(id: 'invalid_namespace_book_id', title: 'New Post', body: 'Body')
97
103
 
98
104
  render json: book, namespace: :api_v2
99
105
  end
@@ -205,7 +211,7 @@ module ActionController
205
211
 
206
212
  assert_serializer ActiveModel::Serializer::Null
207
213
 
208
- expected = { 'title' => 'New Post', 'body' => 'Body' }
214
+ expected = { 'id' => 'invalid_namespace_book_id', 'title' => 'New Post', 'body' => 'Body' }
209
215
  actual = JSON.parse(@response.body)
210
216
 
211
217
  assert_equal expected, actual
@@ -2,16 +2,16 @@ require 'test_helper'
2
2
 
3
3
  module SerializationScopeTesting
4
4
  class User < ActiveModelSerializers::Model
5
- attr_accessor :id, :name, :admin
5
+ attributes :id, :name, :admin
6
6
  def admin?
7
7
  admin
8
8
  end
9
9
  end
10
10
  class Comment < ActiveModelSerializers::Model
11
- attr_accessor :id, :body
11
+ attributes :id, :body
12
12
  end
13
13
  class Post < ActiveModelSerializers::Model
14
- attr_accessor :id, :title, :body, :comments
14
+ attributes :id, :title, :body, :comments
15
15
  end
16
16
  class PostSerializer < ActiveModel::Serializer
17
17
  attributes :id, :title, :body, :comments
@@ -33,7 +33,8 @@ module SerializationScopeTesting
33
33
  end
34
34
  end
35
35
  class PostTestController < ActionController::Base
36
- attr_accessor :current_user
36
+ attr_writer :current_user
37
+
37
38
  def render_post_by_non_admin
38
39
  self.current_user = User.new(id: 3, name: 'Pete', admin: false)
39
40
  render json: new_post, serializer: serializer, adapter: :json
@@ -44,6 +45,10 @@ module SerializationScopeTesting
44
45
  render json: new_post, serializer: serializer, adapter: :json
45
46
  end
46
47
 
48
+ def current_user
49
+ defined?(@current_user) ? @current_user : :current_user_not_set
50
+ end
51
+
47
52
  private
48
53
 
49
54
  def new_post
@@ -75,7 +80,8 @@ module SerializationScopeTesting
75
80
  end
76
81
 
77
82
  def test_default_serialization_scope_object
78
- assert_equal @controller.current_user, @controller.serialization_scope
83
+ assert_equal :current_user_not_set, @controller.current_user
84
+ assert_equal :current_user_not_set, @controller.serialization_scope
79
85
  end
80
86
 
81
87
  def test_default_scope_non_admin
@@ -125,7 +131,7 @@ module SerializationScopeTesting
125
131
  end
126
132
 
127
133
  def test_defined_serialization_scope_object
128
- assert_equal @controller.view_context.class, @controller.serialization_scope.class
134
+ assert_equal @controller.view_context.controller, @controller.serialization_scope.controller
129
135
  end
130
136
 
131
137
  def test_serialization_scope_non_admin
@@ -135,7 +135,7 @@ module ActionController
135
135
  like = Like.new(id: 1, likeable: comment, time: 3.days.ago)
136
136
 
137
137
  generate_cached_serializer(like)
138
- like.likable = comment2
138
+ like.likeable = comment2
139
139
  like.time = Time.zone.now.to_s
140
140
 
141
141
  render json: like
@@ -4,13 +4,13 @@ module ActiveModelSerializers
4
4
  class ModelTest < ActiveSupport::TestCase
5
5
  include ActiveModel::Serializer::Lint::Tests
6
6
 
7
- def setup
7
+ setup do
8
8
  @resource = ActiveModelSerializers::Model.new
9
9
  end
10
10
 
11
11
  def test_initialization_with_string_keys
12
12
  klass = Class.new(ActiveModelSerializers::Model) do
13
- attr_accessor :key
13
+ attributes :key
14
14
  end
15
15
  value = 'value'
16
16
 
@@ -18,5 +18,125 @@ module ActiveModelSerializers
18
18
 
19
19
  assert_equal model_instance.read_attribute_for_serialization(:key), value
20
20
  end
21
+
22
+ def test_attributes_can_be_read_for_serialization
23
+ klass = Class.new(ActiveModelSerializers::Model) do
24
+ attributes :one, :two, :three
25
+ end
26
+ original_attributes = { one: 1, two: 2, three: 3 }
27
+ original_instance = klass.new(original_attributes)
28
+
29
+ # Initial value
30
+ instance = original_instance
31
+ expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access
32
+ assert_equal expected_attributes, instance.attributes
33
+ assert_equal 1, instance.one
34
+ assert_equal 1, instance.read_attribute_for_serialization(:one)
35
+
36
+ # FIXME: Change via accessor has no effect on attributes.
37
+ instance = original_instance.dup
38
+ instance.one = :not_one
39
+ assert_equal expected_attributes, instance.attributes
40
+ assert_equal :not_one, instance.one
41
+ assert_equal :not_one, instance.read_attribute_for_serialization(:one)
42
+
43
+ # FIXME: Change via mutating attributes
44
+ instance = original_instance.dup
45
+ instance.attributes[:one] = :not_one
46
+ expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access
47
+ assert_equal expected_attributes, instance.attributes
48
+ assert_equal 1, instance.one
49
+ assert_equal 1, instance.read_attribute_for_serialization(:one)
50
+ end
51
+
52
+ def test_attributes_can_be_read_for_serialization_with_attributes_accessors_fix
53
+ klass = Class.new(ActiveModelSerializers::Model) do
54
+ derive_attributes_from_names_and_fix_accessors
55
+ attributes :one, :two, :three
56
+ end
57
+ original_attributes = { one: 1, two: 2, three: 3 }
58
+ original_instance = klass.new(original_attributes)
59
+
60
+ # Initial value
61
+ instance = original_instance
62
+ expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access
63
+ assert_equal expected_attributes, instance.attributes
64
+ assert_equal 1, instance.one
65
+ assert_equal 1, instance.read_attribute_for_serialization(:one)
66
+
67
+ expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access
68
+ # Change via accessor
69
+ instance = original_instance.dup
70
+ instance.one = :not_one
71
+ assert_equal expected_attributes, instance.attributes
72
+ assert_equal :not_one, instance.one
73
+ assert_equal :not_one, instance.read_attribute_for_serialization(:one)
74
+
75
+ # Attributes frozen
76
+ assert instance.attributes.frozen?
77
+ end
78
+
79
+ def test_id_attribute_can_be_read_for_serialization
80
+ klass = Class.new(ActiveModelSerializers::Model) do
81
+ attributes :id, :one, :two, :three
82
+ end
83
+ self.class.const_set(:SomeTestModel, klass)
84
+ original_attributes = { id: :ego, one: 1, two: 2, three: 3 }
85
+ original_instance = klass.new(original_attributes)
86
+
87
+ # Initial value
88
+ instance = original_instance.dup
89
+ expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access
90
+ assert_equal expected_attributes, instance.attributes
91
+ assert_equal :ego, instance.id
92
+ assert_equal :ego, instance.read_attribute_for_serialization(:id)
93
+
94
+ # FIXME: Change via accessor has no effect on attributes.
95
+ instance = original_instance.dup
96
+ instance.id = :superego
97
+ assert_equal expected_attributes, instance.attributes
98
+ assert_equal :superego, instance.id
99
+ assert_equal :superego, instance.read_attribute_for_serialization(:id)
100
+
101
+ # FIXME: Change via mutating attributes
102
+ instance = original_instance.dup
103
+ instance.attributes[:id] = :superego
104
+ expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access
105
+ assert_equal expected_attributes, instance.attributes
106
+ assert_equal :ego, instance.id
107
+ assert_equal :ego, instance.read_attribute_for_serialization(:id)
108
+ ensure
109
+ self.class.send(:remove_const, :SomeTestModel)
110
+ end
111
+
112
+ def test_id_attribute_can_be_read_for_serialization_with_attributes_accessors_fix
113
+ klass = Class.new(ActiveModelSerializers::Model) do
114
+ derive_attributes_from_names_and_fix_accessors
115
+ attributes :id, :one, :two, :three
116
+ end
117
+ self.class.const_set(:SomeTestModel, klass)
118
+ original_attributes = { id: :ego, one: 1, two: 2, three: 3 }
119
+ original_instance = klass.new(original_attributes)
120
+
121
+ # Initial value
122
+ instance = original_instance.dup
123
+ expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access
124
+ assert_equal expected_attributes, instance.attributes
125
+ assert_equal :ego, instance.id
126
+ assert_equal :ego, instance.read_attribute_for_serialization(:id)
127
+
128
+ expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access
129
+ # Change via accessor
130
+ instance = original_instance.dup
131
+ instance.id = :superego
132
+ assert_equal expected_attributes, instance.attributes
133
+ assert_equal :superego, instance.id
134
+ assert_equal :superego, instance.read_attribute_for_serialization(:id)
135
+
136
+ # Attributes frozen
137
+ assert instance.attributes.frozen?
138
+ ensure
139
+ self.class.send(:remove_const, :SomeTestModel)
140
+ end
21
141
  end
22
142
  end