active_model_serializers 0.10.0 → 0.10.3

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -4
  3. data/.travis.yml +9 -1
  4. data/CHANGELOG.md +81 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/README.md +24 -24
  8. data/Rakefile +3 -3
  9. data/active_model_serializers.gemspec +20 -24
  10. data/docs/ARCHITECTURE.md +6 -7
  11. data/docs/README.md +2 -0
  12. data/docs/general/adapters.md +4 -2
  13. data/docs/general/caching.md +7 -1
  14. data/docs/general/configuration_options.md +70 -1
  15. data/docs/general/deserialization.md +1 -1
  16. data/docs/general/fields.md +31 -0
  17. data/docs/general/rendering.md +42 -3
  18. data/docs/general/serializers.md +97 -8
  19. data/docs/howto/add_pagination_links.md +4 -5
  20. data/docs/howto/add_relationship_links.md +137 -0
  21. data/docs/howto/add_root_key.md +4 -0
  22. data/docs/howto/grape_integration.md +42 -0
  23. data/docs/howto/outside_controller_use.md +9 -2
  24. data/docs/howto/passing_arbitrary_options.md +2 -2
  25. data/docs/howto/test.md +2 -0
  26. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  27. data/docs/integrations/ember-and-json-api.md +64 -32
  28. data/docs/jsonapi/schema.md +1 -1
  29. data/lib/action_controller/serialization.rb +13 -3
  30. data/lib/active_model/serializer/adapter/base.rb +2 -0
  31. data/lib/active_model/serializer/array_serializer.rb +8 -5
  32. data/lib/active_model/serializer/association.rb +19 -4
  33. data/lib/active_model/serializer/belongs_to_reflection.rb +0 -3
  34. data/lib/active_model/serializer/collection_serializer.rb +35 -12
  35. data/lib/active_model/serializer/{associations.rb → concerns/associations.rb} +13 -11
  36. data/lib/active_model/serializer/{attributes.rb → concerns/attributes.rb} +0 -0
  37. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +72 -113
  38. data/lib/active_model/serializer/{configuration.rb → concerns/configuration.rb} +25 -1
  39. data/lib/active_model/serializer/{links.rb → concerns/links.rb} +0 -0
  40. data/lib/active_model/serializer/{meta.rb → concerns/meta.rb} +0 -0
  41. data/lib/active_model/serializer/{type.rb → concerns/type.rb} +0 -0
  42. data/lib/active_model/serializer/error_serializer.rb +11 -7
  43. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  44. data/lib/active_model/serializer/has_many_reflection.rb +0 -3
  45. data/lib/active_model/serializer/has_one_reflection.rb +0 -3
  46. data/lib/active_model/serializer/lint.rb +134 -130
  47. data/lib/active_model/serializer/reflection.rb +37 -21
  48. data/lib/active_model/serializer/version.rb +1 -1
  49. data/lib/active_model/serializer.rb +76 -37
  50. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  51. data/lib/active_model_serializers/adapter/base.rb +38 -38
  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 +8 -1
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +30 -19
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +23 -9
  56. data/lib/active_model_serializers/adapter/json_api.rb +44 -43
  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/key_transform.rb +4 -0
  61. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  62. data/lib/active_model_serializers/model.rb +4 -2
  63. data/lib/active_model_serializers/railtie.rb +3 -1
  64. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  65. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  66. data/lib/active_model_serializers/serialization_context.rb +10 -3
  67. data/lib/active_model_serializers.rb +7 -0
  68. data/lib/generators/rails/serializer_generator.rb +4 -4
  69. data/lib/grape/active_model_serializers.rb +7 -5
  70. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  71. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  72. data/test/action_controller/adapter_selector_test.rb +4 -4
  73. data/test/action_controller/explicit_serializer_test.rb +5 -4
  74. data/test/action_controller/json/include_test.rb +106 -27
  75. data/test/action_controller/json_api/errors_test.rb +6 -7
  76. data/test/action_controller/json_api/fields_test.rb +57 -0
  77. data/test/action_controller/json_api/linked_test.rb +29 -24
  78. data/test/action_controller/json_api/pagination_test.rb +19 -19
  79. data/test/action_controller/json_api/transform_test.rb +3 -3
  80. data/test/action_controller/lookup_proc_test.rb +49 -0
  81. data/test/action_controller/namespace_lookup_test.rb +226 -0
  82. data/test/action_controller/serialization_test.rb +10 -7
  83. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  84. data/test/active_model_serializers/key_transform_test.rb +286 -252
  85. data/test/active_model_serializers/model_test.rb +17 -4
  86. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +143 -0
  87. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  88. data/test/adapter/attributes_test.rb +43 -0
  89. data/test/adapter/json/collection_test.rb +14 -0
  90. data/test/adapter/json/transform_test.rb +15 -15
  91. data/test/adapter/json_api/collection_test.rb +4 -3
  92. data/test/adapter/json_api/errors_test.rb +17 -19
  93. data/test/adapter/json_api/fields_test.rb +4 -3
  94. data/test/adapter/json_api/has_many_test.rb +39 -18
  95. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +166 -0
  96. data/test/adapter/json_api/json_api_test.rb +5 -7
  97. data/test/adapter/json_api/linked_test.rb +33 -12
  98. data/test/adapter/json_api/links_test.rb +4 -2
  99. data/test/adapter/json_api/pagination_links_test.rb +35 -8
  100. data/test/adapter/json_api/relationship_test.rb +309 -73
  101. data/test/adapter/json_api/resource_identifier_test.rb +27 -2
  102. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  103. data/test/adapter/json_api/transform_test.rb +255 -253
  104. data/test/adapter/json_api/type_test.rb +1 -1
  105. data/test/adapter/json_test.rb +8 -7
  106. data/test/adapter/null_test.rb +1 -2
  107. data/test/adapter/polymorphic_test.rb +5 -5
  108. data/test/adapter_test.rb +1 -1
  109. data/test/benchmark/app.rb +1 -1
  110. data/test/benchmark/benchmarking_support.rb +1 -1
  111. data/test/benchmark/bm_active_record.rb +81 -0
  112. data/test/benchmark/bm_adapter.rb +38 -0
  113. data/test/benchmark/bm_caching.rb +16 -16
  114. data/test/benchmark/bm_lookup_chain.rb +83 -0
  115. data/test/benchmark/bm_transform.rb +16 -5
  116. data/test/benchmark/controllers.rb +16 -17
  117. data/test/benchmark/fixtures.rb +72 -72
  118. data/test/cache_test.rb +143 -49
  119. data/test/collection_serializer_test.rb +3 -3
  120. data/test/fixtures/poro.rb +52 -48
  121. data/test/generators/serializer_generator_test.rb +22 -5
  122. data/test/grape_test.rb +152 -56
  123. data/test/lint_test.rb +1 -1
  124. data/test/logger_test.rb +13 -11
  125. data/test/serializable_resource_test.rb +18 -22
  126. data/test/serializers/association_macros_test.rb +3 -2
  127. data/test/serializers/associations_test.rb +107 -32
  128. data/test/serializers/attribute_test.rb +2 -2
  129. data/test/serializers/attributes_test.rb +1 -1
  130. data/test/serializers/fieldset_test.rb +1 -1
  131. data/test/serializers/meta_test.rb +12 -6
  132. data/test/serializers/root_test.rb +1 -1
  133. data/test/serializers/serializer_for_test.rb +6 -4
  134. data/test/serializers/serializer_for_with_namespace_test.rb +87 -0
  135. data/test/support/isolated_unit.rb +5 -2
  136. data/test/support/rails5_shims.rb +8 -2
  137. data/test/support/rails_app.rb +0 -9
  138. data/test/support/serialization_testing.rb +23 -5
  139. data/test/test_helper.rb +1 -0
  140. metadata +85 -34
  141. data/.rubocop_todo.yml +0 -167
  142. data/lib/active_model/serializer/include_tree.rb +0 -111
  143. data/test/adapter/json_api/relationships_test.rb +0 -199
  144. data/test/include_tree/from_include_args_test.rb +0 -26
  145. data/test/include_tree/from_string_test.rb +0 -94
  146. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,75 +1,12 @@
1
1
  module ActiveModelSerializers
2
2
  module Adapter
3
3
  class Attributes < Base
4
- def initialize(serializer, options = {})
5
- super
6
- @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
7
- @cached_attributes = options[:cache_attributes] || {}
8
- end
9
-
10
4
  def serializable_hash(options = nil)
11
5
  options = serialization_options(options)
6
+ options[:fields] ||= instance_options[:fields]
7
+ serialized_hash = serializer.serializable_hash(instance_options, options, self)
12
8
 
13
- if serializer.respond_to?(:each)
14
- serializable_hash_for_collection(options)
15
- else
16
- serializable_hash_for_single_resource(options)
17
- end
18
- end
19
-
20
- private
21
-
22
- def serializable_hash_for_collection(options)
23
- cache_attributes
24
-
25
- serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
26
- end
27
-
28
- def serializable_hash_for_single_resource(options)
29
- resource = resource_object_for(options)
30
- relationships = resource_relationships(options)
31
- resource.merge(relationships)
32
- end
33
-
34
- def resource_relationships(options)
35
- relationships = {}
36
- serializer.associations(@include_tree).each do |association|
37
- relationships[association.key] ||= relationship_value_for(association, options)
38
- end
39
-
40
- relationships
41
- end
42
-
43
- def relationship_value_for(association, options)
44
- return association.options[:virtual_value] if association.options[:virtual_value]
45
- return unless association.serializer && association.serializer.object
46
-
47
- opts = instance_options.merge(include: @include_tree[association.key])
48
- relationship_value = Attributes.new(association.serializer, opts).serializable_hash(options)
49
-
50
- if association.options[:polymorphic] && relationship_value
51
- polymorphic_type = association.serializer.object.class.name.underscore
52
- relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
53
- end
54
-
55
- relationship_value
56
- end
57
-
58
- # Set @cached_attributes
59
- def cache_attributes
60
- return if @cached_attributes.present?
61
-
62
- @cached_attributes = ActiveModel::Serializer.cache_read_multi(serializer, self, @include_tree)
63
- end
64
-
65
- def resource_object_for(options)
66
- if serializer.class.cache_enabled?
67
- @cached_attributes.fetch(serializer.cache_key(self)) do
68
- serializer.cached_fields(options[:fields], self)
69
- end
70
- else
71
- serializer.cached_fields(options[:fields], self)
72
- end
9
+ self.class.transform_key_casing!(serialized_hash, instance_options)
73
10
  end
74
11
  end
75
12
  end
@@ -8,6 +8,40 @@ module ActiveModelSerializers
8
8
  ActiveModelSerializers::Adapter.register(subclass)
9
9
  end
10
10
 
11
+ # Sets the default transform for the adapter.
12
+ #
13
+ # @return [Symbol] the default transform for the adapter
14
+ def self.default_key_transform
15
+ :unaltered
16
+ end
17
+
18
+ # Determines the transform to use in order of precedence:
19
+ # adapter option, global config, adapter default.
20
+ #
21
+ # @param options [Object]
22
+ # @return [Symbol] the transform to use
23
+ def self.transform(options)
24
+ return options[:key_transform] if options && options[:key_transform]
25
+ ActiveModelSerializers.config.key_transform || default_key_transform
26
+ end
27
+
28
+ # Transforms the casing of the supplied value.
29
+ #
30
+ # @param value [Object] the value to be transformed
31
+ # @param options [Object] serializable resource options
32
+ # @return [Symbol] the default transform for the adapter
33
+ def self.transform_key_casing!(value, options)
34
+ KeyTransform.send(transform(options), value)
35
+ end
36
+
37
+ def self.cache_key
38
+ @cache_key ||= ActiveModelSerializers::Adapter.registered_name(self)
39
+ end
40
+
41
+ def self.fragment_cache(cached_hash, non_cached_hash)
42
+ non_cached_hash.merge cached_hash
43
+ end
44
+
11
45
  attr_reader :serializer, :instance_options
12
46
 
13
47
  def initialize(serializer, options = {})
@@ -15,10 +49,6 @@ module ActiveModelSerializers
15
49
  @instance_options = options
16
50
  end
17
51
 
18
- def cached_name
19
- @cached_name ||= self.class.name.demodulize.underscore
20
- end
21
-
22
52
  # Subclasses that implement this method must first call
23
53
  # options = serialization_options(options)
24
54
  def serializable_hash(_options = nil)
@@ -29,14 +59,12 @@ module ActiveModelSerializers
29
59
  serializable_hash(options)
30
60
  end
31
61
 
32
- def fragment_cache(cached_hash, non_cached_hash)
33
- non_cached_hash.merge cached_hash
62
+ def cache_key
63
+ self.class.cache_key
34
64
  end
35
65
 
36
- def cache_check(serializer)
37
- serializer.cache_check(self) do
38
- yield
39
- end
66
+ def fragment_cache(cached_hash, non_cached_hash)
67
+ self.class.fragment_cache(cached_hash, non_cached_hash)
40
68
  end
41
69
 
42
70
  private
@@ -50,34 +78,6 @@ module ActiveModelSerializers
50
78
  def root
51
79
  serializer.json_key.to_sym if serializer.json_key
52
80
  end
53
-
54
- class << self
55
- # Sets the default transform for the adapter.
56
- #
57
- # @return [Symbol] the default transform for the adapter
58
- def default_key_transform
59
- :unaltered
60
- end
61
-
62
- # Determines the transform to use in order of precedence:
63
- # adapter option, global config, adapter default.
64
- #
65
- # @param options [Object]
66
- # @return [Symbol] the transform to use
67
- def transform(options)
68
- return options[:key_transform] if options && options[:key_transform]
69
- ActiveModelSerializers.config.key_transform || default_key_transform
70
- end
71
-
72
- # Transforms the casing of the supplied value.
73
- #
74
- # @param value [Object] the value to be transformed
75
- # @param options [Object] serializable resource options
76
- # @return [Symbol] the default transform for the adapter
77
- def transform_key_casing!(value, options)
78
- KeyTransform.send(transform(options), value)
79
- end
80
- end
81
81
  end
82
82
  end
83
83
  end
@@ -71,7 +71,7 @@ module ActiveModelSerializers
71
71
  hash[:href] = @href if defined?(@href)
72
72
  hash[:meta] = @meta if defined?(@meta)
73
73
 
74
- hash
74
+ hash.any? ? hash : nil
75
75
  end
76
76
 
77
77
  protected
@@ -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,7 +10,13 @@ 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
@@ -5,47 +5,58 @@ 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
+ if association.options[:include_data]
19
+ hash[:data] = data_for(association)
20
+ end
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)
38
+ def data_for(association)
39
+ serializer = association.serializer
41
40
  if serializer.respond_to?(:each)
42
41
  serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
43
- elsif association_options[:virtual_value]
44
- association_options[:virtual_value]
42
+ elsif (virtual_value = association.options[:virtual_value])
43
+ virtual_value
45
44
  elsif serializer && serializer.object
46
45
  ResourceIdentifier.new(serializer, serializable_resource_options).as_json
47
46
  end
48
47
  end
48
+
49
+ def links_for(association)
50
+ association.links.each_with_object({}) do |(key, value), hash|
51
+ result = Link.new(parent_serializer, value).as_json
52
+ hash[key] = result if result
53
+ end
54
+ end
55
+
56
+ def meta_for(association)
57
+ meta = association.meta
58
+ meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
59
+ end
49
60
  end
50
61
  end
51
62
  end
@@ -2,11 +2,30 @@ module ActiveModelSerializers
2
2
  module Adapter
3
3
  class JsonApi
4
4
  class ResourceIdentifier
5
+ def self.type_for(class_name, serializer_type = nil, transform_options = {})
6
+ if serializer_type
7
+ raw_type = serializer_type
8
+ else
9
+ inflection =
10
+ if ActiveModelSerializers.config.jsonapi_resource_type == :singular
11
+ :singularize
12
+ else
13
+ :pluralize
14
+ end
15
+
16
+ raw_type = class_name.underscore
17
+ raw_type = ActiveSupport::Inflector.public_send(inflection, raw_type)
18
+ raw_type
19
+ .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator)
20
+ raw_type
21
+ end
22
+ JsonApi.send(:transform_key_casing!, raw_type, transform_options)
23
+ end
24
+
5
25
  # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
6
26
  def initialize(serializer, options)
7
27
  @id = id_for(serializer)
8
- @type = JsonApi.send(:transform_key_casing!, type_for(serializer),
9
- options)
28
+ @type = type_for(serializer, options)
10
29
  end
11
30
 
12
31
  def as_json
@@ -19,13 +38,8 @@ module ActiveModelSerializers
19
38
 
20
39
  private
21
40
 
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
41
+ def type_for(serializer, transform_options)
42
+ self.class.type_for(serializer.object.class.name, serializer._type, transform_options)
29
43
  end
30
44
 
31
45
  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,21 @@ 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
+ process_relationship(association.serializer, include_slice[association.key])
257
261
  end
258
262
  end
259
263
 
260
- def process_relationship(serializer, include_tree)
264
+ def process_relationship(serializer, include_slice)
261
265
  if serializer.respond_to?(:each)
262
- serializer.each { |s| process_relationship(s, include_tree) }
266
+ serializer.each { |s| process_relationship(s, include_slice) }
263
267
  return
264
268
  end
265
269
  return unless serializer && serializer.object
266
- return unless process_resource(serializer, false)
270
+ return unless process_resource(serializer, false, include_slice)
267
271
 
268
- process_relationships(serializer, include_tree)
272
+ process_relationships(serializer, include_slice)
269
273
  end
270
274
 
271
275
  # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
@@ -289,8 +293,8 @@ module ActiveModelSerializers
289
293
  end
290
294
 
291
295
  # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
292
- def resource_object_for(serializer)
293
- resource_object = cache_check(serializer) do
296
+ def resource_object_for(serializer, include_slice = {})
297
+ resource_object = serializer.fetch(self) do
294
298
  resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
295
299
 
296
300
  requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
@@ -300,7 +304,7 @@ module ActiveModelSerializers
300
304
  end
301
305
 
302
306
  requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
303
- relationships = relationships_for(serializer, requested_associations)
307
+ relationships = relationships_for(serializer, requested_associations, include_slice)
304
308
  resource_object[:relationships] = relationships if relationships.any?
305
309
 
306
310
  links = links_for(serializer)
@@ -428,17 +432,13 @@ module ActiveModelSerializers
428
432
  # id: 'required-id',
429
433
  # meta: meta
430
434
  # }.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
435
+ def relationships_for(serializer, requested_associations, include_slice)
436
+ include_directive = JSONAPI::IncludeDirective.new(
437
+ requested_associations,
438
+ allow_wildcard: true
439
+ )
440
+ serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
441
+ hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
442
442
  end
443
443
  end
444
444
 
@@ -467,7 +467,8 @@ module ActiveModelSerializers
467
467
  # }.reject! {|_,v| v.nil? }
468
468
  def links_for(serializer)
469
469
  serializer._links.each_with_object({}) do |(name, value), hash|
470
- hash[name] = Link.new(serializer, value).as_json
470
+ result = Link.new(serializer, value).as_json
471
+ hash[name] = result if result
471
472
  end
472
473
  end
473
474
 
@@ -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
@@ -11,6 +11,7 @@ module ActiveModelSerializers
11
11
  # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
12
12
  def camel(value)
13
13
  case value
14
+ when Array then value.map { |item| camel(item) }
14
15
  when Hash then value.deep_transform_keys! { |key| camel(key) }
15
16
  when Symbol then camel(value.to_s).to_sym
16
17
  when String then value.underscore.camelize
@@ -25,6 +26,7 @@ module ActiveModelSerializers
25
26
  # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
26
27
  def camel_lower(value)
27
28
  case value
29
+ when Array then value.map { |item| camel_lower(item) }
28
30
  when Hash then value.deep_transform_keys! { |key| camel_lower(key) }
29
31
  when Symbol then camel_lower(value.to_s).to_sym
30
32
  when String then value.underscore.camelize(:lower)
@@ -40,6 +42,7 @@ module ActiveModelSerializers
40
42
  # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize}
41
43
  def dash(value)
42
44
  case value
45
+ when Array then value.map { |item| dash(item) }
43
46
  when Hash then value.deep_transform_keys! { |key| dash(key) }
44
47
  when Symbol then dash(value.to_s).to_sym
45
48
  when String then value.underscore.dasherize
@@ -55,6 +58,7 @@ module ActiveModelSerializers
55
58
  # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L89-L98 ActiveSupport::Inflector.underscore}
56
59
  def underscore(value)
57
60
  case value
61
+ when Array then value.map { |item| underscore(item) }
58
62
  when Hash then value.deep_transform_keys! { |key| underscore(key) }
59
63
  when Symbol then underscore(value.to_s).to_sym
60
64
  when String then value.underscore
@@ -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