active_model_serializers 0.10.0 → 0.10.9

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 (206) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +10 -5
  3. data/.travis.yml +41 -21
  4. data/CHANGELOG.md +200 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +25 -4
  7. data/README.md +166 -28
  8. data/Rakefile +5 -32
  9. data/active_model_serializers.gemspec +23 -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 +137 -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 +15 -3
  34. data/lib/active_model/serializable_resource.rb +2 -0
  35. data/lib/active_model/serializer/adapter/attributes.rb +2 -0
  36. data/lib/active_model/serializer/adapter/base.rb +4 -0
  37. data/lib/active_model/serializer/adapter/json.rb +2 -0
  38. data/lib/active_model/serializer/adapter/json_api.rb +2 -0
  39. data/lib/active_model/serializer/adapter/null.rb +2 -0
  40. data/lib/active_model/serializer/adapter.rb +2 -0
  41. data/lib/active_model/serializer/array_serializer.rb +10 -5
  42. data/lib/active_model/serializer/association.rb +64 -10
  43. data/lib/active_model/serializer/attribute.rb +2 -0
  44. data/lib/active_model/serializer/belongs_to_reflection.rb +6 -3
  45. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  46. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +87 -116
  47. data/lib/active_model/serializer/error_serializer.rb +13 -7
  48. data/lib/active_model/serializer/errors_serializer.rb +27 -20
  49. data/lib/active_model/serializer/field.rb +2 -0
  50. data/lib/active_model/serializer/fieldset.rb +2 -0
  51. data/lib/active_model/serializer/has_many_reflection.rb +5 -3
  52. data/lib/active_model/serializer/has_one_reflection.rb +3 -4
  53. data/lib/active_model/serializer/lazy_association.rb +99 -0
  54. data/lib/active_model/serializer/link.rb +23 -0
  55. data/lib/active_model/serializer/lint.rb +136 -130
  56. data/lib/active_model/serializer/null.rb +2 -0
  57. data/lib/active_model/serializer/reflection.rb +132 -67
  58. data/lib/active_model/serializer/version.rb +3 -1
  59. data/lib/active_model/serializer.rb +308 -82
  60. data/lib/active_model_serializers/adapter/attributes.rb +5 -66
  61. data/lib/active_model_serializers/adapter/base.rb +41 -39
  62. data/lib/active_model_serializers/adapter/json.rb +2 -0
  63. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +4 -2
  64. data/lib/active_model_serializers/adapter/json_api/error.rb +2 -0
  65. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +2 -0
  66. data/lib/active_model_serializers/adapter/json_api/link.rb +3 -1
  67. data/lib/active_model_serializers/adapter/json_api/meta.rb +2 -0
  68. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +49 -21
  69. data/lib/active_model_serializers/adapter/json_api/relationship.rb +77 -23
  70. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +41 -10
  71. data/lib/active_model_serializers/adapter/json_api.rb +84 -65
  72. data/lib/active_model_serializers/adapter/null.rb +2 -0
  73. data/lib/active_model_serializers/adapter.rb +9 -1
  74. data/lib/active_model_serializers/callbacks.rb +2 -0
  75. data/lib/active_model_serializers/deprecate.rb +3 -2
  76. data/lib/active_model_serializers/deserialization.rb +4 -0
  77. data/lib/active_model_serializers/json_pointer.rb +2 -0
  78. data/lib/active_model_serializers/logging.rb +2 -0
  79. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  80. data/lib/active_model_serializers/model.rb +111 -28
  81. data/lib/active_model_serializers/railtie.rb +7 -1
  82. data/lib/active_model_serializers/register_jsonapi_renderer.rb +46 -31
  83. data/lib/active_model_serializers/serializable_resource.rb +10 -7
  84. data/lib/active_model_serializers/serialization_context.rb +12 -3
  85. data/lib/active_model_serializers/test/schema.rb +4 -2
  86. data/lib/active_model_serializers/test/serializer.rb +2 -0
  87. data/lib/active_model_serializers/test.rb +2 -0
  88. data/lib/active_model_serializers.rb +35 -10
  89. data/lib/generators/rails/resource_override.rb +3 -1
  90. data/lib/generators/rails/serializer_generator.rb +6 -4
  91. data/lib/grape/active_model_serializers.rb +9 -5
  92. data/lib/grape/formatters/active_model_serializers.rb +21 -2
  93. data/lib/grape/helpers/active_model_serializers.rb +3 -0
  94. data/lib/tasks/rubocop.rake +55 -0
  95. data/test/action_controller/adapter_selector_test.rb +16 -5
  96. data/test/action_controller/explicit_serializer_test.rb +7 -4
  97. data/test/action_controller/json/include_test.rb +108 -27
  98. data/test/action_controller/json_api/deserialization_test.rb +3 -1
  99. data/test/action_controller/json_api/errors_test.rb +10 -9
  100. data/test/action_controller/json_api/fields_test.rb +68 -0
  101. data/test/action_controller/json_api/linked_test.rb +31 -24
  102. data/test/action_controller/json_api/pagination_test.rb +33 -23
  103. data/test/action_controller/json_api/transform_test.rb +13 -3
  104. data/test/action_controller/lookup_proc_test.rb +51 -0
  105. data/test/action_controller/namespace_lookup_test.rb +234 -0
  106. data/test/action_controller/serialization_scope_name_test.rb +14 -6
  107. data/test/action_controller/serialization_test.rb +23 -12
  108. data/test/active_model_serializers/adapter_for_test.rb +2 -0
  109. data/test/active_model_serializers/json_pointer_test.rb +17 -13
  110. data/test/active_model_serializers/logging_test.rb +2 -0
  111. data/test/active_model_serializers/model_test.rb +139 -4
  112. data/test/active_model_serializers/railtie_test_isolated.rb +14 -7
  113. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +163 -0
  114. data/test/active_model_serializers/serialization_context_test_isolated.rb +25 -10
  115. data/test/active_model_serializers/test/schema_test.rb +5 -2
  116. data/test/active_model_serializers/test/serializer_test.rb +2 -0
  117. data/test/active_record_test.rb +2 -0
  118. data/test/adapter/attributes_test.rb +42 -0
  119. data/test/adapter/deprecation_test.rb +2 -0
  120. data/test/adapter/json/belongs_to_test.rb +2 -0
  121. data/test/adapter/json/collection_test.rb +16 -0
  122. data/test/adapter/json/has_many_test.rb +12 -2
  123. data/test/adapter/json/transform_test.rb +17 -15
  124. data/test/adapter/json_api/belongs_to_test.rb +2 -0
  125. data/test/adapter/json_api/collection_test.rb +6 -3
  126. data/test/adapter/json_api/errors_test.rb +19 -19
  127. data/test/adapter/json_api/fields_test.rb +14 -3
  128. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +2 -0
  129. data/test/adapter/json_api/has_many_test.rb +51 -20
  130. data/test/adapter/json_api/has_one_test.rb +2 -0
  131. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +215 -0
  132. data/test/adapter/json_api/json_api_test.rb +7 -7
  133. data/test/adapter/json_api/linked_test.rb +35 -12
  134. data/test/adapter/json_api/links_test.rb +22 -3
  135. data/test/adapter/json_api/pagination_links_test.rb +55 -13
  136. data/test/adapter/json_api/parse_test.rb +3 -1
  137. data/test/adapter/json_api/relationship_test.rb +311 -73
  138. data/test/adapter/json_api/resource_meta_test.rb +5 -3
  139. data/test/adapter/json_api/toplevel_jsonapi_test.rb +2 -0
  140. data/test/adapter/json_api/transform_test.rb +265 -253
  141. data/test/adapter/json_api/type_test.rb +170 -36
  142. data/test/adapter/json_test.rb +10 -7
  143. data/test/adapter/null_test.rb +3 -2
  144. data/test/adapter/polymorphic_test.rb +54 -5
  145. data/test/adapter_test.rb +3 -1
  146. data/test/array_serializer_test.rb +2 -0
  147. data/test/benchmark/app.rb +3 -1
  148. data/test/benchmark/benchmarking_support.rb +3 -1
  149. data/test/benchmark/bm_active_record.rb +83 -0
  150. data/test/benchmark/bm_adapter.rb +40 -0
  151. data/test/benchmark/bm_caching.rb +18 -16
  152. data/test/benchmark/bm_lookup_chain.rb +85 -0
  153. data/test/benchmark/bm_transform.rb +23 -10
  154. data/test/benchmark/controllers.rb +18 -17
  155. data/test/benchmark/fixtures.rb +74 -72
  156. data/test/cache_test.rb +301 -69
  157. data/test/collection_serializer_test.rb +33 -14
  158. data/test/fixtures/active_record.rb +47 -10
  159. data/test/fixtures/poro.rb +128 -183
  160. data/test/generators/scaffold_controller_generator_test.rb +2 -0
  161. data/test/generators/serializer_generator_test.rb +25 -5
  162. data/test/grape_test.rb +172 -56
  163. data/test/lint_test.rb +3 -1
  164. data/test/logger_test.rb +15 -11
  165. data/test/poro_test.rb +2 -0
  166. data/test/serializable_resource_test.rb +20 -22
  167. data/test/serializers/association_macros_test.rb +5 -2
  168. data/test/serializers/associations_test.rb +274 -49
  169. data/test/serializers/attribute_test.rb +7 -3
  170. data/test/serializers/attributes_test.rb +3 -1
  171. data/test/serializers/caching_configuration_test_isolated.rb +8 -6
  172. data/test/serializers/configuration_test.rb +2 -0
  173. data/test/serializers/fieldset_test.rb +3 -1
  174. data/test/serializers/meta_test.rb +14 -6
  175. data/test/serializers/options_test.rb +19 -6
  176. data/test/serializers/read_attribute_for_serialization_test.rb +5 -3
  177. data/test/serializers/reflection_test.rb +481 -0
  178. data/test/serializers/root_test.rb +3 -1
  179. data/test/serializers/serialization_test.rb +4 -2
  180. data/test/serializers/serializer_for_test.rb +14 -10
  181. data/test/serializers/serializer_for_with_namespace_test.rb +90 -0
  182. data/test/support/isolated_unit.rb +11 -4
  183. data/test/support/rails5_shims.rb +10 -2
  184. data/test/support/rails_app.rb +4 -9
  185. data/test/support/serialization_testing.rb +33 -5
  186. data/test/test_helper.rb +15 -0
  187. metadata +126 -46
  188. data/.rubocop_todo.yml +0 -167
  189. data/docs/ARCHITECTURE.md +0 -126
  190. data/lib/active_model/serializer/associations.rb +0 -100
  191. data/lib/active_model/serializer/attributes.rb +0 -82
  192. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  193. data/lib/active_model/serializer/configuration.rb +0 -35
  194. data/lib/active_model/serializer/include_tree.rb +0 -111
  195. data/lib/active_model/serializer/links.rb +0 -35
  196. data/lib/active_model/serializer/meta.rb +0 -29
  197. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  198. data/lib/active_model/serializer/type.rb +0 -25
  199. data/lib/active_model_serializers/key_transform.rb +0 -70
  200. data/test/active_model_serializers/key_transform_test.rb +0 -263
  201. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  202. data/test/adapter/json_api/relationships_test.rb +0 -199
  203. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  204. data/test/include_tree/from_include_args_test.rb +0 -26
  205. data/test/include_tree/from_string_test.rb +0 -94
  206. data/test/include_tree/include_args_to_hash_test.rb +0 -64
data/test/cache_test.rb CHANGED
@@ -1,25 +1,121 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
  require 'tmpdir'
3
5
  require 'tempfile'
4
6
 
5
7
  module ActiveModelSerializers
6
8
  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
9
+ class Article < ::Model
10
+ attributes :title
11
+ # To confirm error is raised when cache_key is not set and cache_key option not passed to cache
9
12
  undef_method :cache_key
10
13
  end
14
+ class ArticleSerializer < ActiveModel::Serializer
15
+ cache only: [:place], skip_digest: true
16
+ attributes :title
17
+ end
11
18
 
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
19
+ class Author < ::Model
20
+ attributes :id, :name
21
+ associations :posts, :bio, :roles
22
+ end
23
+ # Instead of a primitive cache key (i.e. a string), this class
24
+ # returns a list of objects that require to be expanded themselves.
25
+ class AuthorWithExpandableCacheElements < Author
26
+ # For the test purposes it's important that #to_s for HasCacheKey differs
27
+ # between instances, hence not a Struct.
28
+ class HasCacheKey
29
+ attr_reader :cache_key
30
+ def initialize(cache_key)
31
+ @cache_key = cache_key
32
+ end
33
+
34
+ def to_s
35
+ "HasCacheKey##{object_id}"
36
+ end
37
+ end
38
+
39
+ def cache_key
40
+ [
41
+ HasCacheKey.new(name),
42
+ HasCacheKey.new(id)
43
+ ]
44
+ end
45
+ end
46
+ class UncachedAuthor < Author
47
+ # To confirm cache_key is set using updated_at and cache_key option passed to cache
14
48
  undef_method :cache_key
15
49
  end
50
+ class AuthorSerializer < ActiveModel::Serializer
51
+ cache key: 'writer', skip_digest: true
52
+ attributes :id, :name
16
53
 
17
- ArticleSerializer = Class.new(ActiveModel::Serializer) do
18
- cache only: [:place], skip_digest: true
19
- attributes :title
54
+ has_many :posts
55
+ has_many :roles
56
+ has_one :bio
57
+ end
58
+ class AuthorSerializerWithCache < ActiveModel::Serializer
59
+ cache
60
+
61
+ attributes :name
62
+ end
63
+
64
+ class Blog < ::Model
65
+ attributes :name
66
+ associations :writer
67
+ end
68
+ class BlogSerializer < ActiveModel::Serializer
69
+ cache key: 'blog'
70
+ attributes :id, :name
71
+
72
+ belongs_to :writer
20
73
  end
21
74
 
22
- InheritedRoleSerializer = Class.new(RoleSerializer) do
75
+ class Comment < ::Model
76
+ attributes :id, :body
77
+ associations :post, :author
78
+
79
+ # Uses a custom non-time-based cache key
80
+ def cache_key
81
+ "comment/#{id}"
82
+ end
83
+ end
84
+ class CommentSerializer < ActiveModel::Serializer
85
+ cache expires_in: 1.day, skip_digest: true
86
+ attributes :id, :body
87
+ belongs_to :post
88
+ belongs_to :author
89
+ end
90
+
91
+ class Post < ::Model
92
+ attributes :id, :title, :body
93
+ associations :author, :comments, :blog
94
+ end
95
+ class PostSerializer < ActiveModel::Serializer
96
+ cache key: 'post', expires_in: 0.1, skip_digest: true
97
+ attributes :id, :title, :body
98
+
99
+ has_many :comments
100
+ belongs_to :blog
101
+ belongs_to :author
102
+ end
103
+
104
+ class Role < ::Model
105
+ attributes :name, :description, :special_attribute
106
+ associations :author
107
+ end
108
+ class RoleSerializer < ActiveModel::Serializer
109
+ cache only: [:name, :slug], skip_digest: true
110
+ attributes :id, :name, :description
111
+ attribute :friendly_id, key: :slug
112
+ belongs_to :author
113
+
114
+ def friendly_id
115
+ "#{object.name}-#{object.id}"
116
+ end
117
+ end
118
+ class InheritedRoleSerializer < RoleSerializer
23
119
  cache key: 'inherited_role', only: [:name, :special_attribute]
24
120
  attribute :special_attribute
25
121
  end
@@ -27,10 +123,10 @@ module ActiveModelSerializers
27
123
  setup do
28
124
  cache_store.clear
29
125
  @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
30
- @post = Post.new(title: 'New Post', body: 'Body')
126
+ @post = Post.new(id: 'post', title: 'New Post', body: 'Body')
31
127
  @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: [])
128
+ @author = Author.new(id: 'author', name: 'Joao M. D. Moura')
129
+ @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author)
34
130
  @role = Role.new(name: 'Great Author')
35
131
  @location = Location.new(lat: '-23.550520', lng: '-46.633309')
36
132
  @place = Place.new(name: 'Amazing Place')
@@ -55,6 +151,65 @@ module ActiveModelSerializers
55
151
  @blog_serializer = BlogSerializer.new(@blog)
56
152
  end
57
153
 
154
+ def test_expiring_of_cache_at_update_of_record
155
+ original_cache_versioning = :none
156
+
157
+ if ARModels::Author.respond_to?(:cache_versioning)
158
+ original_cache_versioning = ARModels::Author.cache_versioning
159
+ ARModels::Author.cache_versioning = true
160
+ end
161
+
162
+ author = ARModels::Author.create(name: 'Foo')
163
+ author_json = AuthorSerializerWithCache.new(author).as_json
164
+
165
+ assert_equal 'Foo', author_json[:name]
166
+
167
+ author.update_attributes(name: 'Bar')
168
+ author_json = AuthorSerializerWithCache.new(author).as_json
169
+
170
+ expected = 'Bar'
171
+ actual = author_json[:name]
172
+ if ENV['APPVEYOR'] && actual != expected
173
+ skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)')
174
+ else
175
+ assert_equal expected, actual
176
+ end
177
+ ensure
178
+ ARModels::Author.cache_versioning = original_cache_versioning unless original_cache_versioning == :none
179
+ end
180
+
181
+ def test_cache_expiration_in_collection_on_update_of_record
182
+ original_cache_versioning = :none
183
+
184
+ if ARModels::Author.respond_to?(:cache_versioning)
185
+ original_cache_versioning = ARModels::Author.cache_versioning
186
+ ARModels::Author.cache_versioning = true
187
+ end
188
+
189
+ foo = 'Foo'
190
+ foo2 = 'Foo2'
191
+ author = ARModels::Author.create(name: foo)
192
+ author2 = ARModels::Author.create(name: foo2)
193
+ author_collection = [author, author, author2]
194
+
195
+ collection_json = render_object_with_cache(author_collection, each_serializer: AuthorSerializerWithCache)
196
+ actual = collection_json
197
+ expected = [{ name: foo }, { name: foo }, { name: foo2 }]
198
+ if ENV['APPVEYOR'] && actual != expected
199
+ skip('Cache expiration tests sometimes fail on Appveyor. FIXME :)')
200
+ else
201
+ assert_equal expected, actual
202
+ end
203
+
204
+ bar = 'Bar'
205
+ author.update!(name: bar)
206
+
207
+ collection_json = render_object_with_cache(author_collection, each_serializer: AuthorSerializerWithCache)
208
+ assert_equal [{ name: bar }, { name: bar }, { name: foo2 }], collection_json
209
+ ensure
210
+ ARModels::Author.cache_versioning = original_cache_versioning unless original_cache_versioning == :none
211
+ end
212
+
58
213
  def test_explicit_cache_store
59
214
  default_store = Class.new(ActiveModel::Serializer) do
60
215
  cache
@@ -93,7 +248,7 @@ module ActiveModelSerializers
93
248
  def test_cache_key_definition
94
249
  assert_equal('post', @post_serializer.class._cache_key)
95
250
  assert_equal('writer', @author_serializer.class._cache_key)
96
- assert_equal(nil, @comment_serializer.class._cache_key)
251
+ assert_nil(@comment_serializer.class._cache_key)
97
252
  end
98
253
 
99
254
  def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object
@@ -101,14 +256,28 @@ module ActiveModelSerializers
101
256
  uncached_author_serializer = AuthorSerializer.new(uncached_author)
102
257
 
103
258
  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}"
259
+ 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')}"
260
+ key = "#{key}/#{adapter.cache_key}"
106
261
  assert_equal(uncached_author_serializer.attributes.to_json, cache_store.fetch(key).to_json)
107
262
  end
108
263
 
264
+ def test_cache_key_expansion
265
+ author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello')
266
+ same_author = AuthorWithExpandableCacheElements.new(id: 10, name: 'hello')
267
+ diff_author = AuthorWithExpandableCacheElements.new(id: 11, name: 'hello')
268
+
269
+ author_serializer = AuthorSerializer.new(author)
270
+ same_author_serializer = AuthorSerializer.new(same_author)
271
+ diff_author_serializer = AuthorSerializer.new(diff_author)
272
+ adapter = AuthorSerializer.serialization_adapter_instance
273
+
274
+ assert_equal(author_serializer.cache_key(adapter), same_author_serializer.cache_key(adapter))
275
+ refute_equal(author_serializer.cache_key(adapter), diff_author_serializer.cache_key(adapter))
276
+ end
277
+
109
278
  def test_default_cache_key_fallback
110
279
  render_object_with_cache(@comment)
111
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
280
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
112
281
  assert_equal(@comment_serializer.attributes.to_json, cache_store.fetch(key).to_json)
113
282
  end
114
283
 
@@ -117,31 +286,31 @@ module ActiveModelSerializers
117
286
  e = assert_raises ActiveModel::Serializer::UndefinedCacheKey do
118
287
  render_object_with_cache(article)
119
288
  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)
289
+ assert_match(/ActiveModelSerializers::CacheTest::Article must define #cache_key, or the 'key:' option must be passed into 'ActiveModelSerializers::CacheTest::ArticleSerializer.cache'/, e.message)
121
290
  end
122
291
 
123
292
  def test_cache_options_definition
124
293
  assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options)
125
- assert_equal(nil, @blog_serializer.class._cache_options)
294
+ assert_nil(@blog_serializer.class._cache_options)
126
295
  assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options)
127
296
  end
128
297
 
129
298
  def test_fragment_cache_definition
130
- assert_equal([:name], @role_serializer.class._cache_only)
299
+ assert_equal([:name, :slug], @role_serializer.class._cache_only)
131
300
  assert_equal([:content], @bio_serializer.class._cache_except)
132
301
  end
133
302
 
134
303
  def test_associations_separately_cache
135
304
  cache_store.clear
136
- assert_equal(nil, cache_store.fetch(@post.cache_key))
137
- assert_equal(nil, cache_store.fetch(@comment.cache_key))
305
+ assert_nil(cache_store.fetch(@post.cache_key))
306
+ assert_nil(cache_store.fetch(@comment.cache_key))
138
307
 
139
308
  Timecop.freeze(Time.current) do
140
309
  render_object_with_cache(@post)
141
310
 
142
- key = "#{@post.cache_key}/#{adapter.cached_name}"
311
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
143
312
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
144
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
313
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
145
314
  assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
146
315
  end
147
316
  end
@@ -152,9 +321,9 @@ module ActiveModelSerializers
152
321
  render_object_with_cache(@post)
153
322
 
154
323
  # Check if it cached the objects separately
155
- key = "#{@post.cache_key}/#{adapter.cached_name}"
324
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
156
325
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
157
- key = "#{@comment.cache_key}/#{adapter.cached_name}"
326
+ key = "#{@comment.cache_key}/#{adapter.cache_key}"
158
327
  assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
159
328
 
160
329
  # Simulating update on comments relationship with Post
@@ -166,9 +335,9 @@ module ActiveModelSerializers
166
335
  render_object_with_cache(@post)
167
336
 
168
337
  # Check if the the new comment was cached
169
- key = "#{new_comment.cache_key}/#{adapter.cached_name}"
338
+ key = "#{new_comment.cache_key}/#{adapter.cache_key}"
170
339
  assert_equal(new_comment_serializer.attributes, cache_store.fetch(key))
171
- key = "#{@post.cache_key}/#{adapter.cached_name}"
340
+ key = "#{@post.cache_key}/#{adapter.cache_key}"
172
341
  assert_equal(@post_serializer.attributes, cache_store.fetch(key))
173
342
  end
174
343
  end
@@ -178,14 +347,14 @@ module ActiveModelSerializers
178
347
  id: @location.id,
179
348
  lat: @location.lat,
180
349
  lng: @location.lng,
181
- place: 'Nowhere'
350
+ address: 'Nowhere'
182
351
  }
183
352
 
184
353
  hash = render_object_with_cache(@location)
185
354
 
186
355
  assert_equal(hash, expected_result)
187
- key = "#{@location.cache_key}/#{adapter.cached_name}"
188
- assert_equal({ place: 'Nowhere' }, cache_store.fetch(key))
356
+ key = "#{@location.cache_key}/#{adapter.cache_key}"
357
+ assert_equal({ address: 'Nowhere' }, cache_store.fetch(key))
189
358
  end
190
359
 
191
360
  def test_fragment_cache_with_inheritance
@@ -204,9 +373,9 @@ module ActiveModelSerializers
204
373
 
205
374
  # Based on original failing test by @kevintyll
206
375
  # rubocop:disable Metrics/AbcSize
207
- def test_a_serializer_rendered_by_two_adapter_returns_differently_cached_attributes
376
+ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attributes
208
377
  Object.const_set(:Alert, Class.new(ActiveModelSerializers::Model) do
209
- attr_accessor :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
378
+ attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
210
379
  end)
211
380
  Object.const_set(:UncachedAlertSerializer, Class.new(ActiveModel::Serializer) do
212
381
  attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
@@ -225,7 +394,7 @@ module ActiveModelSerializers
225
394
  created_at: Time.new(2016, 3, 31, 21, 37, 35, 0)
226
395
  )
227
396
 
228
- expected_cached_attributes = {
397
+ expected_fetch_attributes = {
229
398
  id: 1,
230
399
  status: 'fail',
231
400
  resource: 'resource-1',
@@ -233,7 +402,7 @@ module ActiveModelSerializers
233
402
  ended_at: nil,
234
403
  updated_at: alert.updated_at,
235
404
  created_at: alert.created_at
236
- }
405
+ }.with_indifferent_access
237
406
  expected_cached_jsonapi_attributes = {
238
407
  id: '1',
239
408
  type: 'alerts',
@@ -245,15 +414,15 @@ module ActiveModelSerializers
245
414
  updated_at: alert.updated_at,
246
415
  created_at: alert.created_at
247
416
  }
248
- }
417
+ }.with_indifferent_access
249
418
 
250
419
  # Assert attributes are serialized correctly
251
420
  serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :attributes)
252
- attributes_serialization = serializable_alert.as_json
253
- assert_equal expected_cached_attributes, alert.attributes
421
+ attributes_serialization = serializable_alert.as_json.with_indifferent_access
422
+ assert_equal expected_fetch_attributes, alert.attributes
254
423
  assert_equal alert.attributes, attributes_serialization
255
424
  attributes_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
256
- assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key)
425
+ assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key).with_indifferent_access
257
426
 
258
427
  serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :json_api)
259
428
  jsonapi_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
@@ -265,7 +434,7 @@ module ActiveModelSerializers
265
434
  serializable_alert = serializable(alert, serializer: UncachedAlertSerializer, adapter: :json_api)
266
435
  assert_equal serializable_alert.as_json, jsonapi_serialization
267
436
 
268
- cached_serialization = cache_store.fetch(jsonapi_cache_key)
437
+ cached_serialization = cache_store.fetch(jsonapi_cache_key).with_indifferent_access
269
438
  assert_equal expected_cached_jsonapi_attributes, cached_serialization
270
439
  ensure
271
440
  Object.send(:remove_const, :Alert)
@@ -276,43 +445,83 @@ module ActiveModelSerializers
276
445
 
277
446
  def test_uses_file_digest_in_cache_key
278
447
  render_object_with_cache(@blog)
279
- key = "#{@blog.cache_key}/#{adapter.cached_name}/#{::Model::FILE_DIGEST}"
448
+ file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read)
449
+ key = "#{@blog.cache_key}/#{adapter.cache_key}/#{file_digest}"
280
450
  assert_equal(@blog_serializer.attributes, cache_store.fetch(key))
281
451
  end
282
452
 
283
453
  def test_cache_digest_definition
284
- assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
454
+ file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read)
455
+ assert_equal(file_digest, @post_serializer.class._cache_digest)
285
456
  end
286
457
 
287
458
  def test_object_cache_keys
288
459
  serializable = ActiveModelSerializers::SerializableResource.new([@comment, @comment])
289
- include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*')
460
+ include_directive = JSONAPI::IncludeDirective.new('*', allow_wildcard: true)
290
461
 
291
- actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_tree)
462
+ actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_directive)
292
463
 
293
464
  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+} }
465
+ expected_key = "comment/1/#{serializable.adapter.cache_key}"
466
+ assert actual.any? { |key| key == expected_key }, "actual '#{actual}' should include #{expected_key}"
467
+ expected_key = %r{post/post-\d+}
468
+ assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'"
469
+ expected_key = %r{author/author-\d+}
470
+ assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'"
297
471
  end
298
472
 
299
- def test_cached_attributes
300
- serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
473
+ # rubocop:disable Metrics/AbcSize
474
+ def test_fetch_attributes_from_cache
475
+ serializers = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
301
476
 
302
477
  Timecop.freeze(Time.current) do
303
478
  render_object_with_cache(@comment)
304
479
 
305
- attributes = Adapter::Attributes.new(serializer)
306
- attributes.send(:cache_attributes)
307
- cached_attributes = attributes.instance_variable_get(:@cached_attributes)
480
+ options = {}
481
+ adapter_options = {}
482
+ adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
483
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
484
+ cached_attributes = options.fetch(:cached_attributes).with_indifferent_access
485
+
486
+ include_directive = ActiveModelSerializers.default_include_directive
487
+ manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive).with_indifferent_access
488
+ assert_equal manual_cached_attributes, cached_attributes
308
489
 
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
490
+ assert_equal cached_attributes["#{@comment.cache_key}/#{adapter_instance.cache_key}"], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes
491
+ assert_equal cached_attributes["#{@comment.post.cache_key}/#{adapter_instance.cache_key}"], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
311
492
 
312
493
  writer = @comment.post.blog.writer
313
494
  writer_cache_key = writer.cache_key
495
+ assert_equal cached_attributes["#{writer_cache_key}/#{adapter_instance.cache_key}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
496
+ end
497
+ end
498
+ # rubocop:enable Metrics/AbcSize
499
+
500
+ def test_cache_read_multi_with_fragment_cache_enabled
501
+ post_serializer = Class.new(ActiveModel::Serializer) do
502
+ cache except: [:body]
503
+ end
314
504
 
315
- assert_equal cached_attributes["#{writer_cache_key}/#{attributes.cached_name}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
505
+ serializers = ActiveModel::Serializer::CollectionSerializer.new([@post, @post], serializer: post_serializer)
506
+
507
+ Timecop.freeze(Time.current) do
508
+ # Warming up.
509
+ options = {}
510
+ adapter_options = {}
511
+ adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options)
512
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
513
+
514
+ # Should find something with read_multi now
515
+ options = {}
516
+ serializers.serializable_hash(adapter_options, options, adapter_instance)
517
+ cached_attributes = options.fetch(:cached_attributes)
518
+
519
+ include_directive = ActiveModelSerializers.default_include_directive
520
+ manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive)
521
+
522
+ refute_equal 0, cached_attributes.size
523
+ refute_equal 0, manual_cached_attributes.size
524
+ assert_equal manual_cached_attributes, cached_attributes
316
525
  end
317
526
  end
318
527
 
@@ -429,25 +638,53 @@ module ActiveModelSerializers
429
638
  end
430
639
 
431
640
  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))
641
+ author = Author.new(name: 'Joao M. D. Moura')
642
+ role = Role.new(name: 'Great Author', description: nil)
643
+ role.author = [author]
644
+ role_serializer = RoleSerializer.new(role)
645
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(role_serializer)
646
+ expected_result = {
647
+ id: role.id,
648
+ description: role.description,
649
+ slug: "#{role.name}-#{role.id}",
650
+ name: role.name
651
+ }
652
+ cache_store.clear
437
653
 
654
+ role_hash = role_serializer.fetch_attributes_fragment(adapter_instance)
655
+ assert_equal(role_hash, expected_result)
656
+
657
+ role.id = 'this has been updated'
658
+ role.name = 'this was cached'
659
+
660
+ role_hash = role_serializer.fetch_attributes_fragment(adapter_instance)
661
+ assert_equal(expected_result.merge(id: role.id), role_hash)
662
+ end
663
+
664
+ def test_fragment_fetch_with_except
665
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@bio_serializer)
438
666
  expected_result = {
439
- id: @role.id,
440
- description: @role.description,
441
- slug: "#{@role.name}-#{@role.id}",
442
- name: @role.name
667
+ id: @bio.id,
668
+ rating: nil,
669
+ content: @bio.content
443
670
  }
444
- assert_equal(@role_hash, expected_result)
671
+ cache_store.clear
672
+
673
+ bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance)
674
+ assert_equal(expected_result, bio_hash)
675
+
676
+ @bio.content = 'this has been updated'
677
+ @bio.rating = 'this was cached'
678
+
679
+ bio_hash = @bio_serializer.fetch_attributes_fragment(adapter_instance)
680
+ assert_equal(expected_result.merge(content: @bio.content), bio_hash)
445
681
  end
446
682
 
447
683
  def test_fragment_fetch_with_namespaced_object
448
684
  @spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
449
685
  @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
450
- @spam_hash = @spam_serializer.fetch_fragment_cache(ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer))
686
+ adapter_instance = ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer)
687
+ @spam_hash = @spam_serializer.fetch_attributes_fragment(adapter_instance)
451
688
  expected_result = {
452
689
  id: @spam.id
453
690
  }
@@ -476,10 +713,5 @@ module ActiveModelSerializers
476
713
  def adapter
477
714
  @serializable_resource.adapter
478
715
  end
479
-
480
- def cached_serialization(serializer)
481
- cache_key = serializer.cache_key(adapter)
482
- cache_store.fetch(cache_key)
483
- end
484
716
  end
485
717
  end
@@ -1,17 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  module ActiveModel
4
6
  class Serializer
5
7
  class CollectionSerializerTest < ActiveSupport::TestCase
6
- MessagesSerializer = Class.new(ActiveModel::Serializer) do
8
+ class SingularModel < ::Model; end
9
+ class SingularModelSerializer < ActiveModel::Serializer
10
+ end
11
+ class HasManyModel < ::Model
12
+ associations :singular_models
13
+ end
14
+ class HasManyModelSerializer < ActiveModel::Serializer
15
+ has_many :singular_models
16
+
17
+ def custom_options
18
+ instance_options
19
+ end
20
+ end
21
+ class MessagesSerializer < ActiveModel::Serializer
7
22
  type 'messages'
8
23
  end
9
24
 
10
25
  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 })
26
+ @singular_model = SingularModel.new
27
+ @has_many_model = HasManyModel.new
28
+ @resource = build_named_collection @singular_model, @has_many_model
29
+ @serializer = collection_serializer.new(@resource, some: :options)
15
30
  end
16
31
 
17
32
  def collection_serializer
@@ -34,29 +49,29 @@ module ActiveModel
34
49
  def test_each_object_should_be_serialized_with_appropriate_serializer
35
50
  serializers = @serializer.to_a
36
51
 
37
- assert_kind_of CommentSerializer, serializers.first
38
- assert_kind_of Comment, serializers.first.object
52
+ assert_kind_of SingularModelSerializer, serializers.first
53
+ assert_kind_of SingularModel, serializers.first.object
39
54
 
40
- assert_kind_of PostSerializer, serializers.last
41
- assert_kind_of Post, serializers.last.object
55
+ assert_kind_of HasManyModelSerializer, serializers.last
56
+ assert_kind_of HasManyModel, serializers.last.object
42
57
 
43
58
  assert_equal :options, serializers.last.custom_options[:some]
44
59
  end
45
60
 
46
61
  def test_serializer_option_not_passed_to_each_serializer
47
- serializers = collection_serializer.new([@post], { serializer: PostSerializer }).to_a
62
+ serializers = collection_serializer.new([@has_many_model], serializer: HasManyModelSerializer).to_a
48
63
 
49
64
  refute serializers.first.custom_options.key?(:serializer)
50
65
  end
51
66
 
52
67
  def test_root_default
53
- @serializer = collection_serializer.new([@comment, @post])
68
+ @serializer = collection_serializer.new([@singular_model, @has_many_model])
54
69
  assert_nil @serializer.root
55
70
  end
56
71
 
57
72
  def test_root
58
73
  expected = 'custom_root'
59
- @serializer = collection_serializer.new([@comment, @post], root: expected)
74
+ @serializer = collection_serializer.new([@singular_model, @has_many_model], root: expected)
60
75
  assert_equal expected, @serializer.root
61
76
  end
62
77
 
@@ -80,12 +95,16 @@ module ActiveModel
80
95
  resource = []
81
96
  resource.define_singleton_method(:name) { nil }
82
97
  serializer = collection_serializer.new(resource)
83
- assert_nil serializer.json_key
98
+ assert_raise ArgumentError do
99
+ serializer.json_key
100
+ end
84
101
  end
85
102
 
86
103
  def test_json_key_with_resource_without_name_and_no_serializers
87
104
  serializer = collection_serializer.new([])
88
- assert_nil serializer.json_key
105
+ assert_raise ArgumentError do
106
+ serializer.json_key
107
+ end
89
108
  end
90
109
 
91
110
  def test_json_key_with_empty_resources_with_serializer