active_model_serializers 0.10.0 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +6 -5
  3. data/.travis.yml +30 -21
  4. data/CHANGELOG.md +172 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +23 -4
  7. data/README.md +166 -28
  8. data/Rakefile +3 -32
  9. data/active_model_serializers.gemspec +22 -25
  10. data/appveyor.yml +10 -6
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +35 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +86 -1
  16. data/docs/general/deserialization.md +1 -1
  17. data/docs/general/fields.md +31 -0
  18. data/docs/general/getting_started.md +1 -1
  19. data/docs/general/logging.md +7 -0
  20. data/docs/general/rendering.md +63 -25
  21. data/docs/general/serializers.md +125 -14
  22. data/docs/howto/add_pagination_links.md +16 -17
  23. data/docs/howto/add_relationship_links.md +140 -0
  24. data/docs/howto/add_root_key.md +11 -0
  25. data/docs/howto/grape_integration.md +42 -0
  26. data/docs/howto/outside_controller_use.md +12 -4
  27. data/docs/howto/passing_arbitrary_options.md +2 -2
  28. data/docs/howto/serialize_poro.md +46 -5
  29. data/docs/howto/test.md +2 -0
  30. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  31. data/docs/integrations/ember-and-json-api.md +67 -32
  32. data/docs/jsonapi/schema.md +1 -1
  33. data/lib/action_controller/serialization.rb +13 -3
  34. data/lib/active_model/serializer/adapter/base.rb +2 -0
  35. data/lib/active_model/serializer/array_serializer.rb +8 -5
  36. data/lib/active_model/serializer/association.rb +62 -10
  37. data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
  38. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  39. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
  40. data/lib/active_model/serializer/error_serializer.rb +11 -7
  41. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  42. data/lib/active_model/serializer/has_many_reflection.rb +3 -3
  43. data/lib/active_model/serializer/has_one_reflection.rb +1 -4
  44. data/lib/active_model/serializer/lazy_association.rb +95 -0
  45. data/lib/active_model/serializer/lint.rb +134 -130
  46. data/lib/active_model/serializer/reflection.rb +127 -67
  47. data/lib/active_model/serializer/version.rb +1 -1
  48. data/lib/active_model/serializer.rb +297 -79
  49. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  50. data/lib/active_model_serializers/adapter/base.rb +39 -39
  51. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
  52. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  53. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +47 -21
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
  56. data/lib/active_model_serializers/adapter/json_api.rb +71 -57
  57. data/lib/active_model_serializers/adapter.rb +6 -0
  58. data/lib/active_model_serializers/deprecate.rb +1 -2
  59. data/lib/active_model_serializers/deserialization.rb +2 -0
  60. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  61. data/lib/active_model_serializers/model.rb +109 -28
  62. data/lib/active_model_serializers/railtie.rb +3 -1
  63. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  64. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  65. data/lib/active_model_serializers/serialization_context.rb +10 -3
  66. data/lib/active_model_serializers/test/schema.rb +2 -2
  67. data/lib/active_model_serializers.rb +16 -1
  68. data/lib/generators/rails/resource_override.rb +1 -1
  69. data/lib/generators/rails/serializer_generator.rb +4 -4
  70. data/lib/grape/active_model_serializers.rb +7 -5
  71. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  72. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  73. data/lib/tasks/rubocop.rake +53 -0
  74. data/test/action_controller/adapter_selector_test.rb +14 -5
  75. data/test/action_controller/explicit_serializer_test.rb +5 -4
  76. data/test/action_controller/json/include_test.rb +106 -27
  77. data/test/action_controller/json_api/deserialization_test.rb +1 -1
  78. data/test/action_controller/json_api/errors_test.rb +8 -9
  79. data/test/action_controller/json_api/fields_test.rb +66 -0
  80. data/test/action_controller/json_api/linked_test.rb +29 -24
  81. data/test/action_controller/json_api/pagination_test.rb +31 -23
  82. data/test/action_controller/json_api/transform_test.rb +11 -3
  83. data/test/action_controller/lookup_proc_test.rb +49 -0
  84. data/test/action_controller/namespace_lookup_test.rb +232 -0
  85. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  86. data/test/action_controller/serialization_test.rb +12 -9
  87. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  88. data/test/active_model_serializers/model_test.rb +137 -4
  89. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  90. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  91. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  92. data/test/active_model_serializers/test/schema_test.rb +3 -2
  93. data/test/adapter/attributes_test.rb +40 -0
  94. data/test/adapter/json/collection_test.rb +14 -0
  95. data/test/adapter/json/has_many_test.rb +10 -2
  96. data/test/adapter/json/transform_test.rb +15 -15
  97. data/test/adapter/json_api/collection_test.rb +4 -3
  98. data/test/adapter/json_api/errors_test.rb +17 -19
  99. data/test/adapter/json_api/fields_test.rb +12 -3
  100. data/test/adapter/json_api/has_many_test.rb +49 -20
  101. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  102. data/test/adapter/json_api/json_api_test.rb +5 -7
  103. data/test/adapter/json_api/linked_test.rb +33 -12
  104. data/test/adapter/json_api/links_test.rb +4 -2
  105. data/test/adapter/json_api/pagination_links_test.rb +53 -13
  106. data/test/adapter/json_api/parse_test.rb +1 -1
  107. data/test/adapter/json_api/relationship_test.rb +309 -73
  108. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  109. data/test/adapter/json_api/transform_test.rb +263 -253
  110. data/test/adapter/json_api/type_test.rb +168 -36
  111. data/test/adapter/json_test.rb +8 -7
  112. data/test/adapter/null_test.rb +1 -2
  113. data/test/adapter/polymorphic_test.rb +52 -5
  114. data/test/adapter_test.rb +1 -1
  115. data/test/benchmark/app.rb +1 -1
  116. data/test/benchmark/benchmarking_support.rb +1 -1
  117. data/test/benchmark/bm_active_record.rb +81 -0
  118. data/test/benchmark/bm_adapter.rb +38 -0
  119. data/test/benchmark/bm_caching.rb +16 -16
  120. data/test/benchmark/bm_lookup_chain.rb +83 -0
  121. data/test/benchmark/bm_transform.rb +21 -10
  122. data/test/benchmark/controllers.rb +16 -17
  123. data/test/benchmark/fixtures.rb +72 -72
  124. data/test/cache_test.rb +235 -69
  125. data/test/collection_serializer_test.rb +31 -14
  126. data/test/fixtures/active_record.rb +45 -10
  127. data/test/fixtures/poro.rb +124 -181
  128. data/test/generators/serializer_generator_test.rb +23 -5
  129. data/test/grape_test.rb +170 -56
  130. data/test/lint_test.rb +1 -1
  131. data/test/logger_test.rb +13 -11
  132. data/test/serializable_resource_test.rb +18 -22
  133. data/test/serializers/association_macros_test.rb +3 -2
  134. data/test/serializers/associations_test.rb +222 -49
  135. data/test/serializers/attribute_test.rb +5 -3
  136. data/test/serializers/attributes_test.rb +1 -1
  137. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  138. data/test/serializers/fieldset_test.rb +1 -1
  139. data/test/serializers/meta_test.rb +12 -6
  140. data/test/serializers/options_test.rb +17 -6
  141. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  142. data/test/serializers/reflection_test.rb +427 -0
  143. data/test/serializers/root_test.rb +1 -1
  144. data/test/serializers/serialization_test.rb +2 -2
  145. data/test/serializers/serializer_for_test.rb +12 -10
  146. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  147. data/test/support/isolated_unit.rb +9 -4
  148. data/test/support/rails5_shims.rb +8 -2
  149. data/test/support/rails_app.rb +2 -9
  150. data/test/support/serialization_testing.rb +31 -5
  151. data/test/test_helper.rb +13 -0
  152. metadata +130 -71
  153. data/.rubocop_todo.yml +0 -167
  154. data/docs/ARCHITECTURE.md +0 -126
  155. data/lib/active_model/serializer/associations.rb +0 -100
  156. data/lib/active_model/serializer/attributes.rb +0 -82
  157. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  158. data/lib/active_model/serializer/configuration.rb +0 -35
  159. data/lib/active_model/serializer/include_tree.rb +0 -111
  160. data/lib/active_model/serializer/links.rb +0 -35
  161. data/lib/active_model/serializer/meta.rb +0 -29
  162. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  163. data/lib/active_model/serializer/type.rb +0 -25
  164. data/lib/active_model_serializers/key_transform.rb +0 -70
  165. data/test/active_model_serializers/key_transform_test.rb +0 -263
  166. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  167. data/test/adapter/json_api/relationships_test.rb +0 -199
  168. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  169. data/test/include_tree/from_include_args_test.rb +0 -26
  170. data/test/include_tree/from_string_test.rb +0 -94
  171. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -2,6 +2,7 @@ module ActiveModelSerializers
2
2
  module Adapter
3
3
  class JsonApi < Base
4
4
  class PaginationLinks
5
+ MissingSerializationContextError = Class.new(KeyError)
5
6
  FIRST_PAGE = 1
6
7
 
7
8
  attr_reader :collection, :context
@@ -9,16 +10,23 @@ module ActiveModelSerializers
9
10
  def initialize(collection, adapter_options)
10
11
  @collection = collection
11
12
  @adapter_options = adapter_options
12
- @context = adapter_options.fetch(:serialization_context)
13
+ @context = adapter_options.fetch(:serialization_context) do
14
+ fail MissingSerializationContextError, <<-EOF.freeze
15
+ JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext.
16
+ Please pass a ':serialization_context' option or
17
+ override CollectionSerializer#paginated? to return 'false'.
18
+ EOF
19
+ end
13
20
  end
14
21
 
15
22
  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
23
+ {
24
+ self: location_url,
25
+ first: first_page_url,
26
+ prev: prev_page_url,
27
+ next: next_page_url,
28
+ last: last_page_url
29
+ }
22
30
  end
23
31
 
24
32
  protected
@@ -27,25 +35,39 @@ module ActiveModelSerializers
27
35
 
28
36
  private
29
37
 
30
- def pages_from
31
- return {} if collection.total_pages <= FIRST_PAGE
32
-
33
- {}.tap do |pages|
34
- pages[:self] = collection.current_page
38
+ def location_url
39
+ url_for_page(collection.current_page)
40
+ end
35
41
 
36
- unless collection.current_page == FIRST_PAGE
37
- pages[:first] = FIRST_PAGE
38
- pages[:prev] = collection.current_page - FIRST_PAGE
39
- end
42
+ def first_page_url
43
+ url_for_page(1)
44
+ end
40
45
 
41
- unless collection.current_page == collection.total_pages
42
- pages[:next] = collection.current_page + FIRST_PAGE
43
- pages[:last] = collection.total_pages
44
- end
46
+ def last_page_url
47
+ if collection.total_pages == 0
48
+ url_for_page(FIRST_PAGE)
49
+ else
50
+ url_for_page(collection.total_pages)
45
51
  end
46
52
  end
47
53
 
48
- def url(options)
54
+ def prev_page_url
55
+ return nil if collection.current_page == FIRST_PAGE
56
+ url_for_page(collection.current_page - FIRST_PAGE)
57
+ end
58
+
59
+ def next_page_url
60
+ return nil if collection.total_pages == 0 || collection.current_page == collection.total_pages
61
+ url_for_page(collection.next_page)
62
+ end
63
+
64
+ def url_for_page(number)
65
+ params = query_parameters.dup
66
+ params[:page] = { size: per_page, number: number }
67
+ "#{url(adapter_options)}?#{params.to_query}"
68
+ end
69
+
70
+ def url(options = {})
49
71
  @url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url
50
72
  end
51
73
 
@@ -56,6 +78,10 @@ module ActiveModelSerializers
56
78
  def query_parameters
57
79
  @query_parameters ||= context.query_parameters
58
80
  end
81
+
82
+ def per_page
83
+ @per_page ||= collection.try(:per_page) || collection.try(:limit_value) || collection.size
84
+ end
59
85
  end
60
86
  end
61
87
  end
@@ -5,47 +5,99 @@ module ActiveModelSerializers
5
5
  # {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links}
6
6
  # {http://jsonapi.org/format/#document-links Document Links}
7
7
  # {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, {})
8
+ # {http://jsonapi.org/format/#document-meta Document Meta}
9
+ def initialize(parent_serializer, serializable_resource_options, association)
10
+ @parent_serializer = parent_serializer
11
+ @association = association
13
12
  @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
13
  end
21
14
 
22
15
  def as_json
23
16
  hash = {}
24
- hash[:data] = data if association_options[:include_data]
25
- links = self.links
17
+
18
+ hash[:data] = data_for(association) if association.include_data?
19
+
20
+ links = links_for(association)
26
21
  hash[:links] = links if links.any?
27
- meta = self.meta
22
+
23
+ meta = meta_for(association)
28
24
  hash[:meta] = meta if meta
25
+ hash[:meta] = {} if hash.empty?
29
26
 
30
27
  hash
31
28
  end
32
29
 
33
30
  protected
34
31
 
35
- attr_reader :object, :scope, :data, :serializable_resource_options,
36
- :association_options, :links, :meta
32
+ attr_reader :parent_serializer, :serializable_resource_options, :association
37
33
 
38
34
  private
39
35
 
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
36
+ # TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self
37
+ def data_for(association)
38
+ if association.collection?
39
+ data_for_many(association)
40
+ else
41
+ data_for_one(association)
47
42
  end
48
43
  end
44
+
45
+ def data_for_one(association)
46
+ if belongs_to_id_on_self?(association)
47
+ id = parent_serializer.read_attribute_for_serialization(association.reflection.foreign_key)
48
+ type =
49
+ if association.polymorphic?
50
+ # We can't infer resource type for polymorphic relationships from the serializer.
51
+ # We can ONLY know a polymorphic resource type by inspecting each resource.
52
+ association.lazy_association.serializer.json_key
53
+ else
54
+ association.reflection.type.to_s
55
+ end
56
+ ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options)
57
+ else
58
+ # TODO(BF): Process relationship without evaluating lazy_association
59
+ serializer = association.lazy_association.serializer
60
+ if (virtual_value = association.virtual_value)
61
+ virtual_value
62
+ elsif serializer && association.object
63
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
64
+ else
65
+ nil
66
+ end
67
+ end
68
+ end
69
+
70
+ def data_for_many(association)
71
+ # TODO(BF): Process relationship without evaluating lazy_association
72
+ collection_serializer = association.lazy_association.serializer
73
+ if collection_serializer.respond_to?(:each)
74
+ collection_serializer.map do |serializer|
75
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
76
+ end
77
+ elsif (virtual_value = association.virtual_value)
78
+ virtual_value
79
+ else
80
+ []
81
+ end
82
+ end
83
+
84
+ def links_for(association)
85
+ association.links.each_with_object({}) do |(key, value), hash|
86
+ result = Link.new(parent_serializer, value).as_json
87
+ hash[key] = result if result
88
+ end
89
+ end
90
+
91
+ def meta_for(association)
92
+ meta = association.meta
93
+ meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
94
+ end
95
+
96
+ def belongs_to_id_on_self?(association)
97
+ parent_serializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship &&
98
+ association.belongs_to? &&
99
+ parent_serializer.object.respond_to?(association.reflection.foreign_key)
100
+ end
49
101
  end
50
102
  end
51
103
  end
@@ -2,15 +2,48 @@ module ActiveModelSerializers
2
2
  module Adapter
3
3
  class JsonApi
4
4
  class ResourceIdentifier
5
+ def self.type_for(serializer, serializer_type = nil, transform_options = {})
6
+ raw_type = serializer_type ? serializer_type : raw_type_from_serializer_object(serializer.object)
7
+ JsonApi.send(:transform_key_casing!, raw_type, transform_options)
8
+ end
9
+
10
+ def self.for_type_with_id(type, id, options)
11
+ type = inflect_type(type)
12
+ type = type_for(:no_class_needed, type, options)
13
+ if id.blank?
14
+ { type: type }
15
+ else
16
+ { id: id.to_s, type: type }
17
+ end
18
+ end
19
+
20
+ def self.raw_type_from_serializer_object(object)
21
+ class_name = object.class.name # should use model_name
22
+ raw_type = class_name.underscore
23
+ raw_type = inflect_type(raw_type)
24
+ raw_type
25
+ .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator)
26
+ raw_type
27
+ end
28
+
29
+ def self.inflect_type(type)
30
+ singularize = ActiveModelSerializers.config.jsonapi_resource_type == :singular
31
+ inflection = singularize ? :singularize : :pluralize
32
+ ActiveSupport::Inflector.public_send(inflection, type)
33
+ end
34
+
5
35
  # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
6
36
  def initialize(serializer, options)
7
37
  @id = id_for(serializer)
8
- @type = JsonApi.send(:transform_key_casing!, type_for(serializer),
9
- options)
38
+ @type = type_for(serializer, options)
10
39
  end
11
40
 
12
41
  def as_json
13
- { id: id, type: type }
42
+ if id.blank?
43
+ { type: type }
44
+ else
45
+ { id: id.to_s, type: type }
46
+ end
14
47
  end
15
48
 
16
49
  protected
@@ -19,13 +52,9 @@ module ActiveModelSerializers
19
52
 
20
53
  private
21
54
 
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
55
+ def type_for(serializer, transform_options)
56
+ serializer_type = serializer._type
57
+ self.class.type_for(serializer, serializer_type, transform_options)
29
58
  end
30
59
 
31
60
  def id_for(serializer)
@@ -31,16 +31,27 @@ module ActiveModelSerializers
31
31
  autoload :Error
32
32
  autoload :Deserialization
33
33
 
34
+ def self.default_key_transform
35
+ :dash
36
+ end
37
+
38
+ def self.fragment_cache(cached_hash, non_cached_hash, root = true)
39
+ core_cached = cached_hash.first
40
+ core_non_cached = non_cached_hash.first
41
+ no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
42
+ no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
43
+ cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
44
+ hash = root ? { root => cached_resource } : cached_resource
45
+
46
+ hash.deep_merge no_root_non_cache.deep_merge no_root_cache
47
+ end
48
+
34
49
  def initialize(serializer, options = {})
35
50
  super
36
- @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include])
51
+ @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
37
52
  @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
38
53
  end
39
54
 
40
- def self.default_key_transform
41
- :dash
42
- end
43
-
44
55
  # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
45
56
  # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
46
57
  def serializable_hash(*)
@@ -52,6 +63,11 @@ module ActiveModelSerializers
52
63
  self.class.transform_key_casing!(document, instance_options)
53
64
  end
54
65
 
66
+ def fragment_cache(cached_hash, non_cached_hash)
67
+ root = !instance_options.include?(:include)
68
+ self.class.fragment_cache(cached_hash, non_cached_hash, root)
69
+ end
70
+
55
71
  # {http://jsonapi.org/format/#document-top-level Primary data}
56
72
  # definition:
57
73
  # ☐ toplevel_data (required)
@@ -174,18 +190,6 @@ module ActiveModelSerializers
174
190
  hash
175
191
  end
176
192
 
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
193
  protected
190
194
 
191
195
  attr_reader :fieldset
@@ -231,17 +235,17 @@ module ActiveModelSerializers
231
235
  @primary = []
232
236
  @included = []
233
237
  @resource_identifiers = Set.new
234
- serializers.each { |serializer| process_resource(serializer, true) }
235
- serializers.each { |serializer| process_relationships(serializer, @include_tree) }
238
+ serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
239
+ serializers.each { |serializer| process_relationships(serializer, @include_directive) }
236
240
 
237
241
  [@primary, @included]
238
242
  end
239
243
 
240
- def process_resource(serializer, primary)
244
+ def process_resource(serializer, primary, include_slice = {})
241
245
  resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
242
246
  return false unless @resource_identifiers.add?(resource_identifier)
243
247
 
244
- resource_object = resource_object_for(serializer)
248
+ resource_object = resource_object_for(serializer, include_slice)
245
249
  if primary
246
250
  @primary << resource_object
247
251
  else
@@ -251,21 +255,22 @@ module ActiveModelSerializers
251
255
  true
252
256
  end
253
257
 
254
- def process_relationships(serializer, include_tree)
255
- serializer.associations(include_tree).each do |association|
256
- process_relationship(association.serializer, include_tree[association.key])
258
+ def process_relationships(serializer, include_slice)
259
+ serializer.associations(include_slice).each do |association|
260
+ # TODO(BF): Process relationship without evaluating lazy_association
261
+ process_relationship(association.lazy_association.serializer, include_slice[association.key])
257
262
  end
258
263
  end
259
264
 
260
- def process_relationship(serializer, include_tree)
265
+ def process_relationship(serializer, include_slice)
261
266
  if serializer.respond_to?(:each)
262
- serializer.each { |s| process_relationship(s, include_tree) }
267
+ serializer.each { |s| process_relationship(s, include_slice) }
263
268
  return
264
269
  end
265
270
  return unless serializer && serializer.object
266
- return unless process_resource(serializer, false)
271
+ return unless process_resource(serializer, false, include_slice)
267
272
 
268
- process_relationships(serializer, include_tree)
273
+ process_relationships(serializer, include_slice)
269
274
  end
270
275
 
271
276
  # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
@@ -289,21 +294,9 @@ module ActiveModelSerializers
289
294
  end
290
295
 
291
296
  # {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
297
+ def resource_object_for(serializer, include_slice = {})
298
+ resource_object = data_for(serializer, include_slice)
301
299
 
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
300
  # toplevel_links
308
301
  # definition:
309
302
  # allOf
@@ -317,7 +310,10 @@ module ActiveModelSerializers
317
310
  # prs:
318
311
  # https://github.com/rails-api/active_model_serializers/pull/1247
319
312
  # https://github.com/rails-api/active_model_serializers/pull/1018
320
- resource_object[:links] = links if links.any?
313
+ if (links = links_for(serializer)).any?
314
+ resource_object ||= {}
315
+ resource_object[:links] = links
316
+ end
321
317
 
322
318
  # toplevel_meta
323
319
  # alias meta
@@ -327,12 +323,33 @@ module ActiveModelSerializers
327
323
  # {
328
324
  # :'git-ref' => 'abc123'
329
325
  # }
330
- meta = meta_for(serializer)
331
- resource_object[:meta] = meta unless meta.blank?
326
+ if (meta = meta_for(serializer)).present?
327
+ resource_object ||= {}
328
+ resource_object[:meta] = meta
329
+ end
332
330
 
333
331
  resource_object
334
332
  end
335
333
 
334
+ def data_for(serializer, include_slice)
335
+ data = serializer.fetch(self) do
336
+ resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
337
+ break nil if resource_object.nil?
338
+
339
+ requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
340
+ attributes = attributes_for(serializer, requested_fields)
341
+ resource_object[:attributes] = attributes if attributes.any?
342
+ resource_object
343
+ end
344
+ data.tap do |resource_object|
345
+ next if resource_object.nil?
346
+ # NOTE(BF): the attributes are cached above, separately from the relationships, below.
347
+ requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
348
+ relationships = relationships_for(serializer, requested_associations, include_slice)
349
+ resource_object[:relationships] = relationships if relationships.any?
350
+ end
351
+ end
352
+
336
353
  # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
337
354
  # relationships
338
355
  # definition:
@@ -428,17 +445,13 @@ module ActiveModelSerializers
428
445
  # id: 'required-id',
429
446
  # meta: meta
430
447
  # }.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
448
+ def relationships_for(serializer, requested_associations, include_slice)
449
+ include_directive = JSONAPI::IncludeDirective.new(
450
+ requested_associations,
451
+ allow_wildcard: true
452
+ )
453
+ serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
454
+ hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
442
455
  end
443
456
  end
444
457
 
@@ -467,7 +480,8 @@ module ActiveModelSerializers
467
480
  # }.reject! {|_,v| v.nil? }
468
481
  def links_for(serializer)
469
482
  serializer._links.each_with_object({}) do |(name, value), hash|
470
- hash[name] = Link.new(serializer, value).as_json
483
+ result = Link.new(serializer, value).as_json
484
+ hash[name] = result if result
471
485
  end
472
486
  end
473
487
 
@@ -5,11 +5,13 @@ module ActiveModelSerializers
5
5
  private_constant :ADAPTER_MAP if defined?(private_constant)
6
6
 
7
7
  class << self # All methods are class functions
8
+ # :nocov:
8
9
  def new(*args)
9
10
  fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
10
11
  "Adapter.new called with args: '#{args.inspect}', from" \
11
12
  "'caller[0]'."
12
13
  end
14
+ # :nocov:
13
15
 
14
16
  def configured_adapter
15
17
  lookup(ActiveModelSerializers.config.adapter)
@@ -51,6 +53,10 @@ module ActiveModelSerializers
51
53
  self
52
54
  end
53
55
 
56
+ def registered_name(adapter_class)
57
+ ADAPTER_MAP.key adapter_class
58
+ end
59
+
54
60
  # @param adapter [String, Symbol, Class] name to fetch adapter by
55
61
  # @return [ActiveModelSerializers::Adapter] subclass of Adapter
56
62
  # @raise [UnknownAdapterError]
@@ -36,8 +36,7 @@ module ActiveModelSerializers
36
36
  target = is_a?(Module) ? "#{self}." : "#{self.class}#"
37
37
  msg = ["NOTE: #{target}#{name} is deprecated",
38
38
  replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
39
- "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(":")}"
40
- ]
39
+ "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(':')}"]
41
40
  warn "#{msg.join}."
42
41
  send old, *args, &block
43
42
  end
@@ -6,8 +6,10 @@ module ActiveModelSerializers
6
6
  Adapter::JsonApi::Deserialization.parse(*args)
7
7
  end
8
8
 
9
+ # :nocov:
9
10
  def jsonapi_parse!(*args)
10
11
  Adapter::JsonApi::Deserialization.parse!(*args)
11
12
  end
13
+ # :nocov:
12
14
  end
13
15
  end
@@ -0,0 +1,80 @@
1
+ module ActiveModelSerializers
2
+ module LookupChain
3
+ # Standard appending of Serializer to the resource name.
4
+ #
5
+ # Example:
6
+ # Author => AuthorSerializer
7
+ BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
8
+ serializer_from(resource_class)
9
+ end
10
+
11
+ # Uses the namespace of the resource to find the serializer
12
+ #
13
+ # Example:
14
+ # British::Author => British::AuthorSerializer
15
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
16
+ resource_namespace = namespace_for(resource_class)
17
+ serializer_name = serializer_from(resource_class)
18
+
19
+ "#{resource_namespace}::#{serializer_name}"
20
+ end
21
+
22
+ # Uses the controller namespace of the resource to find the serializer
23
+ #
24
+ # Example:
25
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
26
+ BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
27
+ resource_name = resource_class_name(resource_class)
28
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
29
+ end
30
+
31
+ # Allows for serializers to be defined in parent serializers
32
+ # - useful if a relationship only needs a different set of attributes
33
+ # than if it were rendered independently.
34
+ #
35
+ # Example:
36
+ # class BlogSerializer < ActiveModel::Serializer
37
+ # class AuthorSerialier < ActiveModel::Serializer
38
+ # ...
39
+ # end
40
+ #
41
+ # belongs_to :author
42
+ # ...
43
+ # end
44
+ #
45
+ # The belongs_to relationship would be rendered with
46
+ # BlogSerializer::AuthorSerialier
47
+ BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
48
+ return if serializer_class == ActiveModel::Serializer
49
+
50
+ serializer_name = serializer_from(resource_class)
51
+ "#{serializer_class}::#{serializer_name}"
52
+ end
53
+
54
+ DEFAULT = [
55
+ BY_PARENT_SERIALIZER,
56
+ BY_NAMESPACE,
57
+ BY_RESOURCE_NAMESPACE,
58
+ BY_RESOURCE
59
+ ].freeze
60
+
61
+ module_function
62
+
63
+ def namespace_for(klass)
64
+ klass.name.deconstantize
65
+ end
66
+
67
+ def resource_class_name(klass)
68
+ klass.name.demodulize
69
+ end
70
+
71
+ def serializer_from_resource_name(name)
72
+ "#{name}Serializer"
73
+ end
74
+
75
+ def serializer_from(klass)
76
+ name = resource_class_name(klass)
77
+ serializer_from_resource_name(name)
78
+ end
79
+ end
80
+ end