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,106 @@
1
+ - Start Date: (2015-10-29)
2
+ - RFC PR: https://github.com/rails-api/active_model_serializers/pull/1310
3
+ - ActiveModelSerializers Issue: https://github.com/rails-api/active_model_serializers/issues/1298
4
+
5
+ # Summary
6
+
7
+ Provide a consistent API for the user of the AMS.
8
+
9
+ # Motivation
10
+
11
+ The actual public API is defined under `ActiveModelSerializers`,
12
+ `ActiveModel::Serializer` and `ActiveModel`.
13
+
14
+ At the `ActiveModel::Serializer` we have:
15
+
16
+ - `ActiveModel::Serializer.config`
17
+ - `ActiveModel::Serializer`
18
+
19
+ At the `ActiveModelSerializers` we have:
20
+
21
+ - `ActiveModelSerializers::Model`
22
+ - `ActiveModelSerializers.logger`
23
+
24
+ At `ActiveModel` we have:
25
+
26
+ - `ActiveModel::SerializableResource`
27
+
28
+ The idea here is to provide a single namespace `ActiveModelSerializers` to the user.
29
+ Following the same idea we have on other gems like
30
+ [Devise](https://github.com/plataformatec/devise/blob/e9c82472ffe7c43a448945f77e034a0e47dde0bb/lib/devise.rb),
31
+ [Refile](https://github.com/refile/refile/blob/6b24c293d044862dafbf1bfa4606672a64903aa2/lib/refile.rb) and
32
+ [Active Job](https://github.com/rails/rails/blob/30bacc26f8f258b39e12f63fe52389a968d9c1ea/activejob/lib/active_job.rb)
33
+ for example.
34
+
35
+ This way we are clarifing the boundaries of
36
+ [ActiveModelSerializers and Rails](https://github.com/rails-api/active_model_serializers/blob/master/CHANGELOG.md#prehistory)
37
+ and make clear that the `ActiveModel::Serializer` class is no longer the primary
38
+ behavior of the ActiveModelSerializers.
39
+
40
+ # Detailed design
41
+
42
+ ## New classes and modules organization
43
+
44
+ Since this will be a big change we can do this on baby steps, read small pull requests. A
45
+ possible approach is:
46
+
47
+ - All new code will be in `lib/active_model_serializers/` using
48
+ the module namespace `ActiveModelSerializers`.
49
+ - Move all content under `ActiveModel::Serializer` to be under
50
+ `ActiveModelSerializers`, the adapter is on this steps;
51
+ - Move all content under `ActiveModel` to be under `ActiveModelSerializers`,
52
+ the `SerializableResource` is on this step;
53
+ - Change all public API that doesn't make sense, keeping in mind only to keep
54
+ this in the same namespace
55
+ - Update the README;
56
+ - Update the docs;
57
+
58
+ The following table represents the current and the desired classes and modules
59
+ at the first moment.
60
+
61
+ | Current | Desired | Notes |
62
+ |--------------------------------------------------------|--------------------------------------------------|--------------------|
63
+ | `ActiveModelSerializers` and `ActiveModel::Serializer` | `ActiveModelSerializers` | The main namespace |
64
+ | `ActiveModelSerializers.logger` | `ActiveModelSerializers.logger` ||
65
+ | `ActiveModelSerializers::Model` | `ActiveModelSerializers::Model` ||
66
+ | `ActiveModel::SerializableResource` | `ActiveModelSerializers::SerializableResource` ||
67
+ | `ActiveModel::Serializer` | `ActiveModelSerializers::Serializer` | The name can be discussed in a future pull request. For example, we can rename this to `Resource` [following this idea](https://github.com/rails-api/active_model_serializers/pull/1301/files#r42963185) more info about naming in the next section|
68
+ | `ActiveModel::Serializer.config` | `ActiveModelSerializers.config` ||
69
+
70
+ ## Renaming of class and modules
71
+
72
+ When moving some content to the new namespace we can find some names that does
73
+ not make much sense like `ActiveModel::Serializer::Adapter::JsonApi`.
74
+ Discussion of renaming existing classes / modules and JsonApi objects will
75
+ happen in separate pull requests, and issues, and in the google doc
76
+ https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing
77
+
78
+ Some of names already have a definition.
79
+
80
+ - Adapters get their own namespace under ActiveModelSerializers. E.g
81
+ `ActiveModelSerializers::Adapter`
82
+ - Serializers get their own namespace under ActiveModelSerializers. E.g
83
+ `ActiveModelSerializers::Serializer`
84
+
85
+ ## Keeping compatibility
86
+
87
+ All moved classes or modules be aliased to their old name and location with
88
+ deprecation warnings, such as
89
+ [was done for CollectionSerializer](https://github.com/rails-api/active_model_serializers/pull/1251).
90
+
91
+ # Drawbacks
92
+
93
+ This will be a breaking change, so all users serializers will be broken after a
94
+ major bump.
95
+ All pull requests will need to rebase since the architeture will change a lot.
96
+
97
+ # Alternatives
98
+
99
+ We can keep the way it is, and keep in mind to not add another namespace as a
100
+ public API.
101
+
102
+ # Unresolved questions
103
+
104
+ What is the better class name to be used to the class that will be inherited at
105
+ the creation of a serializer. This can be discussed in other RFC or directly via
106
+ pull request.
@@ -0,0 +1,15 @@
1
+ - Start Date: (YYYY-MM-DD)
2
+ - RFC PR: https://github.com/rails-api/active_model_serializers/pull/dddd
3
+ - ActiveModelSerializers Issue: https://github.com/rails-api/active_model_serializers/issues/dddd
4
+
5
+ # Summary
6
+
7
+ # Motivation
8
+
9
+ # Detailed design
10
+
11
+ # Drawbacks
12
+
13
+ # Alternatives
14
+
15
+ # Unresolved questions
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_model_serializers/serialization_context'
5
+
6
+ module ActionController
7
+ module Serialization
8
+ extend ActiveSupport::Concern
9
+
10
+ include ActionController::Renderers
11
+
12
+ module ClassMethods
13
+ def serialization_scope(scope)
14
+ self._serialization_scope = scope
15
+ end
16
+ end
17
+
18
+ included do
19
+ class_attribute :_serialization_scope
20
+ self._serialization_scope = :current_user
21
+
22
+ attr_writer :namespace_for_serializer
23
+ end
24
+
25
+ def namespace_for_serializer
26
+ @namespace_for_serializer ||= namespace_for_class(self.class) unless namespace_for_class(self.class) == Object
27
+ end
28
+
29
+ def namespace_for_class(klass)
30
+ if Module.method_defined?(:module_parent)
31
+ klass.module_parent
32
+ else
33
+ klass.parent
34
+ end
35
+ end
36
+
37
+ def serialization_scope
38
+ return unless _serialization_scope && respond_to?(_serialization_scope, true)
39
+
40
+ send(_serialization_scope)
41
+ end
42
+
43
+ def get_serializer(resource, options = {})
44
+ unless use_adapter?
45
+ warn 'ActionController::Serialization#use_adapter? has been removed. '\
46
+ "Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new"
47
+ options[:adapter] = false
48
+ end
49
+
50
+ options.fetch(:namespace) { options[:namespace] = namespace_for_serializer }
51
+
52
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)
53
+ serializable_resource.serialization_scope ||= options.fetch(:scope) { serialization_scope }
54
+ serializable_resource.serialization_scope_name = options.fetch(:scope_name) { _serialization_scope }
55
+ # For compatibility with the JSON renderer: `json.to_json(options) if json.is_a?(String)`.
56
+ # Otherwise, since `serializable_resource` is not a string, the renderer would call
57
+ # `to_json` on a String and given odd results, such as `"".to_json #=> '""'`
58
+ serializable_resource.adapter.is_a?(String) ? serializable_resource.adapter : serializable_resource
59
+ end
60
+
61
+ # Deprecated
62
+ def use_adapter?
63
+ true
64
+ end
65
+
66
+ [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
67
+ define_method renderer_method do |resource, options|
68
+ options.fetch(:serialization_context) do
69
+ options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options)
70
+ end
71
+ serializable_resource = get_serializer(resource, options)
72
+ super(serializable_resource, options)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module ActiveModel
6
+ class SerializableResource
7
+ class << self
8
+ extend ActiveModelSerializers::Deprecate
9
+
10
+ delegate_and_deprecate :new, ActiveModelSerializers::SerializableResource
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,418 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread_safe'
4
+ require 'jsonapi/include_directive'
5
+ require 'active_model/serializer/collection_serializer'
6
+ require 'active_model/serializer/array_serializer'
7
+ require 'active_model/serializer/error_serializer'
8
+ require 'active_model/serializer/errors_serializer'
9
+ require 'active_model/serializer/concerns/caching'
10
+ require 'active_model/serializer/fieldset'
11
+ require 'active_model/serializer/lint'
12
+
13
+ # ActiveModel::Serializer is an abstract class that is
14
+ # reified when subclassed to decorate a resource.
15
+ module ActiveModel
16
+ class Serializer
17
+ undef_method :select, :display # These IO methods, which are mixed into Kernel,
18
+ # sometimes conflict with attribute names. We don't need these IO methods.
19
+
20
+ # @see #serializable_hash for more details on these valid keys.
21
+ SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
22
+ extend ActiveSupport::Autoload
23
+ eager_autoload do
24
+ autoload :Adapter
25
+ autoload :Null
26
+ autoload :Attribute
27
+ autoload :Link
28
+ autoload :Association
29
+ autoload :Reflection
30
+ autoload :BelongsToReflection
31
+ autoload :HasOneReflection
32
+ autoload :HasManyReflection
33
+ end
34
+ include ActiveSupport::Configurable
35
+ include Caching
36
+
37
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
38
+ # @return [ActiveModel::Serializer]
39
+ # Preferentially returns
40
+ # 1. resource.serializer_class
41
+ # 2. ArraySerializer when resource is a collection
42
+ # 3. options[:serializer]
43
+ # 4. lookup serializer when resource is a Class
44
+ def self.serializer_for(resource_or_class, options = {})
45
+ if resource_or_class.respond_to?(:serializer_class)
46
+ resource_or_class.serializer_class
47
+ elsif resource_or_class.respond_to?(:to_ary)
48
+ config.collection_serializer
49
+ else
50
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
51
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
52
+ end
53
+ end
54
+
55
+ # @see ActiveModelSerializers::Adapter.lookup
56
+ # Deprecated
57
+ def self.adapter
58
+ ActiveModelSerializers::Adapter.lookup(config.adapter)
59
+ end
60
+ class << self
61
+ extend ActiveModelSerializers::Deprecate
62
+ deprecate :adapter, 'ActiveModelSerializers::Adapter.configured_adapter'
63
+ end
64
+
65
+ # @api private
66
+ def self.serializer_lookup_chain_for(klass, namespace = nil)
67
+ lookups = ActiveModelSerializers.config.serializer_lookup_chain
68
+ Array[*lookups].flat_map do |lookup|
69
+ lookup.call(klass, self, namespace)
70
+ end.compact
71
+ end
72
+
73
+ # Used to cache serializer name => serializer class
74
+ # when looked up by Serializer.get_serializer_for.
75
+ def self.serializers_cache
76
+ @serializers_cache ||= ThreadSafe::Cache.new
77
+ end
78
+
79
+ # @api private
80
+ # Find a serializer from a class and caches the lookup.
81
+ # Preferentially returns:
82
+ # 1. class name appended with "Serializer"
83
+ # 2. try again with superclass, if present
84
+ # 3. nil
85
+ def self.get_serializer_for(klass, namespace = nil)
86
+ return nil unless config.serializer_lookup_enabled
87
+
88
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
89
+ serializers_cache.fetch_or_store(cache_key) do
90
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
91
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
92
+ serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
93
+
94
+ if serializer_class
95
+ serializer_class
96
+ elsif klass.superclass
97
+ get_serializer_for(klass.superclass)
98
+ else
99
+ nil # No serializer found
100
+ end
101
+ end
102
+ end
103
+
104
+ # @api private
105
+ def self.include_directive_from_options(options)
106
+ if options[:include_directive]
107
+ options[:include_directive]
108
+ elsif options[:include]
109
+ JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
110
+ else
111
+ ActiveModelSerializers.default_include_directive
112
+ end
113
+ end
114
+
115
+ # @api private
116
+ def self.serialization_adapter_instance
117
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
118
+ end
119
+
120
+ # Preferred interface is ActiveModelSerializers.config
121
+ # BEGIN DEFAULT CONFIGURATION
122
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
123
+ config.serializer_lookup_enabled = true
124
+
125
+ # @deprecated Use {#config.collection_serializer=} instead of this. Is
126
+ # compatibility layer for ArraySerializer.
127
+ def config.array_serializer=(collection_serializer)
128
+ self.collection_serializer = collection_serializer
129
+ end
130
+
131
+ # @deprecated Use {#config.collection_serializer} instead of this. Is
132
+ # compatibility layer for ArraySerializer.
133
+ def config.array_serializer
134
+ collection_serializer
135
+ end
136
+
137
+ config.default_includes = '*'
138
+ config.adapter = :attributes
139
+ config.key_transform = nil
140
+ config.jsonapi_pagination_links_enabled = true
141
+ config.jsonapi_resource_type = :plural
142
+ config.jsonapi_namespace_separator = '-'.freeze
143
+ config.jsonapi_version = '1.0'
144
+ config.jsonapi_toplevel_meta = {}
145
+ # Make JSON API top-level jsonapi member opt-in
146
+ # ref: http://jsonapi.org/format/#document-top-level
147
+ config.jsonapi_include_toplevel_object = false
148
+ config.jsonapi_use_foreign_key_on_belongs_to_relationship = false
149
+ config.include_data_default = true
150
+
151
+ # For configuring how serializers are found.
152
+ # This should be an array of procs.
153
+ #
154
+ # The priority of the output is that the first item
155
+ # in the evaluated result array will take precedence
156
+ # over other possible serializer paths.
157
+ #
158
+ # i.e.: First match wins.
159
+ #
160
+ # @example output
161
+ # => [
162
+ # "CustomNamespace::ResourceSerializer",
163
+ # "ParentSerializer::ResourceSerializer",
164
+ # "ResourceNamespace::ResourceSerializer" ,
165
+ # "ResourceSerializer"]
166
+ #
167
+ # If CustomNamespace::ResourceSerializer exists, it will be used
168
+ # for serialization
169
+ config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
170
+
171
+ config.schema_path = 'test/support/schemas'
172
+ # END DEFAULT CONFIGURATION
173
+
174
+ with_options instance_writer: false, instance_reader: false do |serializer|
175
+ serializer.class_attribute :_attributes_data # @api private
176
+ self._attributes_data ||= {}
177
+ end
178
+ with_options instance_writer: false, instance_reader: true do |serializer|
179
+ serializer.class_attribute :_reflections
180
+ self._reflections ||= {}
181
+ serializer.class_attribute :_links # @api private
182
+ self._links ||= {}
183
+ serializer.class_attribute :_meta # @api private
184
+ serializer.class_attribute :_type # @api private
185
+ end
186
+
187
+ def self.inherited(base)
188
+ super
189
+ base._attributes_data = _attributes_data.dup
190
+ base._reflections = _reflections.dup
191
+ base._links = _links.dup
192
+ end
193
+
194
+ # @return [Array<Symbol>] Key names of declared attributes
195
+ # @see Serializer::attribute
196
+ def self._attributes
197
+ _attributes_data.keys
198
+ end
199
+
200
+ # BEGIN SERIALIZER MACROS
201
+
202
+ # @example
203
+ # class AdminAuthorSerializer < ActiveModel::Serializer
204
+ # attributes :id, :name, :recent_edits
205
+ def self.attributes(*attrs)
206
+ attrs = attrs.first if attrs.first.class == Array
207
+
208
+ attrs.each do |attr|
209
+ attribute(attr)
210
+ end
211
+ end
212
+
213
+ # @example
214
+ # class AdminAuthorSerializer < ActiveModel::Serializer
215
+ # attributes :id, :recent_edits
216
+ # attribute :name, key: :title
217
+ #
218
+ # attribute :full_name do
219
+ # "#{object.first_name} #{object.last_name}"
220
+ # end
221
+ #
222
+ # def recent_edits
223
+ # object.edits.last(5)
224
+ # end
225
+ def self.attribute(attr, options = {}, &block)
226
+ key = options.fetch(:key, attr)
227
+ _attributes_data[key] = Attribute.new(attr, options, block)
228
+ end
229
+
230
+ # @param [Symbol] name of the association
231
+ # @param [Hash<Symbol => any>] options for the reflection
232
+ # @return [void]
233
+ #
234
+ # @example
235
+ # has_many :comments, serializer: CommentSummarySerializer
236
+ #
237
+ def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
238
+ associate(HasManyReflection.new(name, options, block))
239
+ end
240
+
241
+ # @param [Symbol] name of the association
242
+ # @param [Hash<Symbol => any>] options for the reflection
243
+ # @return [void]
244
+ #
245
+ # @example
246
+ # belongs_to :author, serializer: AuthorSerializer
247
+ #
248
+ def self.belongs_to(name, options = {}, &block)
249
+ associate(BelongsToReflection.new(name, options, block))
250
+ end
251
+
252
+ # @param [Symbol] name of the association
253
+ # @param [Hash<Symbol => any>] options for the reflection
254
+ # @return [void]
255
+ #
256
+ # @example
257
+ # has_one :author, serializer: AuthorSerializer
258
+ #
259
+ def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
260
+ associate(HasOneReflection.new(name, options, block))
261
+ end
262
+
263
+ # Add reflection and define {name} accessor.
264
+ # @param [ActiveModel::Serializer::Reflection] reflection
265
+ # @return [void]
266
+ #
267
+ # @api private
268
+ def self.associate(reflection)
269
+ key = reflection.options[:key] || reflection.name
270
+ self._reflections[key] = reflection
271
+ end
272
+ private_class_method :associate
273
+
274
+ # Define a link on a serializer.
275
+ # @example
276
+ # link(:self) { resource_url(object) }
277
+ # @example
278
+ # link(:self) { "http://example.com/resource/#{object.id}" }
279
+ # @example
280
+ # link :resource, "http://example.com/resource"
281
+ # @example
282
+ # link(:callback, if: :internal?), { "http://example.com/callback" }
283
+ #
284
+ def self.link(name, *args, &block)
285
+ options = args.extract_options!
286
+ # For compatibility with the use case of passing link directly as string argument
287
+ # without block, we are creating a wrapping block
288
+ _links[name] = Link.new(name, options, block || ->(_serializer) { args.first })
289
+ end
290
+
291
+ # Set the JSON API meta attribute of a serializer.
292
+ # @example
293
+ # class AdminAuthorSerializer < ActiveModel::Serializer
294
+ # meta { stuff: 'value' }
295
+ # @example
296
+ # meta do
297
+ # { comment_count: object.comments.count }
298
+ # end
299
+ def self.meta(value = nil, &block)
300
+ self._meta = block || value
301
+ end
302
+
303
+ # Set the JSON API type of a serializer.
304
+ # @example
305
+ # class AdminAuthorSerializer < ActiveModel::Serializer
306
+ # type 'authors'
307
+ def self.type(type)
308
+ self._type = type && type.to_s
309
+ end
310
+
311
+ # END SERIALIZER MACROS
312
+
313
+ attr_accessor :object, :root, :scope
314
+
315
+ # `scope_name` is set as :current_user by default in the controller.
316
+ # If the instance does not have a method named `scope_name`, it
317
+ # defines the method so that it calls the +scope+.
318
+ def initialize(object, options = {})
319
+ self.object = object
320
+ self.instance_options = options
321
+ self.root = instance_options[:root]
322
+ self.scope = instance_options[:scope]
323
+
324
+ return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
325
+
326
+ define_singleton_method scope_name, -> { scope }
327
+ end
328
+
329
+ def success?
330
+ true
331
+ end
332
+
333
+ # Return the +attributes+ of +object+ as presented
334
+ # by the serializer.
335
+ def attributes(requested_attrs = nil, reload = false)
336
+ @attributes = nil if reload
337
+ @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
338
+ next if attr.excluded?(self)
339
+ next unless requested_attrs.nil? || requested_attrs.include?(key)
340
+ hash[key] = attr.value(self)
341
+ end
342
+ end
343
+
344
+ # @param [JSONAPI::IncludeDirective] include_directive (defaults to the
345
+ # +default_include_directive+ config value when not provided)
346
+ # @return [Enumerator<Association>]
347
+ def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
348
+ include_slice ||= include_directive
349
+ return Enumerator.new {} unless object
350
+
351
+ Enumerator.new do |y|
352
+ (self.instance_reflections ||= self.class._reflections.deep_dup).each do |key, reflection|
353
+ next if reflection.excluded?(self)
354
+ next unless include_directive.key?(key)
355
+
356
+ association = reflection.build_association(self, instance_options, include_slice)
357
+ y.yield association
358
+ end
359
+ end
360
+ end
361
+
362
+ # @return [Hash] containing the attributes and first level
363
+ # associations, similar to how ActiveModel::Serializers::JSON is used
364
+ # in ActiveRecord::Base.
365
+ def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
366
+ adapter_options ||= {}
367
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
368
+ resource = attributes_hash(adapter_options, options, adapter_instance)
369
+ relationships = associations_hash(adapter_options, options, adapter_instance)
370
+ resource.merge(relationships)
371
+ end
372
+ alias to_hash serializable_hash
373
+ alias to_h serializable_hash
374
+
375
+ # @see #serializable_hash
376
+ def as_json(adapter_opts = nil)
377
+ serializable_hash(adapter_opts)
378
+ end
379
+
380
+ # Used by adapter as resource root.
381
+ def json_key
382
+ root || _type || object.class.model_name.to_s.underscore
383
+ end
384
+
385
+ def read_attribute_for_serialization(attr)
386
+ if respond_to?(attr)
387
+ send(attr)
388
+ else
389
+ object.read_attribute_for_serialization(attr)
390
+ end
391
+ end
392
+
393
+ # @api private
394
+ def attributes_hash(_adapter_options, options, adapter_instance)
395
+ if self.class.cache_enabled?
396
+ fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
397
+ elsif self.class.fragment_cache_enabled?
398
+ fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
399
+ else
400
+ attributes(options[:fields], true)
401
+ end
402
+ end
403
+
404
+ # @api private
405
+ def associations_hash(adapter_options, options, adapter_instance)
406
+ include_directive = options.fetch(:include_directive)
407
+ include_slice = options[:include_slice]
408
+ associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
409
+ adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
410
+ relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
411
+ end
412
+ end
413
+
414
+ protected
415
+
416
+ attr_accessor :instance_options, :instance_reflections
417
+ end
418
+ end