active_model_serializers 0.10.0 → 0.10.7

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 (171) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +6 -5
  3. data/.travis.yml +30 -21
  4. data/CHANGELOG.md +172 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +23 -4
  7. data/README.md +166 -28
  8. data/Rakefile +3 -32
  9. data/active_model_serializers.gemspec +22 -25
  10. data/appveyor.yml +10 -6
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +35 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +86 -1
  16. data/docs/general/deserialization.md +1 -1
  17. data/docs/general/fields.md +31 -0
  18. data/docs/general/getting_started.md +1 -1
  19. data/docs/general/logging.md +7 -0
  20. data/docs/general/rendering.md +63 -25
  21. data/docs/general/serializers.md +125 -14
  22. data/docs/howto/add_pagination_links.md +16 -17
  23. data/docs/howto/add_relationship_links.md +140 -0
  24. data/docs/howto/add_root_key.md +11 -0
  25. data/docs/howto/grape_integration.md +42 -0
  26. data/docs/howto/outside_controller_use.md +12 -4
  27. data/docs/howto/passing_arbitrary_options.md +2 -2
  28. data/docs/howto/serialize_poro.md +46 -5
  29. data/docs/howto/test.md +2 -0
  30. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  31. data/docs/integrations/ember-and-json-api.md +67 -32
  32. data/docs/jsonapi/schema.md +1 -1
  33. data/lib/action_controller/serialization.rb +13 -3
  34. data/lib/active_model/serializer/adapter/base.rb +2 -0
  35. data/lib/active_model/serializer/array_serializer.rb +8 -5
  36. data/lib/active_model/serializer/association.rb +62 -10
  37. data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
  38. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  39. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
  40. data/lib/active_model/serializer/error_serializer.rb +11 -7
  41. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  42. data/lib/active_model/serializer/has_many_reflection.rb +3 -3
  43. data/lib/active_model/serializer/has_one_reflection.rb +1 -4
  44. data/lib/active_model/serializer/lazy_association.rb +95 -0
  45. data/lib/active_model/serializer/lint.rb +134 -130
  46. data/lib/active_model/serializer/reflection.rb +127 -67
  47. data/lib/active_model/serializer/version.rb +1 -1
  48. data/lib/active_model/serializer.rb +297 -79
  49. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  50. data/lib/active_model_serializers/adapter/base.rb +39 -39
  51. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
  52. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  53. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +47 -21
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
  56. data/lib/active_model_serializers/adapter/json_api.rb +71 -57
  57. data/lib/active_model_serializers/adapter.rb +6 -0
  58. data/lib/active_model_serializers/deprecate.rb +1 -2
  59. data/lib/active_model_serializers/deserialization.rb +2 -0
  60. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  61. data/lib/active_model_serializers/model.rb +109 -28
  62. data/lib/active_model_serializers/railtie.rb +3 -1
  63. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  64. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  65. data/lib/active_model_serializers/serialization_context.rb +10 -3
  66. data/lib/active_model_serializers/test/schema.rb +2 -2
  67. data/lib/active_model_serializers.rb +16 -1
  68. data/lib/generators/rails/resource_override.rb +1 -1
  69. data/lib/generators/rails/serializer_generator.rb +4 -4
  70. data/lib/grape/active_model_serializers.rb +7 -5
  71. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  72. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  73. data/lib/tasks/rubocop.rake +53 -0
  74. data/test/action_controller/adapter_selector_test.rb +14 -5
  75. data/test/action_controller/explicit_serializer_test.rb +5 -4
  76. data/test/action_controller/json/include_test.rb +106 -27
  77. data/test/action_controller/json_api/deserialization_test.rb +1 -1
  78. data/test/action_controller/json_api/errors_test.rb +8 -9
  79. data/test/action_controller/json_api/fields_test.rb +66 -0
  80. data/test/action_controller/json_api/linked_test.rb +29 -24
  81. data/test/action_controller/json_api/pagination_test.rb +31 -23
  82. data/test/action_controller/json_api/transform_test.rb +11 -3
  83. data/test/action_controller/lookup_proc_test.rb +49 -0
  84. data/test/action_controller/namespace_lookup_test.rb +232 -0
  85. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  86. data/test/action_controller/serialization_test.rb +12 -9
  87. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  88. data/test/active_model_serializers/model_test.rb +137 -4
  89. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  90. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  91. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  92. data/test/active_model_serializers/test/schema_test.rb +3 -2
  93. data/test/adapter/attributes_test.rb +40 -0
  94. data/test/adapter/json/collection_test.rb +14 -0
  95. data/test/adapter/json/has_many_test.rb +10 -2
  96. data/test/adapter/json/transform_test.rb +15 -15
  97. data/test/adapter/json_api/collection_test.rb +4 -3
  98. data/test/adapter/json_api/errors_test.rb +17 -19
  99. data/test/adapter/json_api/fields_test.rb +12 -3
  100. data/test/adapter/json_api/has_many_test.rb +49 -20
  101. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  102. data/test/adapter/json_api/json_api_test.rb +5 -7
  103. data/test/adapter/json_api/linked_test.rb +33 -12
  104. data/test/adapter/json_api/links_test.rb +4 -2
  105. data/test/adapter/json_api/pagination_links_test.rb +53 -13
  106. data/test/adapter/json_api/parse_test.rb +1 -1
  107. data/test/adapter/json_api/relationship_test.rb +309 -73
  108. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  109. data/test/adapter/json_api/transform_test.rb +263 -253
  110. data/test/adapter/json_api/type_test.rb +168 -36
  111. data/test/adapter/json_test.rb +8 -7
  112. data/test/adapter/null_test.rb +1 -2
  113. data/test/adapter/polymorphic_test.rb +52 -5
  114. data/test/adapter_test.rb +1 -1
  115. data/test/benchmark/app.rb +1 -1
  116. data/test/benchmark/benchmarking_support.rb +1 -1
  117. data/test/benchmark/bm_active_record.rb +81 -0
  118. data/test/benchmark/bm_adapter.rb +38 -0
  119. data/test/benchmark/bm_caching.rb +16 -16
  120. data/test/benchmark/bm_lookup_chain.rb +83 -0
  121. data/test/benchmark/bm_transform.rb +21 -10
  122. data/test/benchmark/controllers.rb +16 -17
  123. data/test/benchmark/fixtures.rb +72 -72
  124. data/test/cache_test.rb +235 -69
  125. data/test/collection_serializer_test.rb +31 -14
  126. data/test/fixtures/active_record.rb +45 -10
  127. data/test/fixtures/poro.rb +124 -181
  128. data/test/generators/serializer_generator_test.rb +23 -5
  129. data/test/grape_test.rb +170 -56
  130. data/test/lint_test.rb +1 -1
  131. data/test/logger_test.rb +13 -11
  132. data/test/serializable_resource_test.rb +18 -22
  133. data/test/serializers/association_macros_test.rb +3 -2
  134. data/test/serializers/associations_test.rb +222 -49
  135. data/test/serializers/attribute_test.rb +5 -3
  136. data/test/serializers/attributes_test.rb +1 -1
  137. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  138. data/test/serializers/fieldset_test.rb +1 -1
  139. data/test/serializers/meta_test.rb +12 -6
  140. data/test/serializers/options_test.rb +17 -6
  141. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  142. data/test/serializers/reflection_test.rb +427 -0
  143. data/test/serializers/root_test.rb +1 -1
  144. data/test/serializers/serialization_test.rb +2 -2
  145. data/test/serializers/serializer_for_test.rb +12 -10
  146. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  147. data/test/support/isolated_unit.rb +9 -4
  148. data/test/support/rails5_shims.rb +8 -2
  149. data/test/support/rails_app.rb +2 -9
  150. data/test/support/serialization_testing.rb +31 -5
  151. data/test/test_helper.rb +13 -0
  152. metadata +130 -71
  153. data/.rubocop_todo.yml +0 -167
  154. data/docs/ARCHITECTURE.md +0 -126
  155. data/lib/active_model/serializer/associations.rb +0 -100
  156. data/lib/active_model/serializer/attributes.rb +0 -82
  157. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  158. data/lib/active_model/serializer/configuration.rb +0 -35
  159. data/lib/active_model/serializer/include_tree.rb +0 -111
  160. data/lib/active_model/serializer/links.rb +0 -35
  161. data/lib/active_model/serializer/meta.rb +0 -29
  162. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  163. data/lib/active_model/serializer/type.rb +0 -25
  164. data/lib/active_model_serializers/key_transform.rb +0 -70
  165. data/test/active_model_serializers/key_transform_test.rb +0 -263
  166. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  167. data/test/adapter/json_api/relationships_test.rb +0 -199
  168. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  169. data/test/include_tree/from_include_args_test.rb +0 -26
  170. data/test/include_tree/from_string_test.rb +0 -94
  171. data/test/include_tree/include_args_to_hash_test.rb +0 -64
data/test/cache_test.rb CHANGED
@@ -4,22 +4,111 @@ require 'tempfile'
4
4
 
5
5
  module ActiveModelSerializers
6
6
  class CacheTest < ActiveSupport::TestCase
7
- UncachedAuthor = Class.new(Author) do
8
- # To confirm cache_key is set using updated_at and cache_key option passed to cache
7
+ class Article < ::Model
8
+ attributes :title
9
+ # To confirm error is raised when cache_key is not set and cache_key option not passed to cache
9
10
  undef_method :cache_key
10
11
  end
12
+ class ArticleSerializer < ActiveModel::Serializer
13
+ cache only: [:place], skip_digest: true
14
+ attributes :title
15
+ end
11
16
 
12
- Article = Class.new(::Model) do
13
- # To confirm error is raised when cache_key is not set and cache_key option not passed to cache
17
+ class Author < ::Model
18
+ attributes :id, :name
19
+ associations :posts, :bio, :roles
20
+ end
21
+ # Instead of a primitive cache key (i.e. a string), this class
22
+ # returns a list of objects that require to be expanded themselves.
23
+ class AuthorWithExpandableCacheElements < Author
24
+ # For the test purposes it's important that #to_s for HasCacheKey differs
25
+ # between instances, hence not a Struct.
26
+ class HasCacheKey
27
+ attr_reader :cache_key
28
+ def initialize(cache_key)
29
+ @cache_key = cache_key
30
+ end
31
+
32
+ def to_s
33
+ "HasCacheKey##{object_id}"
34
+ end
35
+ end
36
+
37
+ def cache_key
38
+ [
39
+ HasCacheKey.new(name),
40
+ HasCacheKey.new(id)
41
+ ]
42
+ end
43
+ end
44
+ class UncachedAuthor < Author
45
+ # To confirm cache_key is set using updated_at and cache_key option passed to cache
14
46
  undef_method :cache_key
15
47
  end
48
+ class AuthorSerializer < ActiveModel::Serializer
49
+ cache key: 'writer', skip_digest: true
50
+ attributes :id, :name
16
51
 
17
- ArticleSerializer = Class.new(ActiveModel::Serializer) do
18
- cache only: [:place], skip_digest: true
19
- attributes :title
52
+ has_many :posts
53
+ has_many :roles
54
+ has_one :bio
55
+ end
56
+
57
+ class Blog < ::Model
58
+ attributes :name
59
+ associations :writer
60
+ end
61
+ class BlogSerializer < ActiveModel::Serializer
62
+ cache key: 'blog'
63
+ attributes :id, :name
64
+
65
+ belongs_to :writer
66
+ end
67
+
68
+ class Comment < ::Model
69
+ attributes :id, :body
70
+ associations :post, :author
71
+
72
+ # Uses a custom non-time-based cache key
73
+ def cache_key
74
+ "comment/#{id}"
75
+ end
76
+ end
77
+ class CommentSerializer < ActiveModel::Serializer
78
+ cache expires_in: 1.day, skip_digest: true
79
+ attributes :id, :body
80
+ belongs_to :post
81
+ belongs_to :author
20
82
  end
21
83
 
22
- InheritedRoleSerializer = Class.new(RoleSerializer) do
84
+ class Post < ::Model
85
+ attributes :id, :title, :body
86
+ associations :author, :comments, :blog
87
+ end
88
+ class PostSerializer < ActiveModel::Serializer
89
+ cache key: 'post', expires_in: 0.1, skip_digest: true
90
+ attributes :id, :title, :body
91
+
92
+ has_many :comments
93
+ belongs_to :blog
94
+ belongs_to :author
95
+ end
96
+
97
+ class Role < ::Model
98
+ attributes :name, :description, :special_attribute
99
+ associations :author
100
+ end
101
+ class RoleSerializer < ActiveModel::Serializer
102
+ cache only: [:name, :slug], skip_digest: true
103
+ attributes :id, :name, :description
104
+ attribute :friendly_id, key: :slug
105
+ belongs_to :author
106
+
107
+ def friendly_id
108
+ "#{object.name}-#{object.id}"
109
+ end
110
+ end
111
+ class InheritedRoleSerializer < RoleSerializer
23
112
  cache key: 'inherited_role', only: [:name, :special_attribute]
24
113
  attribute :special_attribute
25
114
  end
@@ -27,10 +116,10 @@ module ActiveModelSerializers
27
116
  setup do
28
117
  cache_store.clear
29
118
  @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
30
- @post = Post.new(title: 'New Post', body: 'Body')
119
+ @post = Post.new(id: 'post', title: 'New Post', body: 'Body')
31
120
  @bio = Bio.new(id: 1, content: 'AMS Contributor')
32
- @author = Author.new(name: 'Joao M. D. Moura')
33
- @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: [])
121
+ @author = Author.new(id: 'author', name: 'Joao M. D. Moura')
122
+ @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author)
34
123
  @role = Role.new(name: 'Great Author')
35
124
  @location = Location.new(lat: '-23.550520', lng: '-46.633309')
36
125
  @place = Place.new(name: 'Amazing Place')
@@ -93,7 +182,7 @@ module ActiveModelSerializers
93
182
  def test_cache_key_definition
94
183
  assert_equal('post', @post_serializer.class._cache_key)
95
184
  assert_equal('writer', @author_serializer.class._cache_key)
96
- assert_equal(nil, @comment_serializer.class._cache_key)
185
+ assert_nil(@comment_serializer.class._cache_key)
97
186
  end
98
187
 
99
188
  def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object
@@ -101,14 +190,28 @@ module ActiveModelSerializers
101
190
  uncached_author_serializer = AuthorSerializer.new(uncached_author)
102
191
 
103
192
  render_object_with_cache(uncached_author)
104
- key = "#{uncached_author_serializer.class._cache_key}/#{uncached_author_serializer.object.id}-#{uncached_author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}"
105
- key = "#{key}/#{adapter.cached_name}"
193
+ key = "#{uncached_author_serializer.class._cache_key}/#{uncached_author_serializer.object.id}-#{uncached_author_serializer.object.updated_at.strftime('%Y%m%d%H%M%S%9N')}"
194
+ key = "#{key}/#{adapter.cache_key}"
106
195
  assert_equal(uncached_author_serializer.attributes.to_json, cache_store.fetch(key).to_json)
107
196
  end
108
197
 
198
+ def test_cache_key_expansion
199
+ author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello')
200
+ same_author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello')
201
+ diff_author = AuthorWithExpandableCacheElements.new(id: 11, name: 'hello')
202
+
203
+ author_serializer = AuthorSerializer.new(author)
204
+ same_author_serializer = AuthorSerializer.new(same_author)
205
+ diff_author_serializer = AuthorSerializer.new(diff_author)
206
+ adapter = AuthorSerializer.serialization_adapter_instance
207
+
208
+ assert_equal(author_serializer.cache_key(adapter), same_author_serializer.cache_key(adapter))
209
+ refute_equal(author_serializer.cache_key(adapter), diff_author_serializer.cache_key(adapter))
210
+ end
211
+
109
212
  def test_default_cache_key_fallback
110
213
  render_object_with_cache(@comment)
111
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
214
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
112
215
  assert_equal(@comment_serializer.attributes.to_json, cache_store.fetch(key).to_json)
113
216
  end
114
217
 
@@ -117,31 +220,31 @@ module ActiveModelSerializers
117
220
  e = assert_raises ActiveModel::Serializer::UndefinedCacheKey do
118
221
  render_object_with_cache(article)
119
222
  end
120
- assert_match(/ActiveModelSerializers::CacheTest::Article must define #cache_key, or the 'key:' option must be passed into 'CachedActiveModelSerializers_CacheTest_ArticleSerializer.cache'/, e.message)
223
+ assert_match(/ActiveModelSerializers::CacheTest::Article must define #cache_key, or the 'key:' option must be passed into 'ActiveModelSerializers::CacheTest::ArticleSerializer.cache'/, e.message)
121
224
  end
122
225
 
123
226
  def test_cache_options_definition
124
227
  assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options)
125
- assert_equal(nil, @blog_serializer.class._cache_options)
228
+ assert_nil(@blog_serializer.class._cache_options)
126
229
  assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options)
127
230
  end
128
231
 
129
232
  def test_fragment_cache_definition
130
- assert_equal([:name], @role_serializer.class._cache_only)
233
+ assert_equal([:name, :slug], @role_serializer.class._cache_only)
131
234
  assert_equal([:content], @bio_serializer.class._cache_except)
132
235
  end
133
236
 
134
237
  def test_associations_separately_cache
135
238
  cache_store.clear
136
- assert_equal(nil, cache_store.fetch(@post.cache_key))
137
- assert_equal(nil, cache_store.fetch(@comment.cache_key))
239
+ assert_nil(cache_store.fetch(@post.cache_key))
240
+ assert_nil(cache_store.fetch(@comment.cache_key))
138
241
 
139
242
  Timecop.freeze(Time.current) do
140
243
  render_object_with_cache(@post)
141
244
 
142
- key = "#{@post.cache_key}/#{adapter.cached_name}"
245
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
143
246
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
144
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
247
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
145
248
  assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
146
249
  end
147
250
  end
@@ -152,9 +255,9 @@ module ActiveModelSerializers
152
255
  render_object_with_cache(@post)
153
256
 
154
257
  # Check if it cached the objects separately
155
- key = "#{@post.cache_key}/#{adapter.cached_name}"
258
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
156
259
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
157
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
260
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
158
261
  assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
159
262
 
160
263
  # Simulating update on comments relationship with Post
@@ -166,9 +269,9 @@ module ActiveModelSerializers
166
269
  render_object_with_cache(@post)
167
270
 
168
271
  # Check if the the new comment was cached
169
- key = "#{new_comment.cache_key}/#{adapter.cached_name}"
272
+ key = "#{new_comment.cache_key}/#{adapter.cache_key}"
170
273
  assert_equal(new_comment_serializer.attributes, cache_store.fetch(key))
171
- key = "#{@post.cache_key}/#{adapter.cached_name}"
274
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
172
275
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
173
276
  end
174
277
  end
@@ -178,14 +281,14 @@ module ActiveModelSerializers
178
281
  id: @location.id,
179
282
  lat: @location.lat,
180
283
  lng: @location.lng,
181
- place: 'Nowhere'
284
+ address: 'Nowhere'
182
285
  }
183
286
 
184
287
  hash = render_object_with_cache(@location)
185
288
 
186
289
  assert_equal(hash, expected_result)
187
- key = "#{@location.cache_key}/#{adapter.cached_name}"
188
- assert_equal({ place: 'Nowhere' }, cache_store.fetch(key))
290
+ key = "#{@location.cache_key}/#{adapter.cache_key}"
291
+ assert_equal({ address: 'Nowhere' }, cache_store.fetch(key))
189
292
  end
190
293
 
191
294
  def test_fragment_cache_with_inheritance
@@ -204,9 +307,9 @@ module ActiveModelSerializers
204
307
 
205
308
  # Based on original failing test by @kevintyll
206
309
  # rubocop:disable Metrics/AbcSize
207
- def test_a_serializer_rendered_by_two_adapter_returns_differently_cached_attributes
310
+ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attributes
208
311
  Object.const_set(:Alert, Class.new(ActiveModelSerializers::Model) do
209
- attr_accessor :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
312
+ attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
210
313
  end)
211
314
  Object.const_set(:UncachedAlertSerializer, Class.new(ActiveModel::Serializer) do
212
315
  attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
@@ -225,7 +328,7 @@ module ActiveModelSerializers
225
328
  created_at: Time.new(2016, 3, 31, 21, 37, 35, 0)
226
329
  )
227
330
 
228
- expected_cached_attributes = {
331
+ expected_fetch_attributes = {
229
332
  id: 1,
230
333
  status: 'fail',
231
334
  resource: 'resource-1',
@@ -233,7 +336,7 @@ module ActiveModelSerializers
233
336
  ended_at: nil,
234
337
  updated_at: alert.updated_at,
235
338
  created_at: alert.created_at
236
- }
339
+ }.with_indifferent_access
237
340
  expected_cached_jsonapi_attributes = {
238
341
  id: '1',
239
342
  type: 'alerts',
@@ -245,15 +348,15 @@ module ActiveModelSerializers
245
348
  updated_at: alert.updated_at,
246
349
  created_at: alert.created_at
247
350
  }
248
- }
351
+ }.with_indifferent_access
249
352
 
250
353
  # Assert attributes are serialized correctly
251
354
  serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :attributes)
252
- attributes_serialization = serializable_alert.as_json
253
- assert_equal expected_cached_attributes, alert.attributes
355
+ attributes_serialization = serializable_alert.as_json.with_indifferent_access
356
+ assert_equal expected_fetch_attributes, alert.attributes
254
357
  assert_equal alert.attributes, attributes_serialization
255
358
  attributes_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
256
- assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key)
359
+ assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key).with_indifferent_access
257
360
 
258
361
  serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :json_api)
259
362
  jsonapi_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
@@ -265,7 +368,7 @@ module ActiveModelSerializers
265
368
  serializable_alert = serializable(alert, serializer: UncachedAlertSerializer, adapter: :json_api)
266
369
  assert_equal serializable_alert.as_json, jsonapi_serialization
267
370
 
268
- cached_serialization = cache_store.fetch(jsonapi_cache_key)
371
+ cached_serialization = cache_store.fetch(jsonapi_cache_key).with_indifferent_access
269
372
  assert_equal expected_cached_jsonapi_attributes, cached_serialization
270
373
  ensure
271
374
  Object.send(:remove_const, :Alert)
@@ -276,43 +379,83 @@ module ActiveModelSerializers
276
379
 
277
380
  def test_uses_file_digest_in_cache_key
278
381
  render_object_with_cache(@blog)
279
- key = "#{@blog.cache_key}/#{adapter.cached_name}/#{::Model::FILE_DIGEST}"
382
+ file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read)
383
+ key = "#{@blog.cache_key}/#{adapter.cache_key}/#{file_digest}"
280
384
  assert_equal(@blog_serializer.attributes, cache_store.fetch(key))
281
385
  end
282
386
 
283
387
  def test_cache_digest_definition
284
- assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
388
+ file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read)
389
+ assert_equal(file_digest, @post_serializer.class._cache_digest)
285
390
  end
286
391
 
287
392
  def test_object_cache_keys
288
393
  serializable = ActiveModelSerializers::SerializableResource.new([@comment, @comment])
289
- include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*')
394
+ include_directive = JSONAPI::IncludeDirective.new('*', allow_wildcard: true)
290
395
 
291
- actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_tree)
396
+ actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_directive)
292
397
 
293
398
  assert_equal 3, actual.size
294
- assert actual.any? { |key| key == "comment/1/#{serializable.adapter.cached_name}" }
295
- assert actual.any? { |key| key =~ %r{post/post-\d+} }
296
- assert actual.any? { |key| key =~ %r{author/author-\d+} }
399
+ expected_key = "comment/1/#{serializable.adapter.cache_key}"
400
+ assert actual.any? { |key| key == expected_key }, "actual '#{actual}' should include #{expected_key}"
401
+ expected_key = %r{post/post-\d+}
402
+ assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'"
403
+ expected_key = %r{author/author-\d+}
404
+ assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'"
297
405
  end
298
406
 
299
- def test_cached_attributes
300
- serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
407
+ # rubocop:disable Metrics/AbcSize
408
+ def test_fetch_attributes_from_cache
409
+ serializers = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
301
410
 
302
411
  Timecop.freeze(Time.current) do
303
412
  render_object_with_cache(@comment)
304
413
 
305
- attributes = Adapter::Attributes.new(serializer)
306
- attributes.send(:cache_attributes)
307
- cached_attributes = attributes.instance_variable_get(:@cached_attributes)
414
+ options = {}
415
+ adapter_options = {}
416
+ adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
417
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
418
+ cached_attributes = adapter_options.fetch(:cached_attributes).with_indifferent_access
419
+
420
+ include_directive = ActiveModelSerializers.default_include_directive
421
+ manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive).with_indifferent_access
422
+ assert_equal manual_cached_attributes, cached_attributes
308
423
 
309
- assert_equal cached_attributes["#{@comment.cache_key}/#{attributes.cached_name}"], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes
310
- assert_equal cached_attributes["#{@comment.post.cache_key}/#{attributes.cached_name}"], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
424
+ assert_equal cached_attributes["#{@comment.cache_key}/#{adapter_instance.cache_key}"], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes
425
+ assert_equal cached_attributes["#{@comment.post.cache_key}/#{adapter_instance.cache_key}"], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
311
426
 
312
427
  writer = @comment.post.blog.writer
313
428
  writer_cache_key = writer.cache_key
429
+ assert_equal cached_attributes["#{writer_cache_key}/#{adapter_instance.cache_key}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
430
+ end
431
+ end
432
+ # rubocop:enable Metrics/AbcSize
433
+
434
+ def test_cache_read_multi_with_fragment_cache_enabled
435
+ post_serializer = Class.new(ActiveModel::Serializer) do
436
+ cache except: [:body]
437
+ end
314
438
 
315
- assert_equal cached_attributes["#{writer_cache_key}/#{attributes.cached_name}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
439
+ serializers = ActiveModel::Serializer::CollectionSerializer.new([@post, @post], serializer: post_serializer)
440
+
441
+ Timecop.freeze(Time.current) do
442
+ # Warming up.
443
+ options = {}
444
+ adapter_options = {}
445
+ adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
446
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
447
+
448
+ # Should find something with read_multi now
449
+ adapter_options = {}
450
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
451
+ cached_attributes = adapter_options.fetch(:cached_attributes)
452
+
453
+ include_directive = ActiveModelSerializers.default_include_directive
454
+ manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive)
455
+
456
+ refute_equal 0, cached_attributes.size
457
+ refute_equal 0, manual_cached_attributes.size
458
+ assert_equal manual_cached_attributes, cached_attributes
316
459
  end
317
460
  end
318
461
 
@@ -429,25 +572,53 @@ module ActiveModelSerializers
429
572
  end
430
573
 
431
574
  def test_fragment_fetch_with_virtual_attributes
432
- @author = Author.new(name: 'Joao M. D. Moura')
433
- @role = Role.new(name: 'Great Author', description: nil)
434
- @role.author = [@author]
435
- @role_serializer = RoleSerializer.new(@role)
436
- @role_hash = @role_serializer.fetch_fragment_cache(ActiveModelSerializers::Adapter.configured_adapter.new(@role_serializer))
575
+ author = Author.new(name: 'Joao M. D. Moura')
576
+ role = Role.new(name: 'Great Author', description: nil)
577
+ role.author = [author]
578
+ role_serializer = RoleSerializer.new(role)
579
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(role_serializer)
580
+ expected_result = {
581
+ id: role.id,
582
+ description: role.description,
583
+ slug: "#{role.name}-#{role.id}",
584
+ name: role.name
585
+ }
586
+ cache_store.clear
587
+
588
+ role_hash = role_serializer.fetch_attributes_fragment(adapter_instance)
589
+ assert_equal(role_hash, expected_result)
437
590
 
591
+ role.id = 'this has been updated'
592
+ role.name = 'this was cached'
593
+
594
+ role_hash = role_serializer.fetch_attributes_fragment(adapter_instance)
595
+ assert_equal(expected_result.merge(id: role.id), role_hash)
596
+ end
597
+
598
+ def test_fragment_fetch_with_except
599
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@bio_serializer)
438
600
  expected_result = {
439
- id: @role.id,
440
- description: @role.description,
441
- slug: "#{@role.name}-#{@role.id}",
442
- name: @role.name
601
+ id: @bio.id,
602
+ rating: nil,
603
+ content: @bio.content
443
604
  }
444
- assert_equal(@role_hash, expected_result)
605
+ cache_store.clear
606
+
607
+ bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance)
608
+ assert_equal(expected_result, bio_hash)
609
+
610
+ @bio.content = 'this has been updated'
611
+ @bio.rating = 'this was cached'
612
+
613
+ bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance)
614
+ assert_equal(expected_result.merge(content: @bio.content), bio_hash)
445
615
  end
446
616
 
447
617
  def test_fragment_fetch_with_namespaced_object
448
618
  @spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
449
619
  @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
450
- @spam_hash = @spam_serializer.fetch_fragment_cache(ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer))
620
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer)
621
+ @spam_hash = @spam_serializer.fetch_attributes_fragment(adapter_instance)
451
622
  expected_result = {
452
623
  id: @spam.id
453
624
  }
@@ -476,10 +647,5 @@ module ActiveModelSerializers
476
647
  def adapter
477
648
  @serializable_resource.adapter
478
649
  end
479
-
480
- def cached_serialization(serializer)
481
- cache_key = serializer.cache_key(adapter)
482
- cache_store.fetch(cache_key)
483
- end
484
650
  end
485
651
  end
@@ -3,15 +3,28 @@ require 'test_helper'
3
3
  module ActiveModel
4
4
  class Serializer
5
5
  class CollectionSerializerTest < ActiveSupport::TestCase
6
- MessagesSerializer = Class.new(ActiveModel::Serializer) do
6
+ class SingularModel < ::Model; end
7
+ class SingularModelSerializer < ActiveModel::Serializer
8
+ end
9
+ class HasManyModel < ::Model
10
+ associations :singular_models
11
+ end
12
+ class HasManyModelSerializer < ActiveModel::Serializer
13
+ has_many :singular_models
14
+
15
+ def custom_options
16
+ instance_options
17
+ end
18
+ end
19
+ class MessagesSerializer < ActiveModel::Serializer
7
20
  type 'messages'
8
21
  end
9
22
 
10
23
  def setup
11
- @comment = Comment.new
12
- @post = Post.new
13
- @resource = build_named_collection @comment, @post
14
- @serializer = collection_serializer.new(@resource, { some: :options })
24
+ @singular_model = SingularModel.new
25
+ @has_many_model = HasManyModel.new
26
+ @resource = build_named_collection @singular_model, @has_many_model
27
+ @serializer = collection_serializer.new(@resource, some: :options)
15
28
  end
16
29
 
17
30
  def collection_serializer
@@ -34,29 +47,29 @@ module ActiveModel
34
47
  def test_each_object_should_be_serialized_with_appropriate_serializer
35
48
  serializers = @serializer.to_a
36
49
 
37
- assert_kind_of CommentSerializer, serializers.first
38
- assert_kind_of Comment, serializers.first.object
50
+ assert_kind_of SingularModelSerializer, serializers.first
51
+ assert_kind_of SingularModel, serializers.first.object
39
52
 
40
- assert_kind_of PostSerializer, serializers.last
41
- assert_kind_of Post, serializers.last.object
53
+ assert_kind_of HasManyModelSerializer, serializers.last
54
+ assert_kind_of HasManyModel, serializers.last.object
42
55
 
43
56
  assert_equal :options, serializers.last.custom_options[:some]
44
57
  end
45
58
 
46
59
  def test_serializer_option_not_passed_to_each_serializer
47
- serializers = collection_serializer.new([@post], { serializer: PostSerializer }).to_a
60
+ serializers = collection_serializer.new([@has_many_model], serializer: HasManyModelSerializer).to_a
48
61
 
49
62
  refute serializers.first.custom_options.key?(:serializer)
50
63
  end
51
64
 
52
65
  def test_root_default
53
- @serializer = collection_serializer.new([@comment, @post])
66
+ @serializer = collection_serializer.new([@singular_model, @has_many_model])
54
67
  assert_nil @serializer.root
55
68
  end
56
69
 
57
70
  def test_root
58
71
  expected = 'custom_root'
59
- @serializer = collection_serializer.new([@comment, @post], root: expected)
72
+ @serializer = collection_serializer.new([@singular_model, @has_many_model], root: expected)
60
73
  assert_equal expected, @serializer.root
61
74
  end
62
75
 
@@ -80,12 +93,16 @@ module ActiveModel
80
93
  resource = []
81
94
  resource.define_singleton_method(:name) { nil }
82
95
  serializer = collection_serializer.new(resource)
83
- assert_nil serializer.json_key
96
+ assert_raise ArgumentError do
97
+ serializer.json_key
98
+ end
84
99
  end
85
100
 
86
101
  def test_json_key_with_resource_without_name_and_no_serializers
87
102
  serializer = collection_serializer.new([])
88
- assert_nil serializer.json_key
103
+ assert_raise ArgumentError do
104
+ serializer.json_key
105
+ end
89
106
  end
90
107
 
91
108
  def test_json_key_with_empty_resources_with_serializer
@@ -47,16 +47,6 @@ module ARModels
47
47
  has_many :comments
48
48
  belongs_to :author
49
49
  end
50
-
51
- class Comment < ActiveRecord::Base
52
- belongs_to :post
53
- belongs_to :author
54
- end
55
-
56
- class Author < ActiveRecord::Base
57
- has_many :posts
58
- end
59
-
60
50
  class PostSerializer < ActiveModel::Serializer
61
51
  attributes :id, :title, :body
62
52
 
@@ -64,15 +54,60 @@ module ARModels
64
54
  belongs_to :author
65
55
  end
66
56
 
57
+ class Comment < ActiveRecord::Base
58
+ belongs_to :post
59
+ belongs_to :author
60
+ end
67
61
  class CommentSerializer < ActiveModel::Serializer
68
62
  attributes :id, :contents
69
63
 
70
64
  belongs_to :author
71
65
  end
72
66
 
67
+ class Author < ActiveRecord::Base
68
+ has_many :posts
69
+ end
73
70
  class AuthorSerializer < ActiveModel::Serializer
74
71
  attributes :id, :name
75
72
 
76
73
  has_many :posts
77
74
  end
78
75
  end
76
+
77
+ class Employee < ActiveRecord::Base
78
+ has_many :pictures, as: :imageable
79
+ has_many :object_tags, as: :taggable
80
+ end
81
+
82
+ class PolymorphicSimpleSerializer < ActiveModel::Serializer
83
+ attributes :id
84
+ end
85
+
86
+ class ObjectTag < ActiveRecord::Base
87
+ belongs_to :poly_tag
88
+ belongs_to :taggable, polymorphic: true
89
+ end
90
+ class PolymorphicObjectTagSerializer < ActiveModel::Serializer
91
+ attributes :id
92
+ belongs_to :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true
93
+ end
94
+
95
+ class PolyTag < ActiveRecord::Base
96
+ has_many :object_tags
97
+ end
98
+ class PolymorphicTagSerializer < ActiveModel::Serializer
99
+ attributes :id, :phrase
100
+ has_many :object_tags, serializer: PolymorphicObjectTagSerializer
101
+ end
102
+
103
+ class Picture < ActiveRecord::Base
104
+ belongs_to :imageable, polymorphic: true
105
+ has_many :object_tags, as: :taggable
106
+ end
107
+ class PolymorphicHasManySerializer < ActiveModel::Serializer
108
+ attributes :id, :name
109
+ end
110
+ class PolymorphicBelongsToSerializer < ActiveModel::Serializer
111
+ attributes :id, :title
112
+ belongs_to :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true
113
+ end