active_model_serializers 0.8.3 → 0.10.0

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +29 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  4. data/.gitignore +17 -0
  5. data/.rubocop.yml +104 -0
  6. data/.rubocop_todo.yml +167 -0
  7. data/.simplecov +110 -0
  8. data/.travis.yml +39 -24
  9. data/CHANGELOG.md +465 -6
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +50 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +102 -590
  14. data/Rakefile +93 -8
  15. data/active_model_serializers.gemspec +65 -23
  16. data/appveyor.yml +24 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/serve_benchmark +39 -0
  20. data/docs/ARCHITECTURE.md +126 -0
  21. data/docs/README.md +40 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +245 -0
  24. data/docs/general/caching.md +52 -0
  25. data/docs/general/configuration_options.md +100 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/getting_started.md +133 -0
  28. data/docs/general/instrumentation.md +40 -0
  29. data/docs/general/key_transforms.md +40 -0
  30. data/docs/general/logging.md +14 -0
  31. data/docs/general/rendering.md +255 -0
  32. data/docs/general/serializers.md +372 -0
  33. data/docs/how-open-source-maintained.jpg +0 -0
  34. data/docs/howto/add_pagination_links.md +139 -0
  35. data/docs/howto/add_root_key.md +51 -0
  36. data/docs/howto/outside_controller_use.md +58 -0
  37. data/docs/howto/passing_arbitrary_options.md +27 -0
  38. data/docs/howto/serialize_poro.md +32 -0
  39. data/docs/howto/test.md +152 -0
  40. data/docs/integrations/ember-and-json-api.md +112 -0
  41. data/docs/integrations/grape.md +19 -0
  42. data/docs/jsonapi/errors.md +56 -0
  43. data/docs/jsonapi/schema/schema.json +366 -0
  44. data/docs/jsonapi/schema.md +151 -0
  45. data/docs/rfcs/0000-namespace.md +106 -0
  46. data/docs/rfcs/template.md +15 -0
  47. data/lib/action_controller/serialization.rb +31 -36
  48. data/lib/active_model/serializable_resource.rb +11 -0
  49. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  50. data/lib/active_model/serializer/adapter/base.rb +16 -0
  51. data/lib/active_model/serializer/adapter/json.rb +15 -0
  52. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  53. data/lib/active_model/serializer/adapter/null.rb +15 -0
  54. data/lib/active_model/serializer/adapter.rb +24 -0
  55. data/lib/active_model/serializer/array_serializer.rb +9 -0
  56. data/lib/active_model/serializer/association.rb +19 -0
  57. data/lib/active_model/serializer/associations.rb +87 -220
  58. data/lib/active_model/serializer/attribute.rb +25 -0
  59. data/lib/active_model/serializer/attributes.rb +82 -0
  60. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  61. data/lib/active_model/serializer/caching.rb +333 -0
  62. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +64 -0
  64. data/lib/active_model/serializer/configuration.rb +35 -0
  65. data/lib/active_model/serializer/error_serializer.rb +10 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +27 -0
  67. data/lib/active_model/serializer/field.rb +90 -0
  68. data/lib/active_model/serializer/fieldset.rb +31 -0
  69. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  70. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  71. data/lib/active_model/serializer/include_tree.rb +111 -0
  72. data/lib/active_model/serializer/links.rb +35 -0
  73. data/lib/active_model/serializer/lint.rb +146 -0
  74. data/lib/active_model/serializer/meta.rb +29 -0
  75. data/lib/active_model/serializer/null.rb +17 -0
  76. data/lib/active_model/serializer/reflection.rb +147 -0
  77. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  78. data/lib/active_model/serializer/type.rb +25 -0
  79. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  80. data/lib/active_model/serializer.rb +158 -481
  81. data/lib/active_model_serializers/adapter/attributes.rb +76 -0
  82. data/lib/active_model_serializers/adapter/base.rb +83 -0
  83. data/lib/active_model_serializers/adapter/json.rb +21 -0
  84. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  85. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  86. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  87. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  88. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  89. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +62 -0
  90. data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
  91. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
  92. data/lib/active_model_serializers/adapter/json_api.rb +516 -0
  93. data/lib/active_model_serializers/adapter/null.rb +9 -0
  94. data/lib/active_model_serializers/adapter.rb +92 -0
  95. data/lib/active_model_serializers/callbacks.rb +55 -0
  96. data/lib/active_model_serializers/deprecate.rb +55 -0
  97. data/lib/active_model_serializers/deserialization.rb +13 -0
  98. data/lib/active_model_serializers/json_pointer.rb +14 -0
  99. data/lib/active_model_serializers/key_transform.rb +70 -0
  100. data/lib/active_model_serializers/logging.rb +122 -0
  101. data/lib/active_model_serializers/model.rb +49 -0
  102. data/lib/active_model_serializers/railtie.rb +46 -0
  103. data/lib/active_model_serializers/register_jsonapi_renderer.rb +65 -0
  104. data/lib/active_model_serializers/serializable_resource.rb +81 -0
  105. data/lib/active_model_serializers/serialization_context.rb +32 -0
  106. data/lib/active_model_serializers/test/schema.rb +138 -0
  107. data/lib/active_model_serializers/test/serializer.rb +125 -0
  108. data/lib/active_model_serializers/test.rb +7 -0
  109. data/lib/active_model_serializers.rb +32 -89
  110. data/lib/generators/rails/USAGE +6 -0
  111. data/lib/generators/rails/resource_override.rb +10 -0
  112. data/lib/generators/rails/serializer_generator.rb +36 -0
  113. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  114. data/lib/grape/active_model_serializers.rb +14 -0
  115. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  116. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  117. data/test/action_controller/adapter_selector_test.rb +53 -0
  118. data/test/action_controller/explicit_serializer_test.rb +134 -0
  119. data/test/action_controller/json/include_test.rb +167 -0
  120. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  121. data/test/action_controller/json_api/errors_test.rb +41 -0
  122. data/test/action_controller/json_api/linked_test.rb +197 -0
  123. data/test/action_controller/json_api/pagination_test.rb +116 -0
  124. data/test/action_controller/json_api/transform_test.rb +181 -0
  125. data/test/action_controller/serialization_scope_name_test.rb +229 -0
  126. data/test/action_controller/serialization_test.rb +469 -0
  127. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  128. data/test/active_model_serializers/json_pointer_test.rb +20 -0
  129. data/test/active_model_serializers/key_transform_test.rb +263 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +9 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
  133. data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
  134. data/test/active_model_serializers/test/schema_test.rb +130 -0
  135. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  136. data/test/active_record_test.rb +9 -0
  137. data/test/adapter/deprecation_test.rb +100 -0
  138. data/test/adapter/json/belongs_to_test.rb +45 -0
  139. data/test/adapter/json/collection_test.rb +90 -0
  140. data/test/adapter/json/has_many_test.rb +45 -0
  141. data/test/adapter/json/transform_test.rb +93 -0
  142. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  143. data/test/adapter/json_api/collection_test.rb +95 -0
  144. data/test/adapter/json_api/errors_test.rb +78 -0
  145. data/test/adapter/json_api/fields_test.rb +87 -0
  146. data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
  147. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_test.rb +144 -0
  149. data/test/adapter/json_api/has_one_test.rb +80 -0
  150. data/test/adapter/json_api/json_api_test.rb +35 -0
  151. data/test/adapter/json_api/linked_test.rb +392 -0
  152. data/test/adapter/json_api/links_test.rb +93 -0
  153. data/test/adapter/json_api/pagination_links_test.rb +166 -0
  154. data/test/adapter/json_api/parse_test.rb +137 -0
  155. data/test/adapter/json_api/relationship_test.rb +161 -0
  156. data/test/adapter/json_api/relationships_test.rb +199 -0
  157. data/test/adapter/json_api/resource_identifier_test.rb +85 -0
  158. data/test/adapter/json_api/resource_meta_test.rb +100 -0
  159. data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
  160. data/test/adapter/json_api/transform_test.rb +502 -0
  161. data/test/adapter/json_api/type_test.rb +61 -0
  162. data/test/adapter/json_test.rb +45 -0
  163. data/test/adapter/null_test.rb +23 -0
  164. data/test/adapter/polymorphic_test.rb +171 -0
  165. data/test/adapter_test.rb +67 -0
  166. data/test/array_serializer_test.rb +20 -73
  167. data/test/benchmark/app.rb +65 -0
  168. data/test/benchmark/benchmarking_support.rb +67 -0
  169. data/test/benchmark/bm_caching.rb +119 -0
  170. data/test/benchmark/bm_transform.rb +34 -0
  171. data/test/benchmark/config.ru +3 -0
  172. data/test/benchmark/controllers.rb +84 -0
  173. data/test/benchmark/fixtures.rb +219 -0
  174. data/test/cache_test.rb +485 -0
  175. data/test/collection_serializer_test.rb +110 -0
  176. data/test/fixtures/active_record.rb +78 -0
  177. data/test/fixtures/poro.rb +282 -0
  178. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  179. data/test/generators/serializer_generator_test.rb +57 -0
  180. data/test/grape_test.rb +82 -0
  181. data/test/include_tree/from_include_args_test.rb +26 -0
  182. data/test/include_tree/from_string_test.rb +94 -0
  183. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +18 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +83 -0
  188. data/test/serializers/association_macros_test.rb +36 -0
  189. data/test/serializers/associations_test.rb +295 -0
  190. data/test/serializers/attribute_test.rb +151 -0
  191. data/test/serializers/attributes_test.rb +52 -0
  192. data/test/serializers/caching_configuration_test_isolated.rb +170 -0
  193. data/test/serializers/configuration_test.rb +32 -0
  194. data/test/serializers/fieldset_test.rb +14 -0
  195. data/test/serializers/meta_test.rb +196 -0
  196. data/test/serializers/options_test.rb +21 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/root_test.rb +21 -0
  199. data/test/serializers/serialization_test.rb +55 -0
  200. data/test/serializers/serializer_for_test.rb +134 -0
  201. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  202. data/test/support/isolated_unit.rb +79 -0
  203. data/test/support/rails5_shims.rb +47 -0
  204. data/test/support/rails_app.rb +45 -0
  205. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  206. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  207. data/test/support/schemas/custom/show.json +7 -0
  208. data/test/support/schemas/hyper_schema.json +93 -0
  209. data/test/support/schemas/render_using_json_api.json +43 -0
  210. data/test/support/schemas/simple_json_pointers.json +10 -0
  211. data/test/support/serialization_testing.rb +53 -0
  212. data/test/test_helper.rb +48 -23
  213. metadata +449 -43
  214. data/DESIGN.textile +0 -586
  215. data/Gemfile.edge +0 -9
  216. data/bench/perf.rb +0 -43
  217. data/cruft.md +0 -19
  218. data/lib/active_model/array_serializer.rb +0 -104
  219. data/lib/active_record/serializer_override.rb +0 -16
  220. data/lib/generators/resource_override.rb +0 -13
  221. data/lib/generators/serializer/USAGE +0 -9
  222. data/lib/generators/serializer/serializer_generator.rb +0 -42
  223. data/lib/generators/serializer/templates/serializer.rb +0 -19
  224. data/test/association_test.rb +0 -592
  225. data/test/caching_test.rb +0 -96
  226. data/test/generators_test.rb +0 -85
  227. data/test/no_serialization_scope_test.rb +0 -34
  228. data/test/serialization_scope_name_test.rb +0 -67
  229. data/test/serialization_test.rb +0 -392
  230. data/test/serializer_support_test.rb +0 -51
  231. data/test/serializer_test.rb +0 -1465
  232. data/test/test_fakes.rb +0 -217
@@ -0,0 +1,485 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+ require 'tempfile'
4
+
5
+ module ActiveModelSerializers
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
9
+ undef_method :cache_key
10
+ end
11
+
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
14
+ undef_method :cache_key
15
+ end
16
+
17
+ ArticleSerializer = Class.new(ActiveModel::Serializer) do
18
+ cache only: [:place], skip_digest: true
19
+ attributes :title
20
+ end
21
+
22
+ InheritedRoleSerializer = Class.new(RoleSerializer) do
23
+ cache key: 'inherited_role', only: [:name, :special_attribute]
24
+ attribute :special_attribute
25
+ end
26
+
27
+ setup do
28
+ cache_store.clear
29
+ @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
30
+ @post = Post.new(title: 'New Post', body: 'Body')
31
+ @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: [])
34
+ @role = Role.new(name: 'Great Author')
35
+ @location = Location.new(lat: '-23.550520', lng: '-46.633309')
36
+ @place = Place.new(name: 'Amazing Place')
37
+ @author.posts = [@post]
38
+ @author.roles = [@role]
39
+ @role.author = @author
40
+ @author.bio = @bio
41
+ @bio.author = @author
42
+ @post.comments = [@comment]
43
+ @post.author = @author
44
+ @comment.post = @post
45
+ @comment.author = @author
46
+ @post.blog = @blog
47
+ @location.place = @place
48
+
49
+ @location_serializer = LocationSerializer.new(@location)
50
+ @bio_serializer = BioSerializer.new(@bio)
51
+ @role_serializer = RoleSerializer.new(@role)
52
+ @post_serializer = PostSerializer.new(@post)
53
+ @author_serializer = AuthorSerializer.new(@author)
54
+ @comment_serializer = CommentSerializer.new(@comment)
55
+ @blog_serializer = BlogSerializer.new(@blog)
56
+ end
57
+
58
+ def test_explicit_cache_store
59
+ default_store = Class.new(ActiveModel::Serializer) do
60
+ cache
61
+ end
62
+ explicit_store = Class.new(ActiveModel::Serializer) do
63
+ cache cache_store: ActiveSupport::Cache::FileStore
64
+ end
65
+
66
+ assert ActiveSupport::Cache::MemoryStore, ActiveModelSerializers.config.cache_store
67
+ assert ActiveSupport::Cache::MemoryStore, default_store.cache_store
68
+ assert ActiveSupport::Cache::FileStore, explicit_store.cache_store
69
+ end
70
+
71
+ def test_inherited_cache_configuration
72
+ inherited_serializer = Class.new(PostSerializer)
73
+
74
+ assert_equal PostSerializer._cache_key, inherited_serializer._cache_key
75
+ assert_equal PostSerializer._cache_options, inherited_serializer._cache_options
76
+ end
77
+
78
+ def test_override_cache_configuration
79
+ inherited_serializer = Class.new(PostSerializer) do
80
+ cache key: 'new-key'
81
+ end
82
+
83
+ assert_equal PostSerializer._cache_key, 'post'
84
+ assert_equal inherited_serializer._cache_key, 'new-key'
85
+ end
86
+
87
+ def test_cache_definition
88
+ assert_equal(cache_store, @post_serializer.class._cache)
89
+ assert_equal(cache_store, @author_serializer.class._cache)
90
+ assert_equal(cache_store, @comment_serializer.class._cache)
91
+ end
92
+
93
+ def test_cache_key_definition
94
+ assert_equal('post', @post_serializer.class._cache_key)
95
+ assert_equal('writer', @author_serializer.class._cache_key)
96
+ assert_equal(nil, @comment_serializer.class._cache_key)
97
+ end
98
+
99
+ def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object
100
+ uncached_author = UncachedAuthor.new(name: 'Joao M. D. Moura')
101
+ uncached_author_serializer = AuthorSerializer.new(uncached_author)
102
+
103
+ 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}"
106
+ assert_equal(uncached_author_serializer.attributes.to_json, cache_store.fetch(key).to_json)
107
+ end
108
+
109
+ def test_default_cache_key_fallback
110
+ render_object_with_cache(@comment)
111
+ key = "#{@comment.cache_key}/#{adapter.cached_name}"
112
+ assert_equal(@comment_serializer.attributes.to_json, cache_store.fetch(key).to_json)
113
+ end
114
+
115
+ def test_error_is_raised_if_cache_key_is_not_defined_on_object_or_passed_as_cache_option
116
+ article = Article.new(title: 'Must Read')
117
+ e = assert_raises ActiveModel::Serializer::UndefinedCacheKey do
118
+ render_object_with_cache(article)
119
+ 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)
121
+ end
122
+
123
+ def test_cache_options_definition
124
+ assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options)
125
+ assert_equal(nil, @blog_serializer.class._cache_options)
126
+ assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options)
127
+ end
128
+
129
+ def test_fragment_cache_definition
130
+ assert_equal([:name], @role_serializer.class._cache_only)
131
+ assert_equal([:content], @bio_serializer.class._cache_except)
132
+ end
133
+
134
+ def test_associations_separately_cache
135
+ cache_store.clear
136
+ assert_equal(nil, cache_store.fetch(@post.cache_key))
137
+ assert_equal(nil, cache_store.fetch(@comment.cache_key))
138
+
139
+ Timecop.freeze(Time.current) do
140
+ render_object_with_cache(@post)
141
+
142
+ key = "#{@post.cache_key}/#{adapter.cached_name}"
143
+ assert_equal(@post_serializer.attributes, cache_store.fetch(key))
144
+ key = "#{@comment.cache_key}/#{adapter.cached_name}"
145
+ assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
146
+ end
147
+ end
148
+
149
+ def test_associations_cache_when_updated
150
+ Timecop.freeze(Time.current) do
151
+ # Generate a new Cache of Post object and each objects related to it.
152
+ render_object_with_cache(@post)
153
+
154
+ # Check if it cached the objects separately
155
+ key = "#{@post.cache_key}/#{adapter.cached_name}"
156
+ assert_equal(@post_serializer.attributes, cache_store.fetch(key))
157
+ key = "#{@comment.cache_key}/#{adapter.cached_name}"
158
+ assert_equal(@comment_serializer.attributes, cache_store.fetch(key))
159
+
160
+ # Simulating update on comments relationship with Post
161
+ new_comment = Comment.new(id: 2567, body: 'ZOMG A NEW COMMENT')
162
+ new_comment_serializer = CommentSerializer.new(new_comment)
163
+ @post.comments = [new_comment]
164
+
165
+ # Ask for the serialized object
166
+ render_object_with_cache(@post)
167
+
168
+ # Check if the the new comment was cached
169
+ key = "#{new_comment.cache_key}/#{adapter.cached_name}"
170
+ assert_equal(new_comment_serializer.attributes, cache_store.fetch(key))
171
+ key = "#{@post.cache_key}/#{adapter.cached_name}"
172
+ assert_equal(@post_serializer.attributes, cache_store.fetch(key))
173
+ end
174
+ end
175
+
176
+ def test_fragment_fetch_with_virtual_associations
177
+ expected_result = {
178
+ id: @location.id,
179
+ lat: @location.lat,
180
+ lng: @location.lng,
181
+ place: 'Nowhere'
182
+ }
183
+
184
+ hash = render_object_with_cache(@location)
185
+
186
+ assert_equal(hash, expected_result)
187
+ key = "#{@location.cache_key}/#{adapter.cached_name}"
188
+ assert_equal({ place: 'Nowhere' }, cache_store.fetch(key))
189
+ end
190
+
191
+ def test_fragment_cache_with_inheritance
192
+ inherited = render_object_with_cache(@role, serializer: InheritedRoleSerializer)
193
+ base = render_object_with_cache(@role)
194
+
195
+ assert_includes(inherited.keys, :special_attribute)
196
+ refute_includes(base.keys, :special_attribute)
197
+ end
198
+
199
+ def test_uses_adapter_in_cache_key
200
+ render_object_with_cache(@post)
201
+ key = "#{@post.cache_key}/#{adapter.class.to_s.demodulize.underscore}"
202
+ assert_equal(@post_serializer.attributes, cache_store.fetch(key))
203
+ end
204
+
205
+ # Based on original failing test by @kevintyll
206
+ # rubocop:disable Metrics/AbcSize
207
+ def test_a_serializer_rendered_by_two_adapter_returns_differently_cached_attributes
208
+ Object.const_set(:Alert, Class.new(ActiveModelSerializers::Model) do
209
+ attr_accessor :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
210
+ end)
211
+ Object.const_set(:UncachedAlertSerializer, Class.new(ActiveModel::Serializer) do
212
+ attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at
213
+ end)
214
+ Object.const_set(:AlertSerializer, Class.new(UncachedAlertSerializer) do
215
+ cache
216
+ end)
217
+
218
+ alert = Alert.new(
219
+ id: 1,
220
+ status: 'fail',
221
+ resource: 'resource-1',
222
+ started_at: Time.new(2016, 3, 31, 21, 36, 35, 0),
223
+ ended_at: nil,
224
+ updated_at: Time.new(2016, 3, 31, 21, 27, 35, 0),
225
+ created_at: Time.new(2016, 3, 31, 21, 37, 35, 0)
226
+ )
227
+
228
+ expected_cached_attributes = {
229
+ id: 1,
230
+ status: 'fail',
231
+ resource: 'resource-1',
232
+ started_at: alert.started_at,
233
+ ended_at: nil,
234
+ updated_at: alert.updated_at,
235
+ created_at: alert.created_at
236
+ }
237
+ expected_cached_jsonapi_attributes = {
238
+ id: '1',
239
+ type: 'alerts',
240
+ attributes: {
241
+ status: 'fail',
242
+ resource: 'resource-1',
243
+ started_at: alert.started_at,
244
+ ended_at: nil,
245
+ updated_at: alert.updated_at,
246
+ created_at: alert.created_at
247
+ }
248
+ }
249
+
250
+ # Assert attributes are serialized correctly
251
+ serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :attributes)
252
+ attributes_serialization = serializable_alert.as_json
253
+ assert_equal expected_cached_attributes, alert.attributes
254
+ assert_equal alert.attributes, attributes_serialization
255
+ attributes_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
256
+ assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key)
257
+
258
+ serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :json_api)
259
+ jsonapi_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter)
260
+ # Assert cache keys differ
261
+ refute_equal attributes_cache_key, jsonapi_cache_key
262
+ # Assert (cached) serializations differ
263
+ jsonapi_serialization = serializable_alert.as_json
264
+ assert_equal alert.status, jsonapi_serialization.fetch(:data).fetch(:attributes).fetch(:status)
265
+ serializable_alert = serializable(alert, serializer: UncachedAlertSerializer, adapter: :json_api)
266
+ assert_equal serializable_alert.as_json, jsonapi_serialization
267
+
268
+ cached_serialization = cache_store.fetch(jsonapi_cache_key)
269
+ assert_equal expected_cached_jsonapi_attributes, cached_serialization
270
+ ensure
271
+ Object.send(:remove_const, :Alert)
272
+ Object.send(:remove_const, :AlertSerializer)
273
+ Object.send(:remove_const, :UncachedAlertSerializer)
274
+ end
275
+ # rubocop:enable Metrics/AbcSize
276
+
277
+ def test_uses_file_digest_in_cache_key
278
+ render_object_with_cache(@blog)
279
+ key = "#{@blog.cache_key}/#{adapter.cached_name}/#{::Model::FILE_DIGEST}"
280
+ assert_equal(@blog_serializer.attributes, cache_store.fetch(key))
281
+ end
282
+
283
+ def test_cache_digest_definition
284
+ assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
285
+ end
286
+
287
+ def test_object_cache_keys
288
+ serializable = ActiveModelSerializers::SerializableResource.new([@comment, @comment])
289
+ include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*')
290
+
291
+ actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_tree)
292
+
293
+ 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+} }
297
+ end
298
+
299
+ def test_cached_attributes
300
+ serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
301
+
302
+ Timecop.freeze(Time.current) do
303
+ render_object_with_cache(@comment)
304
+
305
+ attributes = Adapter::Attributes.new(serializer)
306
+ attributes.send(:cache_attributes)
307
+ cached_attributes = attributes.instance_variable_get(:@cached_attributes)
308
+
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
311
+
312
+ writer = @comment.post.blog.writer
313
+ writer_cache_key = writer.cache_key
314
+
315
+ assert_equal cached_attributes["#{writer_cache_key}/#{attributes.cached_name}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
316
+ end
317
+ end
318
+
319
+ def test_serializer_file_path_on_nix
320
+ path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
321
+ caller_line = "#{path}:1:in `<top (required)>'"
322
+ assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
323
+ end
324
+
325
+ def test_serializer_file_path_on_windows
326
+ path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
327
+ caller_line = "#{path}:1:in `<top (required)>'"
328
+ assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
329
+ end
330
+
331
+ def test_serializer_file_path_with_space
332
+ path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb'
333
+ caller_line = "#{path}:1:in `<top (required)>'"
334
+ assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
335
+ end
336
+
337
+ def test_serializer_file_path_with_submatch
338
+ # The submatch in the path ensures we're using a correctly greedy regexp.
339
+ path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb'
340
+ caller_line = "#{path}:1:in `<top (required)>'"
341
+ assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
342
+ end
343
+
344
+ def test_digest_caller_file
345
+ contents = "puts 'AMS rocks'!"
346
+ dir = Dir.mktmpdir('space char')
347
+ file = Tempfile.new('some_ruby.rb', dir)
348
+ file.write(contents)
349
+ path = file.path
350
+ caller_line = "#{path}:1:in `<top (required)>'"
351
+ file.close
352
+ assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents)
353
+ ensure
354
+ file.unlink
355
+ FileUtils.remove_entry dir
356
+ end
357
+
358
+ def test_warn_on_serializer_not_defined_in_file
359
+ called = false
360
+ serializer = Class.new(ActiveModel::Serializer)
361
+ assert_output(nil, /_cache_digest/) do
362
+ serializer.digest_caller_file('')
363
+ called = true
364
+ end
365
+ assert called
366
+ end
367
+
368
+ def test_cached_false_without_cache_store
369
+ cached_serializer = build_cached_serializer do |serializer|
370
+ serializer._cache = nil
371
+ end
372
+ refute cached_serializer.class.cache_enabled?
373
+ end
374
+
375
+ def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except
376
+ cached_serializer = build_cached_serializer do |serializer|
377
+ serializer._cache = Object
378
+ end
379
+ assert cached_serializer.class.cache_enabled?
380
+ end
381
+
382
+ def test_cached_false_with_cache_store_and_with_cache_only
383
+ cached_serializer = build_cached_serializer do |serializer|
384
+ serializer._cache = Object
385
+ serializer._cache_only = [:name]
386
+ end
387
+ refute cached_serializer.class.cache_enabled?
388
+ end
389
+
390
+ def test_cached_false_with_cache_store_and_with_cache_except
391
+ cached_serializer = build_cached_serializer do |serializer|
392
+ serializer._cache = Object
393
+ serializer._cache_except = [:content]
394
+ end
395
+ refute cached_serializer.class.cache_enabled?
396
+ end
397
+
398
+ def test_fragment_cached_false_without_cache_store
399
+ cached_serializer = build_cached_serializer do |serializer|
400
+ serializer._cache = nil
401
+ serializer._cache_only = [:name]
402
+ end
403
+ refute cached_serializer.class.fragment_cache_enabled?
404
+ end
405
+
406
+ def test_fragment_cached_true_with_cache_store_and_cache_only
407
+ cached_serializer = build_cached_serializer do |serializer|
408
+ serializer._cache = Object
409
+ serializer._cache_only = [:name]
410
+ end
411
+ assert cached_serializer.class.fragment_cache_enabled?
412
+ end
413
+
414
+ def test_fragment_cached_true_with_cache_store_and_cache_except
415
+ cached_serializer = build_cached_serializer do |serializer|
416
+ serializer._cache = Object
417
+ serializer._cache_except = [:content]
418
+ end
419
+ assert cached_serializer.class.fragment_cache_enabled?
420
+ end
421
+
422
+ def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only
423
+ cached_serializer = build_cached_serializer do |serializer|
424
+ serializer._cache = Object
425
+ serializer._cache_except = [:content]
426
+ serializer._cache_only = [:name]
427
+ end
428
+ refute cached_serializer.class.fragment_cache_enabled?
429
+ end
430
+
431
+ 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))
437
+
438
+ expected_result = {
439
+ id: @role.id,
440
+ description: @role.description,
441
+ slug: "#{@role.name}-#{@role.id}",
442
+ name: @role.name
443
+ }
444
+ assert_equal(@role_hash, expected_result)
445
+ end
446
+
447
+ def test_fragment_fetch_with_namespaced_object
448
+ @spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
449
+ @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
450
+ @spam_hash = @spam_serializer.fetch_fragment_cache(ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer))
451
+ expected_result = {
452
+ id: @spam.id
453
+ }
454
+ assert_equal(@spam_hash, expected_result)
455
+ end
456
+
457
+ private
458
+
459
+ def cache_store
460
+ ActiveModelSerializers.config.cache_store
461
+ end
462
+
463
+ def build_cached_serializer
464
+ serializer = Class.new(ActiveModel::Serializer)
465
+ serializer._cache_key = nil
466
+ serializer._cache_options = nil
467
+ yield serializer if block_given?
468
+ serializer.new(Object)
469
+ end
470
+
471
+ def render_object_with_cache(obj, options = {})
472
+ @serializable_resource = serializable(obj, options)
473
+ @serializable_resource.serializable_hash
474
+ end
475
+
476
+ def adapter
477
+ @serializable_resource.adapter
478
+ end
479
+
480
+ def cached_serialization(serializer)
481
+ cache_key = serializer.cache_key(adapter)
482
+ cache_store.fetch(cache_key)
483
+ end
484
+ end
485
+ end
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class CollectionSerializerTest < ActiveSupport::TestCase
6
+ MessagesSerializer = Class.new(ActiveModel::Serializer) do
7
+ type 'messages'
8
+ end
9
+
10
+ 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 })
15
+ end
16
+
17
+ def collection_serializer
18
+ CollectionSerializer
19
+ end
20
+
21
+ def build_named_collection(*resource)
22
+ resource.define_singleton_method(:name) { 'MeResource' }
23
+ resource
24
+ end
25
+
26
+ def test_has_object_reader_serializer_interface
27
+ assert_equal @serializer.object, @resource
28
+ end
29
+
30
+ def test_respond_to_each
31
+ assert_respond_to @serializer, :each
32
+ end
33
+
34
+ def test_each_object_should_be_serialized_with_appropriate_serializer
35
+ serializers = @serializer.to_a
36
+
37
+ assert_kind_of CommentSerializer, serializers.first
38
+ assert_kind_of Comment, serializers.first.object
39
+
40
+ assert_kind_of PostSerializer, serializers.last
41
+ assert_kind_of Post, serializers.last.object
42
+
43
+ assert_equal :options, serializers.last.custom_options[:some]
44
+ end
45
+
46
+ def test_serializer_option_not_passed_to_each_serializer
47
+ serializers = collection_serializer.new([@post], { serializer: PostSerializer }).to_a
48
+
49
+ refute serializers.first.custom_options.key?(:serializer)
50
+ end
51
+
52
+ def test_root_default
53
+ @serializer = collection_serializer.new([@comment, @post])
54
+ assert_nil @serializer.root
55
+ end
56
+
57
+ def test_root
58
+ expected = 'custom_root'
59
+ @serializer = collection_serializer.new([@comment, @post], root: expected)
60
+ assert_equal expected, @serializer.root
61
+ end
62
+
63
+ def test_root_with_no_serializers
64
+ expected = 'custom_root'
65
+ @serializer = collection_serializer.new([], root: expected)
66
+ assert_equal expected, @serializer.root
67
+ end
68
+
69
+ def test_json_key_with_resource_with_serializer
70
+ singular_key = @serializer.send(:serializers).first.json_key
71
+ assert_equal singular_key.pluralize, @serializer.json_key
72
+ end
73
+
74
+ def test_json_key_with_resource_with_name_and_no_serializers
75
+ serializer = collection_serializer.new(build_named_collection)
76
+ assert_equal 'me_resources', serializer.json_key
77
+ end
78
+
79
+ def test_json_key_with_resource_with_nil_name_and_no_serializers
80
+ resource = []
81
+ resource.define_singleton_method(:name) { nil }
82
+ serializer = collection_serializer.new(resource)
83
+ assert_nil serializer.json_key
84
+ end
85
+
86
+ def test_json_key_with_resource_without_name_and_no_serializers
87
+ serializer = collection_serializer.new([])
88
+ assert_nil serializer.json_key
89
+ end
90
+
91
+ def test_json_key_with_empty_resources_with_serializer
92
+ resource = []
93
+ serializer = collection_serializer.new(resource, serializer: MessagesSerializer)
94
+ assert_equal 'messages', serializer.json_key
95
+ end
96
+
97
+ def test_json_key_with_root
98
+ expected = 'custom_root'
99
+ serializer = collection_serializer.new(@resource, root: expected)
100
+ assert_equal expected, serializer.json_key
101
+ end
102
+
103
+ def test_json_key_with_root_and_no_serializers
104
+ expected = 'custom_root'
105
+ serializer = collection_serializer.new(build_named_collection, root: expected)
106
+ assert_equal expected, serializer.json_key
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,78 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
4
+ ActiveRecord::Schema.define do
5
+ self.verbose = false
6
+ create_table :posts, force: true do |t|
7
+ t.string :title
8
+ t.text :body
9
+ t.references :author
10
+ t.timestamps null: false
11
+ end
12
+ create_table :authors, force: true do |t|
13
+ t.string :name
14
+ t.timestamps null: false
15
+ end
16
+ create_table :comments, force: true do |t|
17
+ t.text :contents
18
+ t.references :author
19
+ t.references :post
20
+ t.timestamp null: false
21
+ end
22
+ create_table :employees, force: true do |t|
23
+ t.string :name
24
+ t.string :email
25
+ t.timestamp null: false
26
+ end
27
+ create_table :object_tags, force: true do |t|
28
+ t.string :poly_tag_id
29
+ t.string :taggable_type
30
+ t.string :taggable_id
31
+ t.timestamp null: false
32
+ end
33
+ create_table :poly_tags, force: true do |t|
34
+ t.string :phrase
35
+ t.timestamp null: false
36
+ end
37
+ create_table :pictures, force: true do |t|
38
+ t.string :title
39
+ t.string :imageable_type
40
+ t.string :imageable_id
41
+ t.timestamp null: false
42
+ end
43
+ end
44
+
45
+ module ARModels
46
+ class Post < ActiveRecord::Base
47
+ has_many :comments
48
+ belongs_to :author
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
+ class PostSerializer < ActiveModel::Serializer
61
+ attributes :id, :title, :body
62
+
63
+ has_many :comments
64
+ belongs_to :author
65
+ end
66
+
67
+ class CommentSerializer < ActiveModel::Serializer
68
+ attributes :id, :contents
69
+
70
+ belongs_to :author
71
+ end
72
+
73
+ class AuthorSerializer < ActiveModel::Serializer
74
+ attributes :id, :name
75
+
76
+ has_many :posts
77
+ end
78
+ end