active_model_serializers 0.8.3 → 0.10.0

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +29 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  4. data/.gitignore +17 -0
  5. data/.rubocop.yml +104 -0
  6. data/.rubocop_todo.yml +167 -0
  7. data/.simplecov +110 -0
  8. data/.travis.yml +39 -24
  9. data/CHANGELOG.md +465 -6
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +50 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +102 -590
  14. data/Rakefile +93 -8
  15. data/active_model_serializers.gemspec +65 -23
  16. data/appveyor.yml +24 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/serve_benchmark +39 -0
  20. data/docs/ARCHITECTURE.md +126 -0
  21. data/docs/README.md +40 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +245 -0
  24. data/docs/general/caching.md +52 -0
  25. data/docs/general/configuration_options.md +100 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/getting_started.md +133 -0
  28. data/docs/general/instrumentation.md +40 -0
  29. data/docs/general/key_transforms.md +40 -0
  30. data/docs/general/logging.md +14 -0
  31. data/docs/general/rendering.md +255 -0
  32. data/docs/general/serializers.md +372 -0
  33. data/docs/how-open-source-maintained.jpg +0 -0
  34. data/docs/howto/add_pagination_links.md +139 -0
  35. data/docs/howto/add_root_key.md +51 -0
  36. data/docs/howto/outside_controller_use.md +58 -0
  37. data/docs/howto/passing_arbitrary_options.md +27 -0
  38. data/docs/howto/serialize_poro.md +32 -0
  39. data/docs/howto/test.md +152 -0
  40. data/docs/integrations/ember-and-json-api.md +112 -0
  41. data/docs/integrations/grape.md +19 -0
  42. data/docs/jsonapi/errors.md +56 -0
  43. data/docs/jsonapi/schema/schema.json +366 -0
  44. data/docs/jsonapi/schema.md +151 -0
  45. data/docs/rfcs/0000-namespace.md +106 -0
  46. data/docs/rfcs/template.md +15 -0
  47. data/lib/action_controller/serialization.rb +31 -36
  48. data/lib/active_model/serializable_resource.rb +11 -0
  49. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  50. data/lib/active_model/serializer/adapter/base.rb +16 -0
  51. data/lib/active_model/serializer/adapter/json.rb +15 -0
  52. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  53. data/lib/active_model/serializer/adapter/null.rb +15 -0
  54. data/lib/active_model/serializer/adapter.rb +24 -0
  55. data/lib/active_model/serializer/array_serializer.rb +9 -0
  56. data/lib/active_model/serializer/association.rb +19 -0
  57. data/lib/active_model/serializer/associations.rb +87 -220
  58. data/lib/active_model/serializer/attribute.rb +25 -0
  59. data/lib/active_model/serializer/attributes.rb +82 -0
  60. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  61. data/lib/active_model/serializer/caching.rb +333 -0
  62. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +64 -0
  64. data/lib/active_model/serializer/configuration.rb +35 -0
  65. data/lib/active_model/serializer/error_serializer.rb +10 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +27 -0
  67. data/lib/active_model/serializer/field.rb +90 -0
  68. data/lib/active_model/serializer/fieldset.rb +31 -0
  69. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  70. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  71. data/lib/active_model/serializer/include_tree.rb +111 -0
  72. data/lib/active_model/serializer/links.rb +35 -0
  73. data/lib/active_model/serializer/lint.rb +146 -0
  74. data/lib/active_model/serializer/meta.rb +29 -0
  75. data/lib/active_model/serializer/null.rb +17 -0
  76. data/lib/active_model/serializer/reflection.rb +147 -0
  77. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  78. data/lib/active_model/serializer/type.rb +25 -0
  79. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  80. data/lib/active_model/serializer.rb +158 -481
  81. data/lib/active_model_serializers/adapter/attributes.rb +76 -0
  82. data/lib/active_model_serializers/adapter/base.rb +83 -0
  83. data/lib/active_model_serializers/adapter/json.rb +21 -0
  84. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  85. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  86. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  87. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  88. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  89. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +62 -0
  90. data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
  91. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
  92. data/lib/active_model_serializers/adapter/json_api.rb +516 -0
  93. data/lib/active_model_serializers/adapter/null.rb +9 -0
  94. data/lib/active_model_serializers/adapter.rb +92 -0
  95. data/lib/active_model_serializers/callbacks.rb +55 -0
  96. data/lib/active_model_serializers/deprecate.rb +55 -0
  97. data/lib/active_model_serializers/deserialization.rb +13 -0
  98. data/lib/active_model_serializers/json_pointer.rb +14 -0
  99. data/lib/active_model_serializers/key_transform.rb +70 -0
  100. data/lib/active_model_serializers/logging.rb +122 -0
  101. data/lib/active_model_serializers/model.rb +49 -0
  102. data/lib/active_model_serializers/railtie.rb +46 -0
  103. data/lib/active_model_serializers/register_jsonapi_renderer.rb +65 -0
  104. data/lib/active_model_serializers/serializable_resource.rb +81 -0
  105. data/lib/active_model_serializers/serialization_context.rb +32 -0
  106. data/lib/active_model_serializers/test/schema.rb +138 -0
  107. data/lib/active_model_serializers/test/serializer.rb +125 -0
  108. data/lib/active_model_serializers/test.rb +7 -0
  109. data/lib/active_model_serializers.rb +32 -89
  110. data/lib/generators/rails/USAGE +6 -0
  111. data/lib/generators/rails/resource_override.rb +10 -0
  112. data/lib/generators/rails/serializer_generator.rb +36 -0
  113. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  114. data/lib/grape/active_model_serializers.rb +14 -0
  115. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  116. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  117. data/test/action_controller/adapter_selector_test.rb +53 -0
  118. data/test/action_controller/explicit_serializer_test.rb +134 -0
  119. data/test/action_controller/json/include_test.rb +167 -0
  120. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  121. data/test/action_controller/json_api/errors_test.rb +41 -0
  122. data/test/action_controller/json_api/linked_test.rb +197 -0
  123. data/test/action_controller/json_api/pagination_test.rb +116 -0
  124. data/test/action_controller/json_api/transform_test.rb +181 -0
  125. data/test/action_controller/serialization_scope_name_test.rb +229 -0
  126. data/test/action_controller/serialization_test.rb +469 -0
  127. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  128. data/test/active_model_serializers/json_pointer_test.rb +20 -0
  129. data/test/active_model_serializers/key_transform_test.rb +263 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +9 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
  133. data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
  134. data/test/active_model_serializers/test/schema_test.rb +130 -0
  135. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  136. data/test/active_record_test.rb +9 -0
  137. data/test/adapter/deprecation_test.rb +100 -0
  138. data/test/adapter/json/belongs_to_test.rb +45 -0
  139. data/test/adapter/json/collection_test.rb +90 -0
  140. data/test/adapter/json/has_many_test.rb +45 -0
  141. data/test/adapter/json/transform_test.rb +93 -0
  142. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  143. data/test/adapter/json_api/collection_test.rb +95 -0
  144. data/test/adapter/json_api/errors_test.rb +78 -0
  145. data/test/adapter/json_api/fields_test.rb +87 -0
  146. data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
  147. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_test.rb +144 -0
  149. data/test/adapter/json_api/has_one_test.rb +80 -0
  150. data/test/adapter/json_api/json_api_test.rb +35 -0
  151. data/test/adapter/json_api/linked_test.rb +392 -0
  152. data/test/adapter/json_api/links_test.rb +93 -0
  153. data/test/adapter/json_api/pagination_links_test.rb +166 -0
  154. data/test/adapter/json_api/parse_test.rb +137 -0
  155. data/test/adapter/json_api/relationship_test.rb +161 -0
  156. data/test/adapter/json_api/relationships_test.rb +199 -0
  157. data/test/adapter/json_api/resource_identifier_test.rb +85 -0
  158. data/test/adapter/json_api/resource_meta_test.rb +100 -0
  159. data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
  160. data/test/adapter/json_api/transform_test.rb +502 -0
  161. data/test/adapter/json_api/type_test.rb +61 -0
  162. data/test/adapter/json_test.rb +45 -0
  163. data/test/adapter/null_test.rb +23 -0
  164. data/test/adapter/polymorphic_test.rb +171 -0
  165. data/test/adapter_test.rb +67 -0
  166. data/test/array_serializer_test.rb +20 -73
  167. data/test/benchmark/app.rb +65 -0
  168. data/test/benchmark/benchmarking_support.rb +67 -0
  169. data/test/benchmark/bm_caching.rb +119 -0
  170. data/test/benchmark/bm_transform.rb +34 -0
  171. data/test/benchmark/config.ru +3 -0
  172. data/test/benchmark/controllers.rb +84 -0
  173. data/test/benchmark/fixtures.rb +219 -0
  174. data/test/cache_test.rb +485 -0
  175. data/test/collection_serializer_test.rb +110 -0
  176. data/test/fixtures/active_record.rb +78 -0
  177. data/test/fixtures/poro.rb +282 -0
  178. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  179. data/test/generators/serializer_generator_test.rb +57 -0
  180. data/test/grape_test.rb +82 -0
  181. data/test/include_tree/from_include_args_test.rb +26 -0
  182. data/test/include_tree/from_string_test.rb +94 -0
  183. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +18 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +83 -0
  188. data/test/serializers/association_macros_test.rb +36 -0
  189. data/test/serializers/associations_test.rb +295 -0
  190. data/test/serializers/attribute_test.rb +151 -0
  191. data/test/serializers/attributes_test.rb +52 -0
  192. data/test/serializers/caching_configuration_test_isolated.rb +170 -0
  193. data/test/serializers/configuration_test.rb +32 -0
  194. data/test/serializers/fieldset_test.rb +14 -0
  195. data/test/serializers/meta_test.rb +196 -0
  196. data/test/serializers/options_test.rb +21 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/root_test.rb +21 -0
  199. data/test/serializers/serialization_test.rb +55 -0
  200. data/test/serializers/serializer_for_test.rb +134 -0
  201. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  202. data/test/support/isolated_unit.rb +79 -0
  203. data/test/support/rails5_shims.rb +47 -0
  204. data/test/support/rails_app.rb +45 -0
  205. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  206. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  207. data/test/support/schemas/custom/show.json +7 -0
  208. data/test/support/schemas/hyper_schema.json +93 -0
  209. data/test/support/schemas/render_using_json_api.json +43 -0
  210. data/test/support/schemas/simple_json_pointers.json +10 -0
  211. data/test/support/serialization_testing.rb +53 -0
  212. data/test/test_helper.rb +48 -23
  213. metadata +449 -43
  214. data/DESIGN.textile +0 -586
  215. data/Gemfile.edge +0 -9
  216. data/bench/perf.rb +0 -43
  217. data/cruft.md +0 -19
  218. data/lib/active_model/array_serializer.rb +0 -104
  219. data/lib/active_record/serializer_override.rb +0 -16
  220. data/lib/generators/resource_override.rb +0 -13
  221. data/lib/generators/serializer/USAGE +0 -9
  222. data/lib/generators/serializer/serializer_generator.rb +0 -42
  223. data/lib/generators/serializer/templates/serializer.rb +0 -19
  224. data/test/association_test.rb +0 -592
  225. data/test/caching_test.rb +0 -96
  226. data/test/generators_test.rb +0 -85
  227. data/test/no_serialization_scope_test.rb +0 -34
  228. data/test/serialization_scope_name_test.rb +0 -67
  229. data/test/serialization_test.rb +0 -392
  230. data/test/serializer_support_test.rb +0 -51
  231. data/test/serializer_test.rb +0 -1465
  232. data/test/test_fakes.rb +0 -217
@@ -0,0 +1,92 @@
1
+ module ActiveModelSerializers
2
+ module Adapter
3
+ UnknownAdapterError = Class.new(ArgumentError)
4
+ ADAPTER_MAP = {} # rubocop:disable Style/MutableConstant
5
+ private_constant :ADAPTER_MAP if defined?(private_constant)
6
+
7
+ class << self # All methods are class functions
8
+ def new(*args)
9
+ fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
10
+ "Adapter.new called with args: '#{args.inspect}', from" \
11
+ "'caller[0]'."
12
+ end
13
+
14
+ def configured_adapter
15
+ lookup(ActiveModelSerializers.config.adapter)
16
+ end
17
+
18
+ def create(resource, options = {})
19
+ override = options.delete(:adapter)
20
+ klass = override ? adapter_class(override) : configured_adapter
21
+ klass.new(resource, options)
22
+ end
23
+
24
+ # @see ActiveModelSerializers::Adapter.lookup
25
+ def adapter_class(adapter)
26
+ ActiveModelSerializers::Adapter.lookup(adapter)
27
+ end
28
+
29
+ # @return [Hash<adapter_name, adapter_class>]
30
+ def adapter_map
31
+ ADAPTER_MAP
32
+ end
33
+
34
+ # @return [Array<Symbol>] list of adapter names
35
+ def adapters
36
+ adapter_map.keys.sort
37
+ end
38
+
39
+ # Adds an adapter 'klass' with 'name' to the 'adapter_map'
40
+ # Names are stringified and underscored
41
+ # @param name [Symbol, String, Class] name of the registered adapter
42
+ # @param klass [Class] adapter class itself, optional if name is the class
43
+ # @example
44
+ # AMS::Adapter.register(:my_adapter, MyAdapter)
45
+ # @note The registered name strips out 'ActiveModelSerializers::Adapter::'
46
+ # so that registering 'ActiveModelSerializers::Adapter::Json' and
47
+ # 'Json' will both register as 'json'.
48
+ def register(name, klass = name)
49
+ name = name.to_s.gsub(/\AActiveModelSerializers::Adapter::/, ''.freeze)
50
+ adapter_map[name.underscore] = klass
51
+ self
52
+ end
53
+
54
+ # @param adapter [String, Symbol, Class] name to fetch adapter by
55
+ # @return [ActiveModelSerializers::Adapter] subclass of Adapter
56
+ # @raise [UnknownAdapterError]
57
+ def lookup(adapter)
58
+ # 1. return if is a class
59
+ return adapter if adapter.is_a?(Class)
60
+ adapter_name = adapter.to_s.underscore
61
+ # 2. return if registered
62
+ adapter_map.fetch(adapter_name) do
63
+ # 3. try to find adapter class from environment
64
+ adapter_class = find_by_name(adapter_name)
65
+ register(adapter_name, adapter_class)
66
+ adapter_class
67
+ end
68
+ rescue NameError, ArgumentError => e
69
+ failure_message =
70
+ "NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
71
+ raise UnknownAdapterError, failure_message, e.backtrace
72
+ end
73
+
74
+ # @api private
75
+ def find_by_name(adapter_name)
76
+ adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
77
+ "ActiveModelSerializers::Adapter::#{adapter_name}".safe_constantize ||
78
+ "ActiveModelSerializers::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
79
+ fail UnknownAdapterError
80
+ end
81
+ private :find_by_name
82
+ end
83
+
84
+ # Gotta be at the bottom to use the code above it :(
85
+ extend ActiveSupport::Autoload
86
+ autoload :Base
87
+ autoload :Null
88
+ autoload :Attributes
89
+ autoload :Json
90
+ autoload :JsonApi
91
+ end
92
+ end
@@ -0,0 +1,55 @@
1
+ # Adapted from
2
+ # https://github.com/rails/rails/blob/7f18ea14c8/activejob/lib/active_job/callbacks.rb
3
+ require 'active_support/callbacks'
4
+
5
+ module ActiveModelSerializers
6
+ # = ActiveModelSerializers Callbacks
7
+ #
8
+ # ActiveModelSerializers provides hooks during the life cycle of serialization and
9
+ # allow you to trigger logic. Available callbacks are:
10
+ #
11
+ # * <tt>around_render</tt>
12
+ #
13
+ module Callbacks
14
+ extend ActiveSupport::Concern
15
+ include ActiveSupport::Callbacks
16
+
17
+ included do
18
+ define_callbacks :render
19
+ end
20
+
21
+ # These methods will be included into any ActiveModelSerializers object, adding
22
+ # callbacks for +render+.
23
+ module ClassMethods
24
+ # Defines a callback that will get called around the render method,
25
+ # whether it is as_json, to_json, or serializable_hash
26
+ #
27
+ # class ActiveModelSerializers::SerializableResource
28
+ # include ActiveModelSerializers::Callbacks
29
+ #
30
+ # around_render do |args, block|
31
+ # tag_logger do
32
+ # notify_render do
33
+ # block.call(args)
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ # def as_json
39
+ # run_callbacks :render do
40
+ # adapter.as_json
41
+ # end
42
+ # end
43
+ # # Note: So that we can re-use the instrumenter for as_json, to_json, and
44
+ # # serializable_hash, we aren't using the usual format, which would be:
45
+ # # def render(args)
46
+ # # adapter.as_json
47
+ # # end
48
+ # end
49
+ #
50
+ def around_render(*filters, &blk)
51
+ set_callback(:render, :around, *filters, &blk)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ ##
2
+ # Provides a single method +deprecate+ to be used to declare when
3
+ # something is going away.
4
+ #
5
+ # class Legacy
6
+ # def self.klass_method
7
+ # # ...
8
+ # end
9
+ #
10
+ # def instance_method
11
+ # # ...
12
+ # end
13
+ #
14
+ # extend ActiveModelSerializers::Deprecate
15
+ # deprecate :instance_method, "ActiveModelSerializers::NewPlace#new_method"
16
+ #
17
+ # class << self
18
+ # extend ActiveModelSerializers::Deprecate
19
+ # deprecate :klass_method, :none
20
+ # end
21
+ # end
22
+ #
23
+ # Adapted from https://github.com/rubygems/rubygems/blob/1591331/lib/rubygems/deprecate.rb
24
+ module ActiveModelSerializers
25
+ module Deprecate
26
+ ##
27
+ # Simple deprecation method that deprecates +name+ by wrapping it up
28
+ # in a dummy method. It warns on each call to the dummy method
29
+ # telling the user of +replacement+ (unless +replacement+ is :none) that it is planned to go away.
30
+
31
+ def deprecate(name, replacement)
32
+ old = "_deprecated_#{name}"
33
+ alias_method old, name
34
+ class_eval do
35
+ define_method(name) do |*args, &block|
36
+ target = is_a?(Module) ? "#{self}." : "#{self.class}#"
37
+ msg = ["NOTE: #{target}#{name} is deprecated",
38
+ replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
39
+ "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(":")}"
40
+ ]
41
+ warn "#{msg.join}."
42
+ send old, *args, &block
43
+ end
44
+ end
45
+ end
46
+
47
+ def delegate_and_deprecate(method, delegee)
48
+ delegate method, to: delegee
49
+ deprecate method, "#{delegee.name}."
50
+ end
51
+
52
+ module_function :deprecate
53
+ module_function :delegate_and_deprecate
54
+ end
55
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveModelSerializers
2
+ module Deserialization
3
+ module_function
4
+
5
+ def jsonapi_parse(*args)
6
+ Adapter::JsonApi::Deserialization.parse(*args)
7
+ end
8
+
9
+ def jsonapi_parse!(*args)
10
+ Adapter::JsonApi::Deserialization.parse!(*args)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveModelSerializers
2
+ module JsonPointer
3
+ module_function
4
+
5
+ POINTERS = {
6
+ attribute: '/data/attributes/%s'.freeze,
7
+ primary_data: '/data%s'.freeze
8
+ }.freeze
9
+
10
+ def new(pointer_type, value = nil)
11
+ format(POINTERS[pointer_type], value)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+
3
+ module ActiveModelSerializers
4
+ module KeyTransform
5
+ module_function
6
+
7
+ # Transforms values to UpperCamelCase or PascalCase.
8
+ #
9
+ # @example:
10
+ # "some_key" => "SomeKey",
11
+ # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
12
+ def camel(value)
13
+ case value
14
+ when Hash then value.deep_transform_keys! { |key| camel(key) }
15
+ when Symbol then camel(value.to_s).to_sym
16
+ when String then value.underscore.camelize
17
+ else value
18
+ end
19
+ end
20
+
21
+ # Transforms values to camelCase.
22
+ #
23
+ # @example:
24
+ # "some_key" => "someKey",
25
+ # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
26
+ def camel_lower(value)
27
+ case value
28
+ when Hash then value.deep_transform_keys! { |key| camel_lower(key) }
29
+ when Symbol then camel_lower(value.to_s).to_sym
30
+ when String then value.underscore.camelize(:lower)
31
+ else value
32
+ end
33
+ end
34
+
35
+ # Transforms values to dashed-case.
36
+ # This is the default case for the JsonApi adapter.
37
+ #
38
+ # @example:
39
+ # "some_key" => "some-key",
40
+ # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize}
41
+ def dash(value)
42
+ case value
43
+ when Hash then value.deep_transform_keys! { |key| dash(key) }
44
+ when Symbol then dash(value.to_s).to_sym
45
+ when String then value.underscore.dasherize
46
+ else value
47
+ end
48
+ end
49
+
50
+ # Transforms values to underscore_case.
51
+ # This is the default case for deserialization in the JsonApi adapter.
52
+ #
53
+ # @example:
54
+ # "some-key" => "some_key",
55
+ # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L89-L98 ActiveSupport::Inflector.underscore}
56
+ def underscore(value)
57
+ case value
58
+ when Hash then value.deep_transform_keys! { |key| underscore(key) }
59
+ when Symbol then underscore(value.to_s).to_sym
60
+ when String then value.underscore
61
+ else value
62
+ end
63
+ end
64
+
65
+ # Returns the value unaltered
66
+ def unaltered(value)
67
+ value
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,122 @@
1
+ ##
2
+ # ActiveModelSerializers::Logging
3
+ #
4
+ # https://github.com/rails/rails/blob/280654ef88/activejob/lib/active_job/logging.rb
5
+ #
6
+ module ActiveModelSerializers
7
+ module Logging
8
+ RENDER_EVENT = 'render.active_model_serializers'.freeze
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include ActiveModelSerializers::Callbacks
13
+ extend Macros
14
+ instrument_rendering
15
+ end
16
+
17
+ module ClassMethods
18
+ def instrument_rendering
19
+ around_render do |args, block|
20
+ tag_logger do
21
+ notify_render do
22
+ block.call(args)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Macros that can be used to customize the logging of class or instance methods,
30
+ # by extending the class or its singleton.
31
+ #
32
+ # Adapted from:
33
+ # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
34
+ #
35
+ # Provides a single method +notify+ to be used to declare when
36
+ # something a method notifies, with the argument +callback_name+ of the notification callback.
37
+ #
38
+ # class Adapter
39
+ # def self.klass_method
40
+ # # ...
41
+ # end
42
+ #
43
+ # def instance_method
44
+ # # ...
45
+ # end
46
+ #
47
+ # include ActiveModelSerializers::Logging::Macros
48
+ # notify :instance_method, :render
49
+ #
50
+ # class << self
51
+ # extend ActiveModelSerializers::Logging::Macros
52
+ # notify :klass_method, :render
53
+ # end
54
+ # end
55
+ module Macros
56
+ ##
57
+ # Simple notify method that wraps up +name+
58
+ # in a dummy method. It notifies on with the +callback_name+ notifier on
59
+ # each call to the dummy method, telling what the current serializer and adapter
60
+ # are being rendered.
61
+ # Adapted from:
62
+ # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
63
+ def notify(name, callback_name)
64
+ class_eval do
65
+ old = "_notifying_#{callback_name}_#{name}"
66
+ alias_method old, name
67
+ define_method name do |*args, &block|
68
+ run_callbacks callback_name do
69
+ send old, *args, &block
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def notify_render(*)
77
+ event_name = RENDER_EVENT
78
+ ActiveSupport::Notifications.instrument(event_name, notify_render_payload) do
79
+ yield
80
+ end
81
+ end
82
+
83
+ def notify_render_payload
84
+ {
85
+ serializer: serializer || ActiveModel::Serializer::Null,
86
+ adapter: adapter || ActiveModelSerializers::Adapter::Null
87
+ }
88
+ end
89
+
90
+ private
91
+
92
+ def tag_logger(*tags)
93
+ if ActiveModelSerializers.logger.respond_to?(:tagged)
94
+ tags.unshift 'active_model_serializers'.freeze unless logger_tagged_by_active_model_serializers?
95
+ ActiveModelSerializers.logger.tagged(*tags) { yield }
96
+ else
97
+ yield
98
+ end
99
+ end
100
+
101
+ def logger_tagged_by_active_model_serializers?
102
+ ActiveModelSerializers.logger.formatter.current_tags.include?('active_model_serializers'.freeze)
103
+ end
104
+
105
+ class LogSubscriber < ActiveSupport::LogSubscriber
106
+ def render(event)
107
+ info do
108
+ serializer = event.payload[:serializer]
109
+ adapter = event.payload[:adapter]
110
+ duration = event.duration.round(2)
111
+ "Rendered #{serializer.name} with #{adapter.class} (#{duration}ms)"
112
+ end
113
+ end
114
+
115
+ def logger
116
+ ActiveModelSerializers.logger
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ ActiveModelSerializers::Logging::LogSubscriber.attach_to :active_model_serializers
@@ -0,0 +1,49 @@
1
+ # ActiveModelSerializers::Model is a convenient
2
+ # serializable class to inherit from when making
3
+ # serializable non-activerecord objects.
4
+ module ActiveModelSerializers
5
+ class Model
6
+ include ActiveModel::Model
7
+ include ActiveModel::Serializers::JSON
8
+
9
+ attr_reader :attributes, :errors
10
+
11
+ def initialize(attributes = {})
12
+ @attributes = attributes
13
+ @errors = ActiveModel::Errors.new(self)
14
+ super
15
+ end
16
+
17
+ # Defaults to the downcased model name.
18
+ def id
19
+ attributes.fetch(:id) { self.class.name.downcase }
20
+ end
21
+
22
+ # Defaults to the downcased model name and updated_at
23
+ def cache_key
24
+ attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime("%Y%m%d%H%M%S%9N")}" }
25
+ end
26
+
27
+ # Defaults to the time the serializer file was modified.
28
+ def updated_at
29
+ attributes.fetch(:updated_at) { File.mtime(__FILE__) }
30
+ end
31
+
32
+ def read_attribute_for_serialization(key)
33
+ if key == :id || key == 'id'
34
+ attributes.fetch(key) { id }
35
+ else
36
+ attributes[key]
37
+ end
38
+ end
39
+
40
+ # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
+ def self.human_attribute_name(attr, _options = {})
42
+ attr
43
+ end
44
+
45
+ def self.lookup_ancestors
46
+ [self]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,46 @@
1
+ require 'rails/railtie'
2
+ require 'action_controller'
3
+ require 'action_controller/railtie'
4
+ require 'action_controller/serialization'
5
+
6
+ module ActiveModelSerializers
7
+ class Railtie < Rails::Railtie
8
+ config.to_prepare do
9
+ ActiveModel::Serializer.serializers_cache.clear
10
+ end
11
+
12
+ initializer 'active_model_serializers.action_controller' do
13
+ ActiveSupport.on_load(:action_controller) do
14
+ include(::ActionController::Serialization)
15
+ end
16
+ end
17
+
18
+ initializer 'active_model_serializers.prepare_serialization_context' do
19
+ SerializationContext.url_helpers = Rails.application.routes.url_helpers
20
+ SerializationContext.default_url_options = Rails.application.routes.default_url_options
21
+ end
22
+
23
+ # This hook is run after the action_controller railtie has set the configuration
24
+ # based on the *environment* configuration and before any config/initializers are run
25
+ # and also before eager_loading (if enabled).
26
+ initializer 'active_model_serializers.set_configs', :after => 'action_controller.set_configs' do
27
+ ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
28
+ ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
29
+ # We want this hook to run after the config has been set, even if ActionController has already loaded.
30
+ ActiveSupport.on_load(:action_controller) do
31
+ ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store
32
+ end
33
+ end
34
+
35
+ generators do |app|
36
+ Rails::Generators.configure!(app.config.generators)
37
+ Rails::Generators.hidden_namespaces.uniq!
38
+ require 'generators/rails/resource_override'
39
+ end
40
+
41
+ if Rails.env.test?
42
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
43
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,65 @@
1
+ # Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238,
2
+ # the JSON API media type will have its own format/renderer.
3
+ #
4
+ # > We recommend the media type be registered on its own as jsonapi
5
+ # when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added.
6
+ #
7
+ # Usage:
8
+ #
9
+ # ActiveSupport.on_load(:action_controller) do
10
+ # require 'active_model_serializers/register_jsonapi_renderer'
11
+ # end
12
+ #
13
+ # And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`.
14
+ #
15
+ # For example, in a controller action, we can:
16
+ # respond_to do |format|
17
+ # format.jsonapi { render jsonapi: model }
18
+ # end
19
+ #
20
+ # or
21
+ #
22
+ # render jsonapi: model
23
+ #
24
+ # No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`)
25
+
26
+ module ActiveModelSerializers::Jsonapi
27
+ MEDIA_TYPE = 'application/vnd.api+json'.freeze
28
+ HEADERS = {
29
+ response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE },
30
+ request: { 'ACCEPT'.freeze => MEDIA_TYPE }
31
+ }.freeze
32
+ module ControllerSupport
33
+ def serialize_jsonapi(json, options)
34
+ options[:adapter] = :json_api
35
+ options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) }
36
+ get_serializer(json, options)
37
+ end
38
+ end
39
+ end
40
+
41
+ # actionpack/lib/action_dispatch/http/mime_types.rb
42
+ Mime::Type.register ActiveModelSerializers::Jsonapi::MEDIA_TYPE, :jsonapi
43
+
44
+ parsers = Rails::VERSION::MAJOR >= 5 ? ActionDispatch::Http::Parameters : ActionDispatch::ParamsParser
45
+ media_type = Mime::Type.lookup(ActiveModelSerializers::Jsonapi::MEDIA_TYPE)
46
+
47
+ # Proposal: should actually deserialize the JSON API params
48
+ # to the hash format expected by `ActiveModel::Serializers::JSON`
49
+ # actionpack/lib/action_dispatch/http/parameters.rb
50
+ parsers::DEFAULT_PARSERS[media_type] = lambda do |body|
51
+ data = JSON.parse(body)
52
+ data = { :_json => data } unless data.is_a?(Hash)
53
+ data.with_indifferent_access
54
+ end
55
+
56
+ # ref https://github.com/rails/rails/pull/21496
57
+ ActionController::Renderers.add :jsonapi do |json, options|
58
+ json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
59
+ self.content_type ||= media_type
60
+ self.response_body = json
61
+ end
62
+
63
+ ActiveSupport.on_load(:action_controller) do
64
+ include ActiveModelSerializers::Jsonapi::ControllerSupport
65
+ end
@@ -0,0 +1,81 @@
1
+ require 'set'
2
+
3
+ module ActiveModelSerializers
4
+ class SerializableResource
5
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
6
+ include ActiveModelSerializers::Logging
7
+
8
+ delegate :serializable_hash, :as_json, :to_json, to: :adapter
9
+ notify :serializable_hash, :render
10
+ notify :as_json, :render
11
+ notify :to_json, :render
12
+
13
+ # Primary interface to composing a resource with a serializer and adapter.
14
+ # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
15
+ def initialize(resource, options = {})
16
+ @resource = resource
17
+ @adapter_opts, @serializer_opts =
18
+ options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
19
+ end
20
+
21
+ def serialization_scope=(scope)
22
+ serializer_opts[:scope] = scope
23
+ end
24
+
25
+ def serialization_scope
26
+ serializer_opts[:scope]
27
+ end
28
+
29
+ def serialization_scope_name=(scope_name)
30
+ serializer_opts[:scope_name] = scope_name
31
+ end
32
+
33
+ # NOTE: if no adapter is available, returns the resource itself. (i.e. adapter is a no-op)
34
+ def adapter
35
+ @adapter ||= find_adapter
36
+ end
37
+ alias adapter_instance adapter
38
+
39
+ def find_adapter
40
+ return resource unless serializer?
41
+ ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
42
+ rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
43
+ resource
44
+ end
45
+
46
+ def serializer_instance
47
+ @serializer_instance ||= serializer.new(resource, serializer_opts)
48
+ end
49
+
50
+ # Get serializer either explicitly :serializer or implicitly from resource
51
+ # Remove :serializer key from serializer_opts
52
+ # Replace :serializer key with :each_serializer if present
53
+ def serializer
54
+ @serializer ||=
55
+ begin
56
+ @serializer = serializer_opts.delete(:serializer)
57
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource)
58
+
59
+ if serializer_opts.key?(:each_serializer)
60
+ serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
61
+ end
62
+ @serializer
63
+ end
64
+ end
65
+ alias serializer_class serializer
66
+
67
+ # True when no explicit adapter given, or explicit appear is truthy (non-nil)
68
+ # False when explicit adapter is falsy (nil or false)
69
+ def use_adapter?
70
+ !(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
71
+ end
72
+
73
+ def serializer?
74
+ use_adapter? && !serializer.nil?
75
+ end
76
+
77
+ protected
78
+
79
+ attr_reader :resource, :adapter_opts, :serializer_opts
80
+ end
81
+ end