active_model_serializers_custom 0.10.90

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 (215) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +29 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  4. data/.gitignore +35 -0
  5. data/.rubocop.yml +109 -0
  6. data/.simplecov +110 -0
  7. data/.travis.yml +63 -0
  8. data/CHANGELOG.md +727 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +74 -0
  12. data/MIT-LICENSE +22 -0
  13. data/README.md +305 -0
  14. data/Rakefile +76 -0
  15. data/active_model_serializers.gemspec +64 -0
  16. data/appveyor.yml +28 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/rubocop +38 -0
  20. data/bin/serve_benchmark +39 -0
  21. data/docs/README.md +41 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +269 -0
  24. data/docs/general/caching.md +58 -0
  25. data/docs/general/configuration_options.md +185 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/fields.md +31 -0
  28. data/docs/general/getting_started.md +133 -0
  29. data/docs/general/instrumentation.md +40 -0
  30. data/docs/general/key_transforms.md +40 -0
  31. data/docs/general/logging.md +21 -0
  32. data/docs/general/rendering.md +293 -0
  33. data/docs/general/serializers.md +495 -0
  34. data/docs/how-open-source-maintained.jpg +0 -0
  35. data/docs/howto/add_pagination_links.md +138 -0
  36. data/docs/howto/add_relationship_links.md +140 -0
  37. data/docs/howto/add_root_key.md +62 -0
  38. data/docs/howto/grape_integration.md +42 -0
  39. data/docs/howto/outside_controller_use.md +66 -0
  40. data/docs/howto/passing_arbitrary_options.md +27 -0
  41. data/docs/howto/serialize_poro.md +73 -0
  42. data/docs/howto/test.md +154 -0
  43. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  44. data/docs/integrations/ember-and-json-api.md +147 -0
  45. data/docs/integrations/grape.md +19 -0
  46. data/docs/jsonapi/errors.md +56 -0
  47. data/docs/jsonapi/schema.md +151 -0
  48. data/docs/jsonapi/schema/schema.json +366 -0
  49. data/docs/rfcs/0000-namespace.md +106 -0
  50. data/docs/rfcs/template.md +15 -0
  51. data/lib/action_controller/serialization.rb +76 -0
  52. data/lib/active_model/serializable_resource.rb +13 -0
  53. data/lib/active_model/serializer.rb +418 -0
  54. data/lib/active_model/serializer/adapter.rb +26 -0
  55. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  56. data/lib/active_model/serializer/adapter/base.rb +20 -0
  57. data/lib/active_model/serializer/adapter/json.rb +17 -0
  58. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  59. data/lib/active_model/serializer/adapter/null.rb +17 -0
  60. data/lib/active_model/serializer/array_serializer.rb +14 -0
  61. data/lib/active_model/serializer/association.rb +91 -0
  62. data/lib/active_model/serializer/attribute.rb +27 -0
  63. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  64. data/lib/active_model/serializer/collection_serializer.rb +90 -0
  65. data/lib/active_model/serializer/concerns/caching.rb +304 -0
  66. data/lib/active_model/serializer/error_serializer.rb +16 -0
  67. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  68. data/lib/active_model/serializer/field.rb +92 -0
  69. data/lib/active_model/serializer/fieldset.rb +33 -0
  70. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  71. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  72. data/lib/active_model/serializer/lazy_association.rb +99 -0
  73. data/lib/active_model/serializer/link.rb +23 -0
  74. data/lib/active_model/serializer/lint.rb +152 -0
  75. data/lib/active_model/serializer/null.rb +19 -0
  76. data/lib/active_model/serializer/reflection.rb +212 -0
  77. data/lib/active_model/serializer/version.rb +7 -0
  78. data/lib/active_model_serializers.rb +63 -0
  79. data/lib/active_model_serializers/adapter.rb +100 -0
  80. data/lib/active_model_serializers/adapter/attributes.rb +15 -0
  81. data/lib/active_model_serializers/adapter/base.rb +85 -0
  82. data/lib/active_model_serializers/adapter/json.rb +23 -0
  83. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  84. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  85. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  86. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  87. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  88. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  89. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +90 -0
  90. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  91. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  92. data/lib/active_model_serializers/adapter/null.rb +11 -0
  93. data/lib/active_model_serializers/callbacks.rb +57 -0
  94. data/lib/active_model_serializers/deprecate.rb +56 -0
  95. data/lib/active_model_serializers/deserialization.rb +17 -0
  96. data/lib/active_model_serializers/json_pointer.rb +16 -0
  97. data/lib/active_model_serializers/logging.rb +124 -0
  98. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  99. data/lib/active_model_serializers/model.rb +132 -0
  100. data/lib/active_model_serializers/railtie.rb +52 -0
  101. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  102. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  103. data/lib/active_model_serializers/serialization_context.rb +41 -0
  104. data/lib/active_model_serializers/test.rb +9 -0
  105. data/lib/active_model_serializers/test/schema.rb +140 -0
  106. data/lib/active_model_serializers/test/serializer.rb +127 -0
  107. data/lib/generators/rails/USAGE +6 -0
  108. data/lib/generators/rails/resource_override.rb +12 -0
  109. data/lib/generators/rails/serializer_generator.rb +38 -0
  110. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  111. data/lib/grape/active_model_serializers.rb +18 -0
  112. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  113. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  114. data/lib/tasks/rubocop.rake +55 -0
  115. data/test/action_controller/adapter_selector_test.rb +64 -0
  116. data/test/action_controller/explicit_serializer_test.rb +137 -0
  117. data/test/action_controller/json/include_test.rb +248 -0
  118. data/test/action_controller/json_api/deserialization_test.rb +114 -0
  119. data/test/action_controller/json_api/errors_test.rb +42 -0
  120. data/test/action_controller/json_api/fields_test.rb +68 -0
  121. data/test/action_controller/json_api/linked_test.rb +204 -0
  122. data/test/action_controller/json_api/pagination_test.rb +126 -0
  123. data/test/action_controller/json_api/transform_test.rb +191 -0
  124. data/test/action_controller/lookup_proc_test.rb +51 -0
  125. data/test/action_controller/namespace_lookup_test.rb +239 -0
  126. data/test/action_controller/serialization_scope_name_test.rb +237 -0
  127. data/test/action_controller/serialization_test.rb +480 -0
  128. data/test/active_model_serializers/adapter_for_test.rb +210 -0
  129. data/test/active_model_serializers/json_pointer_test.rb +24 -0
  130. data/test/active_model_serializers/logging_test.rb +79 -0
  131. data/test/active_model_serializers/model_test.rb +144 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +70 -0
  133. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +163 -0
  134. data/test/active_model_serializers/serialization_context_test_isolated.rb +73 -0
  135. data/test/active_model_serializers/test/schema_test.rb +133 -0
  136. data/test/active_model_serializers/test/serializer_test.rb +64 -0
  137. data/test/active_record_test.rb +11 -0
  138. data/test/adapter/attributes_test.rb +42 -0
  139. data/test/adapter/deprecation_test.rb +102 -0
  140. data/test/adapter/json/belongs_to_test.rb +47 -0
  141. data/test/adapter/json/collection_test.rb +106 -0
  142. data/test/adapter/json/has_many_test.rb +55 -0
  143. data/test/adapter/json/transform_test.rb +95 -0
  144. data/test/adapter/json_api/belongs_to_test.rb +157 -0
  145. data/test/adapter/json_api/collection_test.rb +98 -0
  146. data/test/adapter/json_api/errors_test.rb +78 -0
  147. data/test/adapter/json_api/fields_test.rb +98 -0
  148. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  149. data/test/adapter/json_api/has_many_test.rb +175 -0
  150. data/test/adapter/json_api/has_one_test.rb +82 -0
  151. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +215 -0
  152. data/test/adapter/json_api/json_api_test.rb +35 -0
  153. data/test/adapter/json_api/linked_test.rb +415 -0
  154. data/test/adapter/json_api/links_test.rb +112 -0
  155. data/test/adapter/json_api/pagination_links_test.rb +208 -0
  156. data/test/adapter/json_api/parse_test.rb +139 -0
  157. data/test/adapter/json_api/relationship_test.rb +399 -0
  158. data/test/adapter/json_api/resource_meta_test.rb +102 -0
  159. data/test/adapter/json_api/toplevel_jsonapi_test.rb +84 -0
  160. data/test/adapter/json_api/transform_test.rb +514 -0
  161. data/test/adapter/json_api/type_test.rb +195 -0
  162. data/test/adapter/json_test.rb +48 -0
  163. data/test/adapter/null_test.rb +24 -0
  164. data/test/adapter/polymorphic_test.rb +220 -0
  165. data/test/adapter_test.rb +69 -0
  166. data/test/array_serializer_test.rb +24 -0
  167. data/test/benchmark/app.rb +67 -0
  168. data/test/benchmark/benchmarking_support.rb +69 -0
  169. data/test/benchmark/bm_active_record.rb +83 -0
  170. data/test/benchmark/bm_adapter.rb +40 -0
  171. data/test/benchmark/bm_caching.rb +121 -0
  172. data/test/benchmark/bm_lookup_chain.rb +85 -0
  173. data/test/benchmark/bm_transform.rb +47 -0
  174. data/test/benchmark/config.ru +3 -0
  175. data/test/benchmark/controllers.rb +85 -0
  176. data/test/benchmark/fixtures.rb +221 -0
  177. data/test/cache_test.rb +717 -0
  178. data/test/collection_serializer_test.rb +129 -0
  179. data/test/fixtures/active_record.rb +115 -0
  180. data/test/fixtures/poro.rb +227 -0
  181. data/test/generators/scaffold_controller_generator_test.rb +26 -0
  182. data/test/generators/serializer_generator_test.rb +77 -0
  183. data/test/grape_test.rb +198 -0
  184. data/test/lint_test.rb +51 -0
  185. data/test/logger_test.rb +22 -0
  186. data/test/poro_test.rb +11 -0
  187. data/test/serializable_resource_test.rb +81 -0
  188. data/test/serializers/association_macros_test.rb +39 -0
  189. data/test/serializers/associations_test.rb +520 -0
  190. data/test/serializers/attribute_test.rb +155 -0
  191. data/test/serializers/attributes_test.rb +54 -0
  192. data/test/serializers/caching_configuration_test_isolated.rb +172 -0
  193. data/test/serializers/configuration_test.rb +34 -0
  194. data/test/serializers/fieldset_test.rb +16 -0
  195. data/test/serializers/meta_test.rb +204 -0
  196. data/test/serializers/options_test.rb +34 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +81 -0
  198. data/test/serializers/reflection_test.rb +481 -0
  199. data/test/serializers/root_test.rb +23 -0
  200. data/test/serializers/serialization_test.rb +57 -0
  201. data/test/serializers/serializer_for_test.rb +138 -0
  202. data/test/serializers/serializer_for_with_namespace_test.rb +90 -0
  203. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  204. data/test/support/isolated_unit.rb +86 -0
  205. data/test/support/rails5_shims.rb +55 -0
  206. data/test/support/rails_app.rb +40 -0
  207. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  208. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  209. data/test/support/schemas/custom/show.json +7 -0
  210. data/test/support/schemas/hyper_schema.json +93 -0
  211. data/test/support/schemas/render_using_json_api.json +43 -0
  212. data/test/support/schemas/simple_json_pointers.json +10 -0
  213. data/test/support/serialization_testing.rb +81 -0
  214. data/test/test_helper.rb +72 -0
  215. metadata +622 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model_serializers/adapter'
4
+ require 'active_model_serializers/deprecate'
5
+
6
+ module ActiveModel
7
+ class Serializer
8
+ # @deprecated Use ActiveModelSerializers::Adapter instead
9
+ module Adapter
10
+ class << self
11
+ extend ActiveModelSerializers::Deprecate
12
+
13
+ DEPRECATED_METHODS = [:create, :adapter_class, :adapter_map, :adapters, :register, :lookup].freeze
14
+ DEPRECATED_METHODS.each do |method|
15
+ delegate_and_deprecate method, ActiveModelSerializers::Adapter
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'active_model/serializer/adapter/base'
23
+ require 'active_model/serializer/adapter/null'
24
+ require 'active_model/serializer/adapter/attributes'
25
+ require 'active_model/serializer/adapter/json'
26
+ require 'active_model/serializer/adapter/json_api'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ module Adapter
6
+ class Attributes < DelegateClass(ActiveModelSerializers::Adapter::Attributes)
7
+ def initialize(serializer, options = {})
8
+ super(ActiveModelSerializers::Adapter::Attributes.new(serializer, options))
9
+ end
10
+ class << self
11
+ extend ActiveModelSerializers::Deprecate
12
+ deprecate :new, 'ActiveModelSerializers::Adapter::Json.'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ module Adapter
6
+ class Base < DelegateClass(ActiveModelSerializers::Adapter::Base)
7
+ class << self
8
+ extend ActiveModelSerializers::Deprecate
9
+ deprecate :inherited, 'ActiveModelSerializers::Adapter::Base.'
10
+ end
11
+
12
+ # :nocov:
13
+ def initialize(serializer, options = {})
14
+ super(ActiveModelSerializers::Adapter::Base.new(serializer, options))
15
+ end
16
+ # :nocov:
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ module Adapter
6
+ class Json < DelegateClass(ActiveModelSerializers::Adapter::Json)
7
+ def initialize(serializer, options = {})
8
+ super(ActiveModelSerializers::Adapter::Json.new(serializer, options))
9
+ end
10
+ class << self
11
+ extend ActiveModelSerializers::Deprecate
12
+ deprecate :new, 'ActiveModelSerializers::Adapter::Json.new'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ module Adapter
6
+ class JsonApi < DelegateClass(ActiveModelSerializers::Adapter::JsonApi)
7
+ def initialize(serializer, options = {})
8
+ super(ActiveModelSerializers::Adapter::JsonApi.new(serializer, options))
9
+ end
10
+ class << self
11
+ extend ActiveModelSerializers::Deprecate
12
+ deprecate :new, 'ActiveModelSerializers::Adapter::JsonApi.new'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ module Adapter
6
+ class Null < DelegateClass(ActiveModelSerializers::Adapter::Null)
7
+ def initialize(serializer, options = {})
8
+ super(ActiveModelSerializers::Adapter::Null.new(serializer, options))
9
+ end
10
+ class << self
11
+ extend ActiveModelSerializers::Deprecate
12
+ deprecate :new, 'ActiveModelSerializers::Adapter::Null.new'
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/collection_serializer'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ class ArraySerializer < CollectionSerializer
8
+ class << self
9
+ extend ActiveModelSerializers::Deprecate
10
+ deprecate :new, 'ActiveModel::Serializer::CollectionSerializer.'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/lazy_association'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ # This class holds all information about serializer's association.
8
+ #
9
+ # @api private
10
+ Association = Struct.new(:reflection, :association_options) do
11
+ attr_reader :lazy_association
12
+ delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association
13
+
14
+ def initialize(*)
15
+ super
16
+ @lazy_association = LazyAssociation.new(reflection, association_options)
17
+ end
18
+
19
+ # @return [Symbol]
20
+ delegate :name, to: :reflection
21
+
22
+ # @return [Symbol]
23
+ def key
24
+ reflection_options.fetch(:key, name)
25
+ end
26
+
27
+ # @return [True,False]
28
+ def key?
29
+ reflection_options.key?(:key)
30
+ end
31
+
32
+ # @return [Hash]
33
+ def links
34
+ reflection_options.fetch(:links) || {}
35
+ end
36
+
37
+ # @return [Hash, nil]
38
+ # This gets mutated, so cannot use the cached reflection_options
39
+ def meta
40
+ reflection.options[:meta]
41
+ end
42
+
43
+ def belongs_to?
44
+ reflection.foreign_key_on == :self
45
+ end
46
+
47
+ def polymorphic?
48
+ true == reflection_options[:polymorphic]
49
+ end
50
+
51
+ # @api private
52
+ def serializable_hash(adapter_options, adapter_instance)
53
+ association_serializer = lazy_association.serializer
54
+ return virtual_value if virtual_value
55
+ association_object = association_serializer && association_serializer.object
56
+ return unless association_object
57
+
58
+ adapter_sub_options = adapter_options.deep_dup
59
+ fields = adapter_options.fetch(:fields, nil)
60
+ sub_fields = []
61
+ if fields.is_a?(Hash)
62
+ sub_fields = fields.fetch(association_serializer.json_key, nil)
63
+ else
64
+ fields.each do |f|
65
+ sub_fields = f.fetch(association_serializer.json_key, nil) if f.is_a?(Hash)
66
+ break if sub_fields.present?
67
+ end unless fields.nil?
68
+ end
69
+
70
+ if sub_fields.present?
71
+ adapter_sub_options[:fields] = sub_fields.collect { |f| f.to_sym }
72
+ else
73
+ adapter_sub_options = {}
74
+ end
75
+
76
+ serialization = association_serializer.serializable_hash(adapter_options, adapter_sub_options, adapter_instance)
77
+
78
+ if polymorphic? && serialization
79
+ polymorphic_type = association_object.class.name.underscore
80
+ serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
81
+ end
82
+
83
+ serialization
84
+ end
85
+
86
+ private
87
+
88
+ delegate :reflection_options, to: :lazy_association
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/serializer/field'
4
+
5
+ module ActiveModel
6
+ class Serializer
7
+ # Holds all the meta-data about an attribute as it was specified in the
8
+ # ActiveModel::Serializer class.
9
+ #
10
+ # @example
11
+ # class PostSerializer < ActiveModel::Serializer
12
+ # attribute :content
13
+ # attribute :name, key: :title
14
+ # attribute :email, key: :author_email, if: :user_logged_in?
15
+ # attribute :preview do
16
+ # truncate(object.content)
17
+ # end
18
+ #
19
+ # def user_logged_in?
20
+ # current_user.logged_in?
21
+ # end
22
+ # end
23
+ #
24
+ class Attribute < Field
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # @api private
6
+ class BelongsToReflection < Reflection
7
+ # @api private
8
+ def foreign_key_on
9
+ :self
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class CollectionSerializer
6
+ include Enumerable
7
+ delegate :each, to: :@serializers
8
+
9
+ attr_reader :object, :root
10
+
11
+ def initialize(resources, options = {})
12
+ @object = resources
13
+ @options = options
14
+ @root = options[:root]
15
+ @serializers = serializers_from_resources
16
+ end
17
+
18
+ def success?
19
+ true
20
+ end
21
+
22
+ # @api private
23
+ def serializable_hash(adapter_options, options, adapter_instance)
24
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
25
+ options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, options[:include_directive])
26
+ serializers.map do |serializer|
27
+ serializer.serializable_hash(adapter_options, options, adapter_instance)
28
+ end
29
+ end
30
+
31
+ # TODO: unify naming of root, json_key, and _type. Right now, a serializer's
32
+ # json_key comes from the root option or the object's model name, by default.
33
+ # But, if a dev defines a custom `json_key` method with an explicit value,
34
+ # we have no simple way to know that it is safe to call that instance method.
35
+ # (which is really a class property at this point, anyhow).
36
+ # rubocop:disable Metrics/CyclomaticComplexity
37
+ # Disabling cop since it's good to highlight the complexity of this method by
38
+ # including all the logic right here.
39
+ def json_key
40
+ return root if root
41
+ # 1. get from options[:serializer] for empty resource collection
42
+ key = object.empty? &&
43
+ (explicit_serializer_class = options[:serializer]) &&
44
+ explicit_serializer_class._type
45
+ # 2. get from first serializer instance in collection
46
+ key ||= (serializer = serializers.first) && serializer.json_key
47
+ # 3. get from collection name, if a named collection
48
+ key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil
49
+ # 4. key may be nil for empty collection and no serializer option
50
+ key &&= key.pluralize
51
+ # 5. fail if the key cannot be determined
52
+ key || fail(ArgumentError, 'Cannot infer root key from collection type. Please specify the root or each_serializer option, or render a JSON String')
53
+ end
54
+ # rubocop:enable Metrics/CyclomaticComplexity
55
+
56
+ def paginated?
57
+ ActiveModelSerializers.config.jsonapi_pagination_links_enabled &&
58
+ object.respond_to?(:current_page) &&
59
+ object.respond_to?(:total_pages) &&
60
+ object.respond_to?(:size)
61
+ end
62
+
63
+ protected
64
+
65
+ attr_reader :serializers, :options
66
+
67
+ private
68
+
69
+ def serializers_from_resources
70
+ serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
71
+ object.map do |resource|
72
+ serializer_from_resource(resource, serializer_context_class, options)
73
+ end
74
+ end
75
+
76
+ def serializer_from_resource(resource, serializer_context_class, options)
77
+ serializer_class = options.fetch(:serializer) do
78
+ serializer_context_class.serializer_for(resource, namespace: options[:namespace])
79
+ end
80
+
81
+ if serializer_class.nil?
82
+ ActiveModelSerializers.logger.debug "No serializer found for resource: #{resource.inspect}"
83
+ throw :no_serializer
84
+ else
85
+ serializer_class.new(resource, options.except(:serializer))
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ UndefinedCacheKey = Class.new(StandardError)
6
+ module Caching
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ with_options instance_writer: false, instance_reader: false do |serializer|
11
+ serializer.class_attribute :_cache # @api private : the cache store
12
+ serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
13
+ serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except
14
+ serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only
15
+ serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
16
+ # _cache_options include:
17
+ # expires_in
18
+ # compress
19
+ # force
20
+ # race_condition_ttl
21
+ # Passed to ::_cache as
22
+ # serializer.cache_store.fetch(cache_key, @klass._cache_options)
23
+ # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
24
+ serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
25
+ end
26
+ end
27
+
28
+ # Matches
29
+ # "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
30
+ # AND
31
+ # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
32
+ # AS
33
+ # c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
34
+ CALLER_FILE = /
35
+ \A # start of string
36
+ .+ # file path (one or more characters)
37
+ (?= # stop previous match when
38
+ :\d+ # a colon is followed by one or more digits
39
+ :in # followed by a colon followed by in
40
+ )
41
+ /x
42
+
43
+ module ClassMethods
44
+ def inherited(base)
45
+ caller_line = caller[1]
46
+ base._cache_digest_file_path = caller_line
47
+ super
48
+ end
49
+
50
+ def _cache_digest
51
+ return @_cache_digest if defined?(@_cache_digest)
52
+ @_cache_digest = digest_caller_file(_cache_digest_file_path)
53
+ end
54
+
55
+ # Hashes contents of file for +_cache_digest+
56
+ def digest_caller_file(caller_line)
57
+ serializer_file_path = caller_line[CALLER_FILE]
58
+ serializer_file_contents = IO.read(serializer_file_path)
59
+ Digest::MD5.hexdigest(serializer_file_contents)
60
+ rescue TypeError, Errno::ENOENT
61
+ warn <<-EOF.strip_heredoc
62
+ Cannot digest non-existent file: '#{caller_line}'.
63
+ Please set `::_cache_digest` of the serializer
64
+ if you'd like to cache it.
65
+ EOF
66
+ ''.freeze
67
+ end
68
+
69
+ def _skip_digest?
70
+ _cache_options && _cache_options[:skip_digest]
71
+ end
72
+
73
+ # @api private
74
+ # maps attribute value to explicit key name
75
+ # @see Serializer::attribute
76
+ # @see Serializer::fragmented_attributes
77
+ def _attributes_keys
78
+ _attributes_data
79
+ .each_with_object({}) do |(key, attr), hash|
80
+ next if key == attr.name
81
+ hash[attr.name] = { key: key }
82
+ end
83
+ end
84
+
85
+ def fragmented_attributes
86
+ cached = _cache_only ? _cache_only : _attributes - _cache_except
87
+ cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
88
+ non_cached = _attributes - cached
89
+ non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) }
90
+ {
91
+ cached: cached,
92
+ non_cached: non_cached
93
+ }
94
+ end
95
+
96
+ # Enables a serializer to be automatically cached
97
+ #
98
+ # Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
99
+ # when Rails.configuration.action_controller.perform_caching
100
+ #
101
+ # @param options [Hash] with valid keys:
102
+ # cache_store : @see ::_cache
103
+ # key : @see ::_cache_key
104
+ # only : @see ::_cache_only
105
+ # except : @see ::_cache_except
106
+ # skip_digest : does not include digest in cache_key
107
+ # all else : @see ::_cache_options
108
+ #
109
+ # @example
110
+ # class PostSerializer < ActiveModel::Serializer
111
+ # cache key: 'post', expires_in: 3.hours
112
+ # attributes :title, :body
113
+ #
114
+ # has_many :comments
115
+ # end
116
+ #
117
+ # @todo require less code comments. See
118
+ # https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
119
+ def cache(options = {})
120
+ self._cache =
121
+ options.delete(:cache_store) ||
122
+ ActiveModelSerializers.config.cache_store ||
123
+ ActiveSupport::Cache.lookup_store(:null_store)
124
+ self._cache_key = options.delete(:key)
125
+ self._cache_only = options.delete(:only)
126
+ self._cache_except = options.delete(:except)
127
+ self._cache_options = options.empty? ? nil : options
128
+ end
129
+
130
+ # Value is from ActiveModelSerializers.config.perform_caching. Is used to
131
+ # globally enable or disable all serializer caching, just like
132
+ # Rails.configuration.action_controller.perform_caching, which is its
133
+ # default value in a Rails application.
134
+ # @return [true, false]
135
+ # Memoizes value of config first time it is called with a non-nil value.
136
+ # rubocop:disable Style/ClassVars
137
+ def perform_caching
138
+ return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil?
139
+ @@perform_caching = ActiveModelSerializers.config.perform_caching
140
+ end
141
+ alias perform_caching? perform_caching
142
+ # rubocop:enable Style/ClassVars
143
+
144
+ # The canonical method for getting the cache store for the serializer.
145
+ #
146
+ # @return [nil] when _cache is not set (i.e. when `cache` has not been called)
147
+ # @return [._cache] when _cache is not the NullStore
148
+ # @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
149
+ # This is so we can use `cache` being called to mean the serializer should be cached
150
+ # even if ActiveModelSerializers.config.cache_store has not yet been set.
151
+ # That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
152
+ # is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
153
+ # @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
154
+ def cache_store
155
+ return nil if _cache.nil?
156
+ return _cache if _cache.class != ActiveSupport::Cache::NullStore
157
+ if ActiveModelSerializers.config.cache_store
158
+ self._cache = ActiveModelSerializers.config.cache_store
159
+ else
160
+ nil
161
+ end
162
+ end
163
+
164
+ def cache_enabled?
165
+ perform_caching? && cache_store && !_cache_only && !_cache_except
166
+ end
167
+
168
+ def fragment_cache_enabled?
169
+ perform_caching? && cache_store &&
170
+ (_cache_only && !_cache_except || !_cache_only && _cache_except)
171
+ end
172
+
173
+ # Read cache from cache_store
174
+ # @return [Hash]
175
+ # Used in CollectionSerializer to set :cached_attributes
176
+ def cache_read_multi(collection_serializer, adapter_instance, include_directive)
177
+ return {} if ActiveModelSerializers.config.cache_store.blank?
178
+
179
+ keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
180
+
181
+ return {} if keys.blank?
182
+
183
+ ActiveModelSerializers.config.cache_store.read_multi(*keys)
184
+ end
185
+
186
+ # Find all cache_key for the collection_serializer
187
+ # @param serializers [ActiveModel::Serializer::CollectionSerializer]
188
+ # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
189
+ # @param include_directive [JSONAPI::IncludeDirective]
190
+ # @return [Array] all cache_key of collection_serializer
191
+ def object_cache_keys(collection_serializer, adapter_instance, include_directive)
192
+ cache_keys = []
193
+
194
+ collection_serializer.each do |serializer|
195
+ cache_keys << object_cache_key(serializer, adapter_instance)
196
+
197
+ serializer.associations(include_directive).each do |association|
198
+ # TODO(BF): Process relationship without evaluating lazy_association
199
+ association_serializer = association.lazy_association.serializer
200
+ if association_serializer.respond_to?(:each)
201
+ association_serializer.each do |sub_serializer|
202
+ cache_keys << object_cache_key(sub_serializer, adapter_instance)
203
+ end
204
+ else
205
+ cache_keys << object_cache_key(association_serializer, adapter_instance)
206
+ end
207
+ end
208
+ end
209
+
210
+ cache_keys.compact.uniq
211
+ end
212
+
213
+ # @return [String, nil] the cache_key of the serializer or nil
214
+ def object_cache_key(serializer, adapter_instance)
215
+ return unless serializer.present? && serializer.object.present?
216
+
217
+ (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
218
+ end
219
+ end
220
+
221
+ ### INSTANCE METHODS
222
+ def fetch_attributes(fields, cached_attributes, adapter_instance)
223
+ key = cache_key(adapter_instance)
224
+ cached_attributes.fetch(key) do
225
+ fetch(adapter_instance, serializer_class._cache_options, key) do
226
+ attributes(fields, true)
227
+ end
228
+ end
229
+ end
230
+
231
+ def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
232
+ if serializer_class.cache_store
233
+ key ||= cache_key(adapter_instance)
234
+ serializer_class.cache_store.fetch(key, cache_options) do
235
+ yield
236
+ end
237
+ else
238
+ yield
239
+ end
240
+ end
241
+
242
+ # 1. Determine cached fields from serializer class options
243
+ # 2. Get non_cached_fields and fetch cache_fields
244
+ # 3. Merge the two hashes using adapter_instance#fragment_cache
245
+ def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
246
+ serializer_class._cache_options ||= {}
247
+ serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
248
+ fields = serializer_class.fragmented_attributes
249
+
250
+ non_cached_fields = fields[:non_cached].dup
251
+ non_cached_hash = attributes(non_cached_fields, true)
252
+ include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
253
+ non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
254
+
255
+ cached_fields = fields[:cached].dup
256
+ key = cache_key(adapter_instance)
257
+ cached_hash =
258
+ cached_attributes.fetch(key) do
259
+ fetch(adapter_instance, serializer_class._cache_options, key) do
260
+ hash = attributes(cached_fields, true)
261
+ include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
262
+ hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
263
+ end
264
+ end
265
+ # Merge both results
266
+ adapter_instance.fragment_cache(cached_hash, non_cached_hash)
267
+ end
268
+
269
+ def cache_key(adapter_instance)
270
+ return @cache_key if defined?(@cache_key)
271
+
272
+ parts = []
273
+ parts << object_cache_key
274
+ parts << adapter_instance.cache_key
275
+ parts << serializer_class._cache_digest unless serializer_class._skip_digest?
276
+ @cache_key = expand_cache_key(parts)
277
+ end
278
+
279
+ def expand_cache_key(parts)
280
+ ActiveSupport::Cache.expand_cache_key(parts)
281
+ end
282
+
283
+ # Use object's cache_key if available, else derive a key from the object
284
+ # Pass the `key` option to the `cache` declaration or override this method to customize the cache key
285
+ def object_cache_key
286
+ if object.respond_to?(:cache_key_with_version)
287
+ object.cache_key_with_version
288
+ elsif object.respond_to?(:cache_key)
289
+ object.cache_key
290
+ elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
291
+ object_time_safe = object.updated_at
292
+ object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
293
+ "#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
294
+ else
295
+ fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
296
+ end
297
+ end
298
+
299
+ def serializer_class
300
+ @serializer_class ||= self.class
301
+ end
302
+ end
303
+ end
304
+ end