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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi
@@ -189,7 +191,7 @@ module ActiveModelSerializers
189
191
 
190
192
  polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
191
193
  if polymorphic
192
- hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'] : nil
194
+ hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'].classify : nil
193
195
  end
194
196
 
195
197
  hash
@@ -205,7 +207,7 @@ module ActiveModelSerializers
205
207
  # @api private
206
208
  def transform_keys(hash, options)
207
209
  transform = options[:key_transform] || :underscore
208
- KeyTransform.send(transform, hash)
210
+ CaseTransform.send(transform, hash)
209
211
  end
210
212
  end
211
213
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi
@@ -71,7 +73,7 @@ module ActiveModelSerializers
71
73
  hash[:href] = @href if defined?(@href)
72
74
  hash[:meta] = @meta if defined?(@meta)
73
75
 
74
- hash
76
+ hash.any? ? hash : nil
75
77
  end
76
78
 
77
79
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi < Base
4
6
  class PaginationLinks
7
+ MissingSerializationContextError = Class.new(KeyError)
5
8
  FIRST_PAGE = 1
6
9
 
7
10
  attr_reader :collection, :context
@@ -9,16 +12,23 @@ module ActiveModelSerializers
9
12
  def initialize(collection, adapter_options)
10
13
  @collection = collection
11
14
  @adapter_options = adapter_options
12
- @context = adapter_options.fetch(:serialization_context)
15
+ @context = adapter_options.fetch(:serialization_context) do
16
+ fail MissingSerializationContextError, <<-EOF.freeze
17
+ JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext.
18
+ Please pass a ':serialization_context' option or
19
+ override CollectionSerializer#paginated? to return 'false'.
20
+ EOF
21
+ end
13
22
  end
14
23
 
15
24
  def as_json
16
- per_page = collection.try(:per_page) || collection.try(:limit_value) || collection.size
17
- pages_from.each_with_object({}) do |(key, value), hash|
18
- params = query_parameters.merge(page: { number: value, size: per_page }).to_query
19
-
20
- hash[key] = "#{url(adapter_options)}?#{params}"
21
- end
25
+ {
26
+ self: location_url,
27
+ first: first_page_url,
28
+ prev: prev_page_url,
29
+ next: next_page_url,
30
+ last: last_page_url
31
+ }
22
32
  end
23
33
 
24
34
  protected
@@ -27,25 +37,39 @@ module ActiveModelSerializers
27
37
 
28
38
  private
29
39
 
30
- def pages_from
31
- return {} if collection.total_pages <= FIRST_PAGE
32
-
33
- {}.tap do |pages|
34
- pages[:self] = collection.current_page
40
+ def location_url
41
+ url_for_page(collection.current_page)
42
+ end
35
43
 
36
- unless collection.current_page == FIRST_PAGE
37
- pages[:first] = FIRST_PAGE
38
- pages[:prev] = collection.current_page - FIRST_PAGE
39
- end
44
+ def first_page_url
45
+ url_for_page(1)
46
+ end
40
47
 
41
- unless collection.current_page == collection.total_pages
42
- pages[:next] = collection.current_page + FIRST_PAGE
43
- pages[:last] = collection.total_pages
44
- end
48
+ def last_page_url
49
+ if collection.total_pages == 0
50
+ url_for_page(FIRST_PAGE)
51
+ else
52
+ url_for_page(collection.total_pages)
45
53
  end
46
54
  end
47
55
 
48
- def url(options)
56
+ def prev_page_url
57
+ return nil if collection.current_page == FIRST_PAGE
58
+ url_for_page(collection.current_page - FIRST_PAGE)
59
+ end
60
+
61
+ def next_page_url
62
+ return nil if collection.total_pages == 0 || collection.current_page == collection.total_pages
63
+ url_for_page(collection.next_page)
64
+ end
65
+
66
+ def url_for_page(number)
67
+ params = query_parameters.dup
68
+ params[:page] = { size: per_page, number: number }
69
+ "#{url(adapter_options)}?#{params.to_query}"
70
+ end
71
+
72
+ def url(options = {})
49
73
  @url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url
50
74
  end
51
75
 
@@ -56,6 +80,10 @@ module ActiveModelSerializers
56
80
  def query_parameters
57
81
  @query_parameters ||= context.query_parameters
58
82
  end
83
+
84
+ def per_page
85
+ @per_page ||= collection.try(:per_page) || collection.try(:limit_value) || collection.size
86
+ end
59
87
  end
60
88
  end
61
89
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi
@@ -5,47 +7,99 @@ module ActiveModelSerializers
5
7
  # {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links}
6
8
  # {http://jsonapi.org/format/#document-links Document Links}
7
9
  # {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage}
8
- # {http://jsonapi.org/format/#document-meta Docment Meta}
9
- def initialize(parent_serializer, serializer, serializable_resource_options, args = {})
10
- @object = parent_serializer.object
11
- @scope = parent_serializer.scope
12
- @association_options = args.fetch(:options, {})
10
+ # {http://jsonapi.org/format/#document-meta Document Meta}
11
+ def initialize(parent_serializer, serializable_resource_options, association)
12
+ @parent_serializer = parent_serializer
13
+ @association = association
13
14
  @serializable_resource_options = serializable_resource_options
14
- @data = data_for(serializer)
15
- @links = args.fetch(:links, {}).each_with_object({}) do |(key, value), hash|
16
- hash[key] = ActiveModelSerializers::Adapter::JsonApi::Link.new(parent_serializer, value).as_json
17
- end
18
- meta = args.fetch(:meta, nil)
19
- @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
20
15
  end
21
16
 
22
17
  def as_json
23
18
  hash = {}
24
- hash[:data] = data if association_options[:include_data]
25
- links = self.links
19
+
20
+ hash[:data] = data_for(association) if association.include_data?
21
+
22
+ links = links_for(association)
26
23
  hash[:links] = links if links.any?
27
- meta = self.meta
24
+
25
+ meta = meta_for(association)
28
26
  hash[:meta] = meta if meta
27
+ hash[:meta] = {} if hash.empty?
29
28
 
30
29
  hash
31
30
  end
32
31
 
33
32
  protected
34
33
 
35
- attr_reader :object, :scope, :data, :serializable_resource_options,
36
- :association_options, :links, :meta
34
+ attr_reader :parent_serializer, :serializable_resource_options, :association
37
35
 
38
36
  private
39
37
 
40
- def data_for(serializer)
41
- if serializer.respond_to?(:each)
42
- serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
43
- elsif association_options[:virtual_value]
44
- association_options[:virtual_value]
45
- elsif serializer && serializer.object
46
- ResourceIdentifier.new(serializer, serializable_resource_options).as_json
38
+ # TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self
39
+ def data_for(association)
40
+ if association.collection?
41
+ data_for_many(association)
42
+ else
43
+ data_for_one(association)
44
+ end
45
+ end
46
+
47
+ def data_for_one(association)
48
+ if belongs_to_id_on_self?(association)
49
+ id = parent_serializer.read_attribute_for_serialization(association.reflection.foreign_key)
50
+ type =
51
+ if association.polymorphic?
52
+ # We can't infer resource type for polymorphic relationships from the serializer.
53
+ # We can ONLY know a polymorphic resource type by inspecting each resource.
54
+ association.lazy_association.serializer.json_key
55
+ else
56
+ association.reflection.type.to_s
57
+ end
58
+ ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options)
59
+ else
60
+ # TODO(BF): Process relationship without evaluating lazy_association
61
+ serializer = association.lazy_association.serializer
62
+ if (virtual_value = association.virtual_value)
63
+ virtual_value
64
+ elsif serializer && association.object
65
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
66
+ else
67
+ nil
68
+ end
69
+ end
70
+ end
71
+
72
+ def data_for_many(association)
73
+ # TODO(BF): Process relationship without evaluating lazy_association
74
+ collection_serializer = association.lazy_association.serializer
75
+ if collection_serializer.respond_to?(:each)
76
+ collection_serializer.map do |serializer|
77
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
78
+ end
79
+ elsif (virtual_value = association.virtual_value)
80
+ virtual_value
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ def links_for(association)
87
+ association.links.each_with_object({}) do |(key, value), hash|
88
+ result = Link.new(parent_serializer, value).as_json
89
+ hash[key] = result if result
47
90
  end
48
91
  end
92
+
93
+ def meta_for(association)
94
+ meta = association.meta
95
+ meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
96
+ end
97
+
98
+ def belongs_to_id_on_self?(association)
99
+ parent_serializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship &&
100
+ association.belongs_to? &&
101
+ parent_serializer.object.respond_to?(association.reflection.foreign_key)
102
+ end
49
103
  end
50
104
  end
51
105
  end
@@ -1,16 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class JsonApi
4
6
  class ResourceIdentifier
7
+ def self.type_for(serializer, serializer_type = nil, transform_options = {})
8
+ raw_type = serializer_type ? serializer_type : raw_type_from_serializer_object(serializer.object)
9
+ JsonApi.send(:transform_key_casing!, raw_type, transform_options)
10
+ end
11
+
12
+ def self.for_type_with_id(type, id, options)
13
+ type = inflect_type(type)
14
+ type = type_for(:no_class_needed, type, options)
15
+ if id.blank?
16
+ { type: type }
17
+ else
18
+ { id: id.to_s, type: type }
19
+ end
20
+ end
21
+
22
+ def self.raw_type_from_serializer_object(object)
23
+ class_name = object.class.name # should use model_name
24
+ raw_type = class_name.underscore
25
+ raw_type = inflect_type(raw_type)
26
+ raw_type
27
+ .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator)
28
+ raw_type
29
+ end
30
+
31
+ def self.inflect_type(type)
32
+ singularize = ActiveModelSerializers.config.jsonapi_resource_type == :singular
33
+ inflection = singularize ? :singularize : :pluralize
34
+ ActiveSupport::Inflector.public_send(inflection, type)
35
+ end
36
+
5
37
  # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
6
38
  def initialize(serializer, options)
7
39
  @id = id_for(serializer)
8
- @type = JsonApi.send(:transform_key_casing!, type_for(serializer),
9
- options)
40
+ @type = type_for(serializer, options)
10
41
  end
11
42
 
12
43
  def as_json
13
- { id: id, type: type }
44
+ if id.blank?
45
+ { type: type }
46
+ else
47
+ { id: id.to_s, type: type }
48
+ end
14
49
  end
15
50
 
16
51
  protected
@@ -19,13 +54,9 @@ module ActiveModelSerializers
19
54
 
20
55
  private
21
56
 
22
- def type_for(serializer)
23
- return serializer._type if serializer._type
24
- if ActiveModelSerializers.config.jsonapi_resource_type == :singular
25
- serializer.object.class.model_name.singular
26
- else
27
- serializer.object.class.model_name.plural
28
- end
57
+ def type_for(serializer, transform_options)
58
+ serializer_type = serializer._type
59
+ self.class.type_for(serializer, serializer_type, transform_options)
29
60
  end
30
61
 
31
62
  def id_for(serializer)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # {http://jsonapi.org/format/ JSON API specification}
2
4
  # rubocop:disable Style/AsciiComments
3
5
  # TODO: implement!
@@ -22,25 +24,38 @@ module ActiveModelSerializers
22
24
  module Adapter
23
25
  class JsonApi < Base
24
26
  extend ActiveSupport::Autoload
25
- autoload :Jsonapi
26
- autoload :ResourceIdentifier
27
- autoload :Relationship
28
- autoload :Link
29
- autoload :PaginationLinks
30
- autoload :Meta
31
- autoload :Error
32
- autoload :Deserialization
33
-
34
- def initialize(serializer, options = {})
35
- super
36
- @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include])
37
- @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
27
+ eager_autoload do
28
+ autoload :Jsonapi
29
+ autoload :ResourceIdentifier
30
+ autoload :Link
31
+ autoload :PaginationLinks
32
+ autoload :Meta
33
+ autoload :Error
34
+ autoload :Deserialization
35
+ autoload :Relationship
38
36
  end
39
37
 
40
38
  def self.default_key_transform
41
39
  :dash
42
40
  end
43
41
 
42
+ def self.fragment_cache(cached_hash, non_cached_hash, root = true)
43
+ core_cached = cached_hash.first
44
+ core_non_cached = non_cached_hash.first
45
+ no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
46
+ no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
47
+ cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
48
+ hash = root ? { root => cached_resource } : cached_resource
49
+
50
+ hash.deep_merge no_root_non_cache.deep_merge no_root_cache
51
+ end
52
+
53
+ def initialize(serializer, options = {})
54
+ super
55
+ @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
56
+ @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
57
+ end
58
+
44
59
  # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
45
60
  # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
46
61
  def serializable_hash(*)
@@ -52,6 +67,11 @@ module ActiveModelSerializers
52
67
  self.class.transform_key_casing!(document, instance_options)
53
68
  end
54
69
 
70
+ def fragment_cache(cached_hash, non_cached_hash)
71
+ root = !instance_options.include?(:include)
72
+ self.class.fragment_cache(cached_hash, non_cached_hash, root)
73
+ end
74
+
55
75
  # {http://jsonapi.org/format/#document-top-level Primary data}
56
76
  # definition:
57
77
  # ☐ toplevel_data (required)
@@ -174,18 +194,6 @@ module ActiveModelSerializers
174
194
  hash
175
195
  end
176
196
 
177
- def fragment_cache(cached_hash, non_cached_hash)
178
- root = false if instance_options.include?(:include)
179
- core_cached = cached_hash.first
180
- core_non_cached = non_cached_hash.first
181
- no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
182
- no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
183
- cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
184
- hash = root ? { root => cached_resource } : cached_resource
185
-
186
- hash.deep_merge no_root_non_cache.deep_merge no_root_cache
187
- end
188
-
189
197
  protected
190
198
 
191
199
  attr_reader :fieldset
@@ -231,17 +239,17 @@ module ActiveModelSerializers
231
239
  @primary = []
232
240
  @included = []
233
241
  @resource_identifiers = Set.new
234
- serializers.each { |serializer| process_resource(serializer, true) }
235
- serializers.each { |serializer| process_relationships(serializer, @include_tree) }
242
+ serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
243
+ serializers.each { |serializer| process_relationships(serializer, @include_directive) }
236
244
 
237
245
  [@primary, @included]
238
246
  end
239
247
 
240
- def process_resource(serializer, primary)
248
+ def process_resource(serializer, primary, include_slice = {})
241
249
  resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
242
250
  return false unless @resource_identifiers.add?(resource_identifier)
243
251
 
244
- resource_object = resource_object_for(serializer)
252
+ resource_object = resource_object_for(serializer, include_slice)
245
253
  if primary
246
254
  @primary << resource_object
247
255
  else
@@ -251,21 +259,22 @@ module ActiveModelSerializers
251
259
  true
252
260
  end
253
261
 
254
- def process_relationships(serializer, include_tree)
255
- serializer.associations(include_tree).each do |association|
256
- process_relationship(association.serializer, include_tree[association.key])
262
+ def process_relationships(serializer, include_slice)
263
+ serializer.associations(include_slice).each do |association|
264
+ # TODO(BF): Process relationship without evaluating lazy_association
265
+ process_relationship(association.lazy_association.serializer, include_slice[association.key])
257
266
  end
258
267
  end
259
268
 
260
- def process_relationship(serializer, include_tree)
269
+ def process_relationship(serializer, include_slice)
261
270
  if serializer.respond_to?(:each)
262
- serializer.each { |s| process_relationship(s, include_tree) }
271
+ serializer.each { |s| process_relationship(s, include_slice) }
263
272
  return
264
273
  end
265
274
  return unless serializer && serializer.object
266
- return unless process_resource(serializer, false)
275
+ return unless process_resource(serializer, false, include_slice)
267
276
 
268
- process_relationships(serializer, include_tree)
277
+ process_relationships(serializer, include_slice)
269
278
  end
270
279
 
271
280
  # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
@@ -289,21 +298,9 @@ module ActiveModelSerializers
289
298
  end
290
299
 
291
300
  # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
292
- def resource_object_for(serializer)
293
- resource_object = cache_check(serializer) do
294
- resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
295
-
296
- requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
297
- attributes = attributes_for(serializer, requested_fields)
298
- resource_object[:attributes] = attributes if attributes.any?
299
- resource_object
300
- end
301
+ def resource_object_for(serializer, include_slice = {})
302
+ resource_object = data_for(serializer, include_slice)
301
303
 
302
- requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
303
- relationships = relationships_for(serializer, requested_associations)
304
- resource_object[:relationships] = relationships if relationships.any?
305
-
306
- links = links_for(serializer)
307
304
  # toplevel_links
308
305
  # definition:
309
306
  # allOf
@@ -317,7 +314,10 @@ module ActiveModelSerializers
317
314
  # prs:
318
315
  # https://github.com/rails-api/active_model_serializers/pull/1247
319
316
  # https://github.com/rails-api/active_model_serializers/pull/1018
320
- resource_object[:links] = links if links.any?
317
+ if (links = links_for(serializer)).any?
318
+ resource_object ||= {}
319
+ resource_object[:links] = links
320
+ end
321
321
 
322
322
  # toplevel_meta
323
323
  # alias meta
@@ -327,12 +327,33 @@ module ActiveModelSerializers
327
327
  # {
328
328
  # :'git-ref' => 'abc123'
329
329
  # }
330
- meta = meta_for(serializer)
331
- resource_object[:meta] = meta unless meta.blank?
330
+ if (meta = meta_for(serializer)).present?
331
+ resource_object ||= {}
332
+ resource_object[:meta] = meta
333
+ end
332
334
 
333
335
  resource_object
334
336
  end
335
337
 
338
+ def data_for(serializer, include_slice)
339
+ data = serializer.fetch(self) do
340
+ resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
341
+ break nil if resource_object.nil?
342
+
343
+ requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
344
+ attributes = attributes_for(serializer, requested_fields)
345
+ resource_object[:attributes] = attributes if attributes.any?
346
+ resource_object
347
+ end
348
+ data.tap do |resource_object|
349
+ next if resource_object.nil?
350
+ # NOTE(BF): the attributes are cached above, separately from the relationships, below.
351
+ requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
352
+ relationships = relationships_for(serializer, requested_associations, include_slice)
353
+ resource_object[:relationships] = relationships if relationships.any?
354
+ end
355
+ end
356
+
336
357
  # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
337
358
  # relationships
338
359
  # definition:
@@ -428,17 +449,13 @@ module ActiveModelSerializers
428
449
  # id: 'required-id',
429
450
  # meta: meta
430
451
  # }.reject! {|_,v| v.nil? }
431
- def relationships_for(serializer, requested_associations)
432
- include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
433
- serializer.associations(include_tree).each_with_object({}) do |association, hash|
434
- hash[association.key] = Relationship.new(
435
- serializer,
436
- association.serializer,
437
- instance_options,
438
- options: association.options,
439
- links: association.links,
440
- meta: association.meta
441
- ).as_json
452
+ def relationships_for(serializer, requested_associations, include_slice)
453
+ include_directive = JSONAPI::IncludeDirective.new(
454
+ requested_associations,
455
+ allow_wildcard: true
456
+ )
457
+ serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
458
+ hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
442
459
  end
443
460
  end
444
461
 
@@ -467,7 +484,9 @@ module ActiveModelSerializers
467
484
  # }.reject! {|_,v| v.nil? }
468
485
  def links_for(serializer)
469
486
  serializer._links.each_with_object({}) do |(name, value), hash|
470
- hash[name] = Link.new(serializer, value).as_json
487
+ next if value.excluded?(serializer)
488
+ result = Link.new(serializer, value.block).as_json
489
+ hash[name] = result if result
471
490
  end
472
491
  end
473
492
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class Null < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  UnknownAdapterError = Class.new(ArgumentError)
@@ -5,11 +7,13 @@ module ActiveModelSerializers
5
7
  private_constant :ADAPTER_MAP if defined?(private_constant)
6
8
 
7
9
  class << self # All methods are class functions
10
+ # :nocov:
8
11
  def new(*args)
9
12
  fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
10
13
  "Adapter.new called with args: '#{args.inspect}', from" \
11
14
  "'caller[0]'."
12
15
  end
16
+ # :nocov:
13
17
 
14
18
  def configured_adapter
15
19
  lookup(ActiveModelSerializers.config.adapter)
@@ -33,7 +37,7 @@ module ActiveModelSerializers
33
37
 
34
38
  # @return [Array<Symbol>] list of adapter names
35
39
  def adapters
36
- adapter_map.keys.sort
40
+ adapter_map.keys.sort!
37
41
  end
38
42
 
39
43
  # Adds an adapter 'klass' with 'name' to the 'adapter_map'
@@ -51,6 +55,10 @@ module ActiveModelSerializers
51
55
  self
52
56
  end
53
57
 
58
+ def registered_name(adapter_class)
59
+ ADAPTER_MAP.key adapter_class
60
+ end
61
+
54
62
  # @param adapter [String, Symbol, Class] name to fetch adapter by
55
63
  # @return [ActiveModelSerializers::Adapter] subclass of Adapter
56
64
  # @raise [UnknownAdapterError]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Adapted from
2
4
  # https://github.com/rails/rails/blob/7f18ea14c8/activejob/lib/active_job/callbacks.rb
3
5
  require 'active_support/callbacks'