active_model_serializers 0.8.3 → 0.10.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +5 -5
  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 +105 -0
  6. data/.simplecov +110 -0
  7. data/.travis.yml +50 -24
  8. data/CHANGELOG.md +650 -6
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +69 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +195 -545
  14. data/Rakefile +64 -8
  15. data/active_model_serializers.gemspec +62 -23
  16. data/appveyor.yml +28 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/rubocop +38 -0
  20. data/bin/serve_benchmark +39 -0
  21. data/docs/README.md +41 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +269 -0
  24. data/docs/general/caching.md +58 -0
  25. data/docs/general/configuration_options.md +185 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/fields.md +31 -0
  28. data/docs/general/getting_started.md +133 -0
  29. data/docs/general/instrumentation.md +40 -0
  30. data/docs/general/key_transforms.md +40 -0
  31. data/docs/general/logging.md +21 -0
  32. data/docs/general/rendering.md +293 -0
  33. data/docs/general/serializers.md +495 -0
  34. data/docs/how-open-source-maintained.jpg +0 -0
  35. data/docs/howto/add_pagination_links.md +138 -0
  36. data/docs/howto/add_relationship_links.md +140 -0
  37. data/docs/howto/add_root_key.md +62 -0
  38. data/docs/howto/grape_integration.md +42 -0
  39. data/docs/howto/outside_controller_use.md +66 -0
  40. data/docs/howto/passing_arbitrary_options.md +27 -0
  41. data/docs/howto/serialize_poro.md +73 -0
  42. data/docs/howto/test.md +154 -0
  43. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  44. data/docs/integrations/ember-and-json-api.md +147 -0
  45. data/docs/integrations/grape.md +19 -0
  46. data/docs/jsonapi/errors.md +56 -0
  47. data/docs/jsonapi/schema/schema.json +366 -0
  48. data/docs/jsonapi/schema.md +151 -0
  49. data/docs/rfcs/0000-namespace.md +106 -0
  50. data/docs/rfcs/template.md +15 -0
  51. data/lib/action_controller/serialization.rb +43 -38
  52. data/lib/active_model/serializable_resource.rb +11 -0
  53. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  54. data/lib/active_model/serializer/adapter/base.rb +18 -0
  55. data/lib/active_model/serializer/adapter/json.rb +15 -0
  56. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  57. data/lib/active_model/serializer/adapter/null.rb +15 -0
  58. data/lib/active_model/serializer/adapter.rb +24 -0
  59. data/lib/active_model/serializer/array_serializer.rb +12 -0
  60. data/lib/active_model/serializer/association.rb +71 -0
  61. data/lib/active_model/serializer/attribute.rb +25 -0
  62. data/lib/active_model/serializer/belongs_to_reflection.rb +11 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +88 -0
  64. data/lib/active_model/serializer/concerns/caching.rb +300 -0
  65. data/lib/active_model/serializer/error_serializer.rb +14 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +32 -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 +7 -0
  71. data/lib/active_model/serializer/lazy_association.rb +96 -0
  72. data/lib/active_model/serializer/link.rb +21 -0
  73. data/lib/active_model/serializer/lint.rb +150 -0
  74. data/lib/active_model/serializer/null.rb +17 -0
  75. data/lib/active_model/serializer/reflection.rb +210 -0
  76. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  77. data/lib/active_model/serializer.rb +343 -442
  78. data/lib/active_model_serializers/adapter/attributes.rb +13 -0
  79. data/lib/active_model_serializers/adapter/base.rb +83 -0
  80. data/lib/active_model_serializers/adapter/json.rb +21 -0
  81. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  82. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  83. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  84. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  85. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  86. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +88 -0
  87. data/lib/active_model_serializers/adapter/json_api/relationship.rb +104 -0
  88. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +66 -0
  89. data/lib/active_model_serializers/adapter/json_api.rb +533 -0
  90. data/lib/active_model_serializers/adapter/null.rb +9 -0
  91. data/lib/active_model_serializers/adapter.rb +98 -0
  92. data/lib/active_model_serializers/callbacks.rb +55 -0
  93. data/lib/active_model_serializers/deprecate.rb +54 -0
  94. data/lib/active_model_serializers/deserialization.rb +15 -0
  95. data/lib/active_model_serializers/json_pointer.rb +14 -0
  96. data/lib/active_model_serializers/logging.rb +122 -0
  97. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  98. data/lib/active_model_serializers/model.rb +130 -0
  99. data/lib/active_model_serializers/railtie.rb +50 -0
  100. data/lib/active_model_serializers/register_jsonapi_renderer.rb +78 -0
  101. data/lib/active_model_serializers/serializable_resource.rb +82 -0
  102. data/lib/active_model_serializers/serialization_context.rb +39 -0
  103. data/lib/active_model_serializers/test/schema.rb +138 -0
  104. data/lib/active_model_serializers/test/serializer.rb +125 -0
  105. data/lib/active_model_serializers/test.rb +7 -0
  106. data/lib/active_model_serializers.rb +47 -81
  107. data/lib/generators/rails/USAGE +6 -0
  108. data/lib/generators/rails/resource_override.rb +10 -0
  109. data/lib/generators/rails/serializer_generator.rb +36 -0
  110. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  111. data/lib/grape/active_model_serializers.rb +16 -0
  112. data/lib/grape/formatters/active_model_serializers.rb +32 -0
  113. data/lib/grape/helpers/active_model_serializers.rb +17 -0
  114. data/lib/tasks/rubocop.rake +53 -0
  115. data/test/action_controller/adapter_selector_test.rb +62 -0
  116. data/test/action_controller/explicit_serializer_test.rb +135 -0
  117. data/test/action_controller/json/include_test.rb +246 -0
  118. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  119. data/test/action_controller/json_api/errors_test.rb +40 -0
  120. data/test/action_controller/json_api/fields_test.rb +66 -0
  121. data/test/action_controller/json_api/linked_test.rb +202 -0
  122. data/test/action_controller/json_api/pagination_test.rb +124 -0
  123. data/test/action_controller/json_api/transform_test.rb +189 -0
  124. data/test/action_controller/lookup_proc_test.rb +49 -0
  125. data/test/action_controller/namespace_lookup_test.rb +232 -0
  126. data/test/action_controller/serialization_scope_name_test.rb +235 -0
  127. data/test/action_controller/serialization_test.rb +478 -0
  128. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  129. data/test/active_model_serializers/json_pointer_test.rb +22 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +142 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +68 -0
  133. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  134. data/test/active_model_serializers/serialization_context_test_isolated.rb +71 -0
  135. data/test/active_model_serializers/test/schema_test.rb +131 -0
  136. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  137. data/test/active_record_test.rb +9 -0
  138. data/test/adapter/attributes_test.rb +40 -0
  139. data/test/adapter/deprecation_test.rb +100 -0
  140. data/test/adapter/json/belongs_to_test.rb +45 -0
  141. data/test/adapter/json/collection_test.rb +104 -0
  142. data/test/adapter/json/has_many_test.rb +53 -0
  143. data/test/adapter/json/transform_test.rb +93 -0
  144. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  145. data/test/adapter/json_api/collection_test.rb +96 -0
  146. data/test/adapter/json_api/errors_test.rb +76 -0
  147. data/test/adapter/json_api/fields_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  149. data/test/adapter/json_api/has_many_test.rb +173 -0
  150. data/test/adapter/json_api/has_one_test.rb +80 -0
  151. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  152. data/test/adapter/json_api/json_api_test.rb +33 -0
  153. data/test/adapter/json_api/linked_test.rb +413 -0
  154. data/test/adapter/json_api/links_test.rb +110 -0
  155. data/test/adapter/json_api/pagination_links_test.rb +206 -0
  156. data/test/adapter/json_api/parse_test.rb +137 -0
  157. data/test/adapter/json_api/relationship_test.rb +397 -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 +512 -0
  161. data/test/adapter/json_api/type_test.rb +193 -0
  162. data/test/adapter/json_test.rb +46 -0
  163. data/test/adapter/null_test.rb +22 -0
  164. data/test/adapter/polymorphic_test.rb +218 -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_active_record.rb +81 -0
  170. data/test/benchmark/bm_adapter.rb +38 -0
  171. data/test/benchmark/bm_caching.rb +119 -0
  172. data/test/benchmark/bm_lookup_chain.rb +83 -0
  173. data/test/benchmark/bm_transform.rb +45 -0
  174. data/test/benchmark/config.ru +3 -0
  175. data/test/benchmark/controllers.rb +83 -0
  176. data/test/benchmark/fixtures.rb +219 -0
  177. data/test/cache_test.rb +651 -0
  178. data/test/collection_serializer_test.rb +127 -0
  179. data/test/fixtures/active_record.rb +113 -0
  180. data/test/fixtures/poro.rb +225 -0
  181. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  182. data/test/generators/serializer_generator_test.rb +75 -0
  183. data/test/grape_test.rb +196 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +20 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +79 -0
  188. data/test/serializers/association_macros_test.rb +37 -0
  189. data/test/serializers/associations_test.rb +518 -0
  190. data/test/serializers/attribute_test.rb +153 -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 +202 -0
  196. data/test/serializers/options_test.rb +32 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/reflection_test.rb +479 -0
  199. data/test/serializers/root_test.rb +21 -0
  200. data/test/serializers/serialization_test.rb +55 -0
  201. data/test/serializers/serializer_for_test.rb +136 -0
  202. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  203. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  204. data/test/support/isolated_unit.rb +84 -0
  205. data/test/support/rails5_shims.rb +53 -0
  206. data/test/support/rails_app.rb +38 -0
  207. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  208. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  209. data/test/support/schemas/custom/show.json +7 -0
  210. data/test/support/schemas/hyper_schema.json +93 -0
  211. data/test/support/schemas/render_using_json_api.json +43 -0
  212. data/test/support/schemas/simple_json_pointers.json +10 -0
  213. data/test/support/serialization_testing.rb +79 -0
  214. data/test/test_helper.rb +59 -21
  215. metadata +529 -43
  216. data/DESIGN.textile +0 -586
  217. data/Gemfile.edge +0 -9
  218. data/bench/perf.rb +0 -43
  219. data/cruft.md +0 -19
  220. data/lib/active_model/array_serializer.rb +0 -104
  221. data/lib/active_model/serializer/associations.rb +0 -233
  222. data/lib/active_record/serializer_override.rb +0 -16
  223. data/lib/generators/resource_override.rb +0 -13
  224. data/lib/generators/serializer/USAGE +0 -9
  225. data/lib/generators/serializer/serializer_generator.rb +0 -42
  226. data/lib/generators/serializer/templates/serializer.rb +0 -19
  227. data/test/association_test.rb +0 -592
  228. data/test/caching_test.rb +0 -96
  229. data/test/generators_test.rb +0 -85
  230. data/test/no_serialization_scope_test.rb +0 -34
  231. data/test/serialization_scope_name_test.rb +0 -67
  232. data/test/serialization_test.rb +0 -392
  233. data/test/serializer_support_test.rb +0 -51
  234. data/test/serializer_test.rb +0 -1465
  235. data/test/test_fakes.rb +0 -217
@@ -0,0 +1,518 @@
1
+ require 'test_helper'
2
+ module ActiveModel
3
+ class Serializer
4
+ class AssociationsTest < ActiveSupport::TestCase
5
+ class ModelWithoutSerializer < ::Model
6
+ attributes :id, :name
7
+ end
8
+
9
+ def setup
10
+ @author = Author.new(name: 'Steve K.')
11
+ @author.bio = nil
12
+ @author.roles = []
13
+ @blog = Blog.new(name: 'AMS Blog')
14
+ @post = Post.new(title: 'New Post', body: 'Body')
15
+ @tag = ModelWithoutSerializer.new(id: 'tagid', name: '#hashtagged')
16
+ @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
17
+ @post.comments = [@comment]
18
+ @post.tags = [@tag]
19
+ @post.blog = @blog
20
+ @comment.post = @post
21
+ @comment.author = nil
22
+ @post.author = @author
23
+ @author.posts = [@post]
24
+
25
+ @post_serializer = PostSerializer.new(@post, custom_options: true)
26
+ @author_serializer = AuthorSerializer.new(@author)
27
+ @comment_serializer = CommentSerializer.new(@comment)
28
+ end
29
+
30
+ def test_has_many_and_has_one
31
+ @author_serializer.associations.each do |association|
32
+ key = association.key
33
+ serializer = association.lazy_association.serializer
34
+
35
+ case key
36
+ when :posts
37
+ assert_equal true, association.include_data?
38
+ assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
39
+ when :bio
40
+ assert_equal true, association.include_data?
41
+ assert_nil serializer
42
+ when :roles
43
+ assert_equal true, association.include_data?
44
+ assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
45
+ else
46
+ flunk "Unknown association: #{key}"
47
+ end
48
+ end
49
+ end
50
+
51
+ def test_has_many_with_no_serializer
52
+ post_serializer_class = Class.new(ActiveModel::Serializer) do
53
+ attributes :id
54
+ has_many :tags
55
+ end
56
+ post_serializer_class.new(@post).associations.each do |association|
57
+ key = association.key
58
+ serializer = association.lazy_association.serializer
59
+
60
+ assert_equal :tags, key
61
+ assert_nil serializer
62
+ assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json
63
+ end
64
+ end
65
+
66
+ def test_serializer_options_are_passed_into_associations_serializers
67
+ association = @post_serializer
68
+ .associations
69
+ .detect { |assoc| assoc.key == :comments }
70
+
71
+ comment_serializer = association.lazy_association.serializer.first
72
+ class << comment_serializer
73
+ def custom_options
74
+ instance_options
75
+ end
76
+ end
77
+ assert comment_serializer.custom_options.fetch(:custom_options)
78
+ end
79
+
80
+ def test_belongs_to
81
+ @comment_serializer.associations.each do |association|
82
+ key = association.key
83
+ serializer = association.lazy_association.serializer
84
+
85
+ case key
86
+ when :post
87
+ assert_kind_of(PostSerializer, serializer)
88
+ when :author
89
+ assert_nil serializer
90
+ else
91
+ flunk "Unknown association: #{key}"
92
+ end
93
+
94
+ assert_equal true, association.include_data?
95
+ end
96
+ end
97
+
98
+ def test_belongs_to_with_custom_method
99
+ assert(
100
+ @post_serializer.associations.any? do |association|
101
+ association.key == :blog
102
+ end
103
+ )
104
+ end
105
+
106
+ def test_associations_inheritance
107
+ inherited_klass = Class.new(PostSerializer)
108
+
109
+ assert_equal(PostSerializer._reflections, inherited_klass._reflections)
110
+ end
111
+
112
+ def test_associations_inheritance_with_new_association
113
+ inherited_klass = Class.new(PostSerializer) do
114
+ has_many :top_comments, serializer: CommentSerializer
115
+ end
116
+
117
+ assert(
118
+ PostSerializer._reflections.values.all? do |reflection|
119
+ inherited_klass._reflections.values.include?(reflection)
120
+ end
121
+ )
122
+
123
+ assert(
124
+ inherited_klass._reflections.values.any? do |reflection|
125
+ reflection.name == :top_comments
126
+ end
127
+ )
128
+ end
129
+
130
+ def test_associations_custom_keys
131
+ serializer = PostWithCustomKeysSerializer.new(@post)
132
+
133
+ expected_association_keys = serializer.associations.map(&:key)
134
+
135
+ assert expected_association_keys.include? :reviews
136
+ assert expected_association_keys.include? :writer
137
+ assert expected_association_keys.include? :site
138
+ end
139
+
140
+ class BelongsToBlogModel < ::Model
141
+ attributes :id, :title
142
+ associations :blog
143
+ end
144
+ class BelongsToBlogModelSerializer < ActiveModel::Serializer
145
+ type :posts
146
+ belongs_to :blog
147
+ end
148
+
149
+ def test_belongs_to_doesnt_load_record
150
+ attributes = { id: 1, title: 'Belongs to Blog', blog: Blog.new(id: 5) }
151
+ post = BelongsToBlogModel.new(attributes)
152
+ class << post
153
+ def blog
154
+ fail 'should use blog_id'
155
+ end
156
+
157
+ def blog_id
158
+ 5
159
+ end
160
+ end
161
+
162
+ actual =
163
+ begin
164
+ original_option = BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship
165
+ BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true
166
+ serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json
167
+ ensure
168
+ BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option
169
+ end
170
+ expected = { data: { id: '1', type: 'posts', relationships: { blog: { data: { id: '5', type: 'blogs' } } } } }
171
+
172
+ assert_equal expected, actual
173
+ end
174
+
175
+ class ExternalBlog < Blog
176
+ attributes :external_id
177
+ end
178
+ class BelongsToExternalBlogModel < ::Model
179
+ attributes :id, :title, :external_blog_id
180
+ associations :external_blog
181
+ end
182
+ class BelongsToExternalBlogModelSerializer < ActiveModel::Serializer
183
+ type :posts
184
+ belongs_to :external_blog
185
+
186
+ def external_blog_id
187
+ object.external_blog.external_id
188
+ end
189
+ end
190
+
191
+ def test_belongs_to_allows_id_overwriting
192
+ attributes = {
193
+ id: 1,
194
+ title: 'Title',
195
+ external_blog: ExternalBlog.new(id: 5, external_id: 6)
196
+ }
197
+ post = BelongsToExternalBlogModel.new(attributes)
198
+
199
+ actual =
200
+ begin
201
+ original_option = BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship
202
+ BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true
203
+ serializable(post, adapter: :json_api, serializer: BelongsToExternalBlogModelSerializer).as_json
204
+ ensure
205
+ BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option
206
+ end
207
+ expected = { data: { id: '1', type: 'posts', relationships: { :'external-blog' => { data: { id: '6', type: 'external-blogs' } } } } }
208
+
209
+ assert_equal expected, actual
210
+ end
211
+
212
+ class InlineAssociationTestPostSerializer < ActiveModel::Serializer
213
+ has_many :comments
214
+ has_many :comments, key: :last_comments do
215
+ object.comments.last(1)
216
+ end
217
+ end
218
+
219
+ def test_virtual_attribute_block
220
+ comment1 = ::ARModels::Comment.create!(contents: 'first comment')
221
+ comment2 = ::ARModels::Comment.create!(contents: 'last comment')
222
+ post = ::ARModels::Post.create!(
223
+ title: 'inline association test',
224
+ body: 'etc',
225
+ comments: [comment1, comment2]
226
+ )
227
+ actual = serializable(post, adapter: :attributes, serializer: InlineAssociationTestPostSerializer).as_json
228
+ expected = {
229
+ comments: [
230
+ { id: 1, contents: 'first comment' },
231
+ { id: 2, contents: 'last comment' }
232
+ ],
233
+ last_comments: [
234
+ { id: 2, contents: 'last comment' }
235
+ ]
236
+ }
237
+
238
+ assert_equal expected, actual
239
+ ensure
240
+ ::ARModels::Post.delete_all
241
+ ::ARModels::Comment.delete_all
242
+ end
243
+
244
+ class NamespacedResourcesTest < ActiveSupport::TestCase
245
+ class ResourceNamespace
246
+ class Post < ::Model
247
+ associations :comments, :author, :description
248
+ end
249
+ class Comment < ::Model; end
250
+ class Author < ::Model; end
251
+ class Description < ::Model; end
252
+ class PostSerializer < ActiveModel::Serializer
253
+ has_many :comments
254
+ belongs_to :author
255
+ has_one :description
256
+ end
257
+ class CommentSerializer < ActiveModel::Serializer; end
258
+ class AuthorSerializer < ActiveModel::Serializer; end
259
+ class DescriptionSerializer < ActiveModel::Serializer; end
260
+ end
261
+
262
+ def setup
263
+ @comment = ResourceNamespace::Comment.new
264
+ @author = ResourceNamespace::Author.new
265
+ @description = ResourceNamespace::Description.new
266
+ @post = ResourceNamespace::Post.new(comments: [@comment],
267
+ author: @author,
268
+ description: @description)
269
+ @post_serializer = ResourceNamespace::PostSerializer.new(@post)
270
+ end
271
+
272
+ def test_associations_namespaced_resources
273
+ @post_serializer.associations.each do |association|
274
+ case association.key
275
+ when :comments
276
+ assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
277
+ when :author
278
+ assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
279
+ when :description
280
+ assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
281
+ else
282
+ flunk "Unknown association: #{key}"
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ class AssociationsNamespacedSerializersTest < ActiveSupport::TestCase
289
+ class Post < ::Model
290
+ associations :comments, :author, :description
291
+
292
+ def latest_comments
293
+ comments[0..3]
294
+ end
295
+ end
296
+ class Comment < ::Model; end
297
+ class Author < ::Model; end
298
+ class Description < ::Model; end
299
+
300
+ class ResourceNamespace
301
+ class PostSerializer < ActiveModel::Serializer
302
+ has_many :comments, namespace: ResourceNamespace
303
+ has_many :latest_comments, namespace: ResourceNamespace
304
+ belongs_to :author, namespace: ResourceNamespace
305
+ has_one :description, namespace: ResourceNamespace
306
+ end
307
+ class CommentSerializer < ActiveModel::Serializer; end
308
+ class AuthorSerializer < ActiveModel::Serializer; end
309
+ class DescriptionSerializer < ActiveModel::Serializer; end
310
+ end
311
+
312
+ def setup
313
+ @comment = Comment.new
314
+ @author = Author.new
315
+ @description = Description.new
316
+ @post = Post.new(comments: [@comment],
317
+ author: @author,
318
+ description: @description)
319
+ @post_serializer = ResourceNamespace::PostSerializer.new(@post)
320
+ end
321
+
322
+ def test_associations_namespaced_serializers
323
+ @post_serializer.associations.each do |association|
324
+ case association.key
325
+ when :comments, :latest_comments
326
+ assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
327
+ when :author
328
+ assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
329
+ when :description
330
+ assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
331
+ else
332
+ flunk "Unknown association: #{key}"
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ class NestedSerializersTest < ActiveSupport::TestCase
339
+ class Post < ::Model
340
+ associations :comments, :author, :description
341
+ end
342
+ class Comment < ::Model; end
343
+ class Author < ::Model; end
344
+ class Description < ::Model; end
345
+ class PostSerializer < ActiveModel::Serializer
346
+ has_many :comments
347
+ class CommentSerializer < ActiveModel::Serializer; end
348
+ belongs_to :author
349
+ class AuthorSerializer < ActiveModel::Serializer; end
350
+ has_one :description
351
+ class DescriptionSerializer < ActiveModel::Serializer; end
352
+ end
353
+
354
+ def setup
355
+ @comment = Comment.new
356
+ @author = Author.new
357
+ @description = Description.new
358
+ @post = Post.new(comments: [@comment],
359
+ author: @author,
360
+ description: @description)
361
+ @post_serializer = PostSerializer.new(@post)
362
+ end
363
+
364
+ def test_associations_namespaced_resources
365
+ @post_serializer.associations.each do |association|
366
+ case association.key
367
+ when :comments
368
+ assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first)
369
+ when :author
370
+ assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer)
371
+ when :description
372
+ assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer)
373
+ else
374
+ flunk "Unknown association: #{key}"
375
+ end
376
+ end
377
+ end
378
+
379
+ # rubocop:disable Metrics/AbcSize
380
+ def test_conditional_associations
381
+ model = Class.new(::Model) do
382
+ attributes :true, :false
383
+ associations :something
384
+ end.new(true: true, false: false)
385
+
386
+ scenarios = [
387
+ { options: { if: :true }, included: true },
388
+ { options: { if: :false }, included: false },
389
+ { options: { unless: :false }, included: true },
390
+ { options: { unless: :true }, included: false },
391
+ { options: { if: 'object.true' }, included: true },
392
+ { options: { if: 'object.false' }, included: false },
393
+ { options: { unless: 'object.false' }, included: true },
394
+ { options: { unless: 'object.true' }, included: false },
395
+ { options: { if: -> { object.true } }, included: true },
396
+ { options: { if: -> { object.false } }, included: false },
397
+ { options: { unless: -> { object.false } }, included: true },
398
+ { options: { unless: -> { object.true } }, included: false },
399
+ { options: { if: -> (s) { s.object.true } }, included: true },
400
+ { options: { if: -> (s) { s.object.false } }, included: false },
401
+ { options: { unless: -> (s) { s.object.false } }, included: true },
402
+ { options: { unless: -> (s) { s.object.true } }, included: false }
403
+ ]
404
+
405
+ scenarios.each do |s|
406
+ serializer = Class.new(ActiveModel::Serializer) do
407
+ belongs_to :something, s[:options]
408
+
409
+ def true
410
+ true
411
+ end
412
+
413
+ def false
414
+ false
415
+ end
416
+ end
417
+
418
+ hash = serializable(model, serializer: serializer).serializable_hash
419
+ assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}")
420
+ end
421
+ end
422
+
423
+ def test_illegal_conditional_associations
424
+ exception = assert_raises(TypeError) do
425
+ Class.new(ActiveModel::Serializer) do
426
+ belongs_to :x, if: nil
427
+ end
428
+ end
429
+
430
+ assert_match(/:if should be a Symbol, String or Proc/, exception.message)
431
+ end
432
+ end
433
+
434
+ class InheritedSerializerTest < ActiveSupport::TestCase
435
+ class PostSerializer < ActiveModel::Serializer
436
+ belongs_to :author
437
+ has_many :comments
438
+ belongs_to :blog
439
+ end
440
+
441
+ class InheritedPostSerializer < PostSerializer
442
+ belongs_to :author, polymorphic: true
443
+ has_many :comments, key: :reviews
444
+ end
445
+
446
+ class AuthorSerializer < ActiveModel::Serializer
447
+ has_many :posts
448
+ has_many :roles
449
+ has_one :bio
450
+ end
451
+
452
+ class InheritedAuthorSerializer < AuthorSerializer
453
+ has_many :roles, polymorphic: true
454
+ has_one :bio, polymorphic: true
455
+ end
456
+
457
+ def setup
458
+ @author = Author.new(name: 'Steve K.')
459
+ @post = Post.new(title: 'New Post', body: 'Body')
460
+ @post_serializer = PostSerializer.new(@post)
461
+ @author_serializer = AuthorSerializer.new(@author)
462
+ @inherited_post_serializer = InheritedPostSerializer.new(@post)
463
+ @inherited_author_serializer = InheritedAuthorSerializer.new(@author)
464
+ @author_associations = @author_serializer.associations.to_a.sort_by(&:name)
465
+ @inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name)
466
+ @post_associations = @post_serializer.associations.to_a
467
+ @inherited_post_associations = @inherited_post_serializer.associations.to_a
468
+ end
469
+
470
+ test 'an author serializer must have [posts,roles,bio] associations' do
471
+ expected = [:posts, :roles, :bio].sort
472
+ result = @author_serializer.associations.map(&:name).sort
473
+ assert_equal(result, expected)
474
+ end
475
+
476
+ test 'a post serializer must have [author,comments,blog] associations' do
477
+ expected = [:author, :comments, :blog].sort
478
+ result = @post_serializer.associations.map(&:name).sort
479
+ assert_equal(result, expected)
480
+ end
481
+
482
+ test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
483
+ expected = [:roles, :bio].sort
484
+ result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name)
485
+ assert_equal(result, expected)
486
+ assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?)
487
+ assert_equal [false, false, false], @author_associations.map(&:polymorphic?)
488
+ end
489
+
490
+ test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
491
+ assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
492
+ assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
493
+
494
+ refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
495
+ assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
496
+
497
+ refute @post_associations.detect { |assoc| assoc.name == :comments }.key?
498
+ original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
499
+ refute original_comment_assoc.key?
500
+ assert_equal :reviews, new_comments_assoc.key
501
+
502
+ original_blog = @post_associations.detect { |assoc| assoc.name == :blog }
503
+ inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog }
504
+ original_parent_serializer = original_blog.lazy_association.association_options.delete(:parent_serializer)
505
+ inherited_parent_serializer = inherited_blog.lazy_association.association_options.delete(:parent_serializer)
506
+ assert_equal PostSerializer, original_parent_serializer.class
507
+ assert_equal InheritedPostSerializer, inherited_parent_serializer.class
508
+ end
509
+
510
+ test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
511
+ expected = [:author, :comments, :blog, :reviews].sort
512
+ result = @inherited_post_serializer.associations.map(&:key).sort
513
+ assert_equal(result, expected)
514
+ end
515
+ end
516
+ end
517
+ end
518
+ end
@@ -0,0 +1,153 @@
1
+ require 'test_helper'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class AttributeTest < ActiveSupport::TestCase
6
+ def setup
7
+ @blog = Blog.new(id: 1, name: 'AMS Hints', type: 'stuff')
8
+ @blog_serializer = AlternateBlogSerializer.new(@blog)
9
+ end
10
+
11
+ def test_attributes_definition
12
+ assert_equal([:id, :title],
13
+ @blog_serializer.class._attributes)
14
+ end
15
+
16
+ def test_json_serializable_hash
17
+ adapter = ActiveModelSerializers::Adapter::Json.new(@blog_serializer)
18
+ assert_equal({ blog: { id: 1, title: 'AMS Hints' } }, adapter.serializable_hash)
19
+ end
20
+
21
+ def test_attribute_inheritance_with_key
22
+ inherited_klass = Class.new(AlternateBlogSerializer)
23
+ blog_serializer = inherited_klass.new(@blog)
24
+ adapter = ActiveModelSerializers::Adapter::Attributes.new(blog_serializer)
25
+ assert_equal({ id: 1, title: 'AMS Hints' }, adapter.serializable_hash)
26
+ end
27
+
28
+ def test_multiple_calls_with_the_same_attribute
29
+ serializer_class = Class.new(ActiveModel::Serializer) do
30
+ attribute :title
31
+ attribute :title
32
+ end
33
+
34
+ assert_equal([:title], serializer_class._attributes)
35
+ end
36
+
37
+ def test_id_attribute_override
38
+ serializer = Class.new(ActiveModel::Serializer) do
39
+ attribute :name, key: :id
40
+ end
41
+
42
+ adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog))
43
+ assert_equal({ blog: { id: 'AMS Hints' } }, adapter.serializable_hash)
44
+ end
45
+
46
+ def test_object_attribute_override
47
+ serializer = Class.new(ActiveModel::Serializer) do
48
+ attribute :name, key: :object
49
+ end
50
+
51
+ adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog))
52
+ assert_equal({ blog: { object: 'AMS Hints' } }, adapter.serializable_hash)
53
+ end
54
+
55
+ def test_type_attribute
56
+ attribute_serializer = Class.new(ActiveModel::Serializer) do
57
+ attribute :id, key: :type
58
+ end
59
+ attributes_serializer = Class.new(ActiveModel::Serializer) do
60
+ attributes :type
61
+ end
62
+
63
+ adapter = ActiveModelSerializers::Adapter::Json.new(attribute_serializer.new(@blog))
64
+ assert_equal({ blog: { type: 1 } }, adapter.serializable_hash)
65
+
66
+ adapter = ActiveModelSerializers::Adapter::Json.new(attributes_serializer.new(@blog))
67
+ assert_equal({ blog: { type: 'stuff' } }, adapter.serializable_hash)
68
+ end
69
+
70
+ def test_id_attribute_override_before
71
+ serializer = Class.new(ActiveModel::Serializer) do
72
+ def id
73
+ 'custom'
74
+ end
75
+
76
+ attribute :id
77
+ end
78
+
79
+ hash = ActiveModelSerializers::SerializableResource.new(@blog, adapter: :json, serializer: serializer).serializable_hash
80
+
81
+ assert_equal('custom', hash[:blog][:id])
82
+ end
83
+
84
+ class PostWithVirtualAttribute < ::Model; attributes :first_name, :last_name end
85
+ class PostWithVirtualAttributeSerializer < ActiveModel::Serializer
86
+ attribute :name do
87
+ "#{object.first_name} #{object.last_name}"
88
+ end
89
+ end
90
+
91
+ def test_virtual_attribute_block
92
+ post = PostWithVirtualAttribute.new(first_name: 'Lucas', last_name: 'Hosseini')
93
+ hash = serializable(post).serializable_hash
94
+ expected = { name: 'Lucas Hosseini' }
95
+
96
+ assert_equal(expected, hash)
97
+ end
98
+
99
+ # rubocop:disable Metrics/AbcSize
100
+ def test_conditional_associations
101
+ model = Class.new(::Model) do
102
+ attributes :true, :false, :attribute
103
+ end.new(true: true, false: false)
104
+
105
+ scenarios = [
106
+ { options: { if: :true }, included: true },
107
+ { options: { if: :false }, included: false },
108
+ { options: { unless: :false }, included: true },
109
+ { options: { unless: :true }, included: false },
110
+ { options: { if: 'object.true' }, included: true },
111
+ { options: { if: 'object.false' }, included: false },
112
+ { options: { unless: 'object.false' }, included: true },
113
+ { options: { unless: 'object.true' }, included: false },
114
+ { options: { if: -> { object.true } }, included: true },
115
+ { options: { if: -> { object.false } }, included: false },
116
+ { options: { unless: -> { object.false } }, included: true },
117
+ { options: { unless: -> { object.true } }, included: false },
118
+ { options: { if: -> (s) { s.object.true } }, included: true },
119
+ { options: { if: -> (s) { s.object.false } }, included: false },
120
+ { options: { unless: -> (s) { s.object.false } }, included: true },
121
+ { options: { unless: -> (s) { s.object.true } }, included: false }
122
+ ]
123
+
124
+ scenarios.each do |s|
125
+ serializer = Class.new(ActiveModel::Serializer) do
126
+ attribute :attribute, s[:options]
127
+
128
+ def true
129
+ true
130
+ end
131
+
132
+ def false
133
+ false
134
+ end
135
+ end
136
+
137
+ hash = serializable(model, serializer: serializer).serializable_hash
138
+ assert_equal(s[:included], hash.key?(:attribute), "Error with #{s[:options]}")
139
+ end
140
+ end
141
+
142
+ def test_illegal_conditional_attributes
143
+ exception = assert_raises(TypeError) do
144
+ Class.new(ActiveModel::Serializer) do
145
+ attribute :x, if: nil
146
+ end
147
+ end
148
+
149
+ assert_match(/:if should be a Symbol, String or Proc/, exception.message)
150
+ end
151
+ end
152
+ end
153
+ end