active_model_serializers 0.8.3 → 0.10.8

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 (235) hide show
  1. checksums.yaml +5 -5
  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 +105 -0
  6. data/.simplecov +110 -0
  7. data/.travis.yml +50 -24
  8. data/CHANGELOG.md +650 -6
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +69 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +195 -545
  14. data/Rakefile +64 -8
  15. data/active_model_serializers.gemspec +62 -23
  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/schema.json +366 -0
  48. data/docs/jsonapi/schema.md +151 -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 +43 -38
  52. data/lib/active_model/serializable_resource.rb +11 -0
  53. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  54. data/lib/active_model/serializer/adapter/base.rb +18 -0
  55. data/lib/active_model/serializer/adapter/json.rb +15 -0
  56. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  57. data/lib/active_model/serializer/adapter/null.rb +15 -0
  58. data/lib/active_model/serializer/adapter.rb +24 -0
  59. data/lib/active_model/serializer/array_serializer.rb +12 -0
  60. data/lib/active_model/serializer/association.rb +71 -0
  61. data/lib/active_model/serializer/attribute.rb +25 -0
  62. data/lib/active_model/serializer/belongs_to_reflection.rb +11 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +88 -0
  64. data/lib/active_model/serializer/concerns/caching.rb +300 -0
  65. data/lib/active_model/serializer/error_serializer.rb +14 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +32 -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 +7 -0
  71. data/lib/active_model/serializer/lazy_association.rb +96 -0
  72. data/lib/active_model/serializer/link.rb +21 -0
  73. data/lib/active_model/serializer/lint.rb +150 -0
  74. data/lib/active_model/serializer/null.rb +17 -0
  75. data/lib/active_model/serializer/reflection.rb +210 -0
  76. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  77. data/lib/active_model/serializer.rb +343 -442
  78. data/lib/active_model_serializers/adapter/attributes.rb +13 -0
  79. data/lib/active_model_serializers/adapter/base.rb +83 -0
  80. data/lib/active_model_serializers/adapter/json.rb +21 -0
  81. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  82. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  83. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  84. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  85. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  86. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +88 -0
  87. data/lib/active_model_serializers/adapter/json_api/relationship.rb +104 -0
  88. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +66 -0
  89. data/lib/active_model_serializers/adapter/json_api.rb +533 -0
  90. data/lib/active_model_serializers/adapter/null.rb +9 -0
  91. data/lib/active_model_serializers/adapter.rb +98 -0
  92. data/lib/active_model_serializers/callbacks.rb +55 -0
  93. data/lib/active_model_serializers/deprecate.rb +54 -0
  94. data/lib/active_model_serializers/deserialization.rb +15 -0
  95. data/lib/active_model_serializers/json_pointer.rb +14 -0
  96. data/lib/active_model_serializers/logging.rb +122 -0
  97. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  98. data/lib/active_model_serializers/model.rb +130 -0
  99. data/lib/active_model_serializers/railtie.rb +50 -0
  100. data/lib/active_model_serializers/register_jsonapi_renderer.rb +78 -0
  101. data/lib/active_model_serializers/serializable_resource.rb +82 -0
  102. data/lib/active_model_serializers/serialization_context.rb +39 -0
  103. data/lib/active_model_serializers/test/schema.rb +138 -0
  104. data/lib/active_model_serializers/test/serializer.rb +125 -0
  105. data/lib/active_model_serializers/test.rb +7 -0
  106. data/lib/active_model_serializers.rb +47 -81
  107. data/lib/generators/rails/USAGE +6 -0
  108. data/lib/generators/rails/resource_override.rb +10 -0
  109. data/lib/generators/rails/serializer_generator.rb +36 -0
  110. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  111. data/lib/grape/active_model_serializers.rb +16 -0
  112. data/lib/grape/formatters/active_model_serializers.rb +32 -0
  113. data/lib/grape/helpers/active_model_serializers.rb +17 -0
  114. data/lib/tasks/rubocop.rake +53 -0
  115. data/test/action_controller/adapter_selector_test.rb +62 -0
  116. data/test/action_controller/explicit_serializer_test.rb +135 -0
  117. data/test/action_controller/json/include_test.rb +246 -0
  118. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  119. data/test/action_controller/json_api/errors_test.rb +40 -0
  120. data/test/action_controller/json_api/fields_test.rb +66 -0
  121. data/test/action_controller/json_api/linked_test.rb +202 -0
  122. data/test/action_controller/json_api/pagination_test.rb +124 -0
  123. data/test/action_controller/json_api/transform_test.rb +189 -0
  124. data/test/action_controller/lookup_proc_test.rb +49 -0
  125. data/test/action_controller/namespace_lookup_test.rb +232 -0
  126. data/test/action_controller/serialization_scope_name_test.rb +235 -0
  127. data/test/action_controller/serialization_test.rb +478 -0
  128. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  129. data/test/active_model_serializers/json_pointer_test.rb +22 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +142 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +68 -0
  133. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  134. data/test/active_model_serializers/serialization_context_test_isolated.rb +71 -0
  135. data/test/active_model_serializers/test/schema_test.rb +131 -0
  136. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  137. data/test/active_record_test.rb +9 -0
  138. data/test/adapter/attributes_test.rb +40 -0
  139. data/test/adapter/deprecation_test.rb +100 -0
  140. data/test/adapter/json/belongs_to_test.rb +45 -0
  141. data/test/adapter/json/collection_test.rb +104 -0
  142. data/test/adapter/json/has_many_test.rb +53 -0
  143. data/test/adapter/json/transform_test.rb +93 -0
  144. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  145. data/test/adapter/json_api/collection_test.rb +96 -0
  146. data/test/adapter/json_api/errors_test.rb +76 -0
  147. data/test/adapter/json_api/fields_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  149. data/test/adapter/json_api/has_many_test.rb +173 -0
  150. data/test/adapter/json_api/has_one_test.rb +80 -0
  151. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  152. data/test/adapter/json_api/json_api_test.rb +33 -0
  153. data/test/adapter/json_api/linked_test.rb +413 -0
  154. data/test/adapter/json_api/links_test.rb +110 -0
  155. data/test/adapter/json_api/pagination_links_test.rb +206 -0
  156. data/test/adapter/json_api/parse_test.rb +137 -0
  157. data/test/adapter/json_api/relationship_test.rb +397 -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 +512 -0
  161. data/test/adapter/json_api/type_test.rb +193 -0
  162. data/test/adapter/json_test.rb +46 -0
  163. data/test/adapter/null_test.rb +22 -0
  164. data/test/adapter/polymorphic_test.rb +218 -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_active_record.rb +81 -0
  170. data/test/benchmark/bm_adapter.rb +38 -0
  171. data/test/benchmark/bm_caching.rb +119 -0
  172. data/test/benchmark/bm_lookup_chain.rb +83 -0
  173. data/test/benchmark/bm_transform.rb +45 -0
  174. data/test/benchmark/config.ru +3 -0
  175. data/test/benchmark/controllers.rb +83 -0
  176. data/test/benchmark/fixtures.rb +219 -0
  177. data/test/cache_test.rb +651 -0
  178. data/test/collection_serializer_test.rb +127 -0
  179. data/test/fixtures/active_record.rb +113 -0
  180. data/test/fixtures/poro.rb +225 -0
  181. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  182. data/test/generators/serializer_generator_test.rb +75 -0
  183. data/test/grape_test.rb +196 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +20 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +79 -0
  188. data/test/serializers/association_macros_test.rb +37 -0
  189. data/test/serializers/associations_test.rb +518 -0
  190. data/test/serializers/attribute_test.rb +153 -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 +202 -0
  196. data/test/serializers/options_test.rb +32 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/reflection_test.rb +479 -0
  199. data/test/serializers/root_test.rb +21 -0
  200. data/test/serializers/serialization_test.rb +55 -0
  201. data/test/serializers/serializer_for_test.rb +136 -0
  202. data/test/serializers/serializer_for_with_namespace_test.rb +88 -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 +84 -0
  205. data/test/support/rails5_shims.rb +53 -0
  206. data/test/support/rails_app.rb +38 -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 +79 -0
  214. data/test/test_helper.rb +59 -21
  215. metadata +529 -43
  216. data/DESIGN.textile +0 -586
  217. data/Gemfile.edge +0 -9
  218. data/bench/perf.rb +0 -43
  219. data/cruft.md +0 -19
  220. data/lib/active_model/array_serializer.rb +0 -104
  221. data/lib/active_model/serializer/associations.rb +0 -233
  222. data/lib/active_record/serializer_override.rb +0 -16
  223. data/lib/generators/resource_override.rb +0 -13
  224. data/lib/generators/serializer/USAGE +0 -9
  225. data/lib/generators/serializer/serializer_generator.rb +0 -42
  226. data/lib/generators/serializer/templates/serializer.rb +0 -19
  227. data/test/association_test.rb +0 -592
  228. data/test/caching_test.rb +0 -96
  229. data/test/generators_test.rb +0 -85
  230. data/test/no_serialization_scope_test.rb +0 -34
  231. data/test/serialization_scope_name_test.rb +0 -67
  232. data/test/serialization_test.rb +0 -392
  233. data/test/serializer_support_test.rb +0 -51
  234. data/test/serializer_test.rb +0 -1465
  235. data/test/test_fakes.rb +0 -217
@@ -0,0 +1,9 @@
1
+ module ActiveModelSerializers
2
+ module Adapter
3
+ class Null < Base
4
+ def serializable_hash(*)
5
+ {}
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,98 @@
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
+ # :nocov:
9
+ def new(*args)
10
+ fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
11
+ "Adapter.new called with args: '#{args.inspect}', from" \
12
+ "'caller[0]'."
13
+ end
14
+ # :nocov:
15
+
16
+ def configured_adapter
17
+ lookup(ActiveModelSerializers.config.adapter)
18
+ end
19
+
20
+ def create(resource, options = {})
21
+ override = options.delete(:adapter)
22
+ klass = override ? adapter_class(override) : configured_adapter
23
+ klass.new(resource, options)
24
+ end
25
+
26
+ # @see ActiveModelSerializers::Adapter.lookup
27
+ def adapter_class(adapter)
28
+ ActiveModelSerializers::Adapter.lookup(adapter)
29
+ end
30
+
31
+ # @return [Hash<adapter_name, adapter_class>]
32
+ def adapter_map
33
+ ADAPTER_MAP
34
+ end
35
+
36
+ # @return [Array<Symbol>] list of adapter names
37
+ def adapters
38
+ adapter_map.keys.sort
39
+ end
40
+
41
+ # Adds an adapter 'klass' with 'name' to the 'adapter_map'
42
+ # Names are stringified and underscored
43
+ # @param name [Symbol, String, Class] name of the registered adapter
44
+ # @param klass [Class] adapter class itself, optional if name is the class
45
+ # @example
46
+ # AMS::Adapter.register(:my_adapter, MyAdapter)
47
+ # @note The registered name strips out 'ActiveModelSerializers::Adapter::'
48
+ # so that registering 'ActiveModelSerializers::Adapter::Json' and
49
+ # 'Json' will both register as 'json'.
50
+ def register(name, klass = name)
51
+ name = name.to_s.gsub(/\AActiveModelSerializers::Adapter::/, ''.freeze)
52
+ adapter_map[name.underscore] = klass
53
+ self
54
+ end
55
+
56
+ def registered_name(adapter_class)
57
+ ADAPTER_MAP.key adapter_class
58
+ end
59
+
60
+ # @param adapter [String, Symbol, Class] name to fetch adapter by
61
+ # @return [ActiveModelSerializers::Adapter] subclass of Adapter
62
+ # @raise [UnknownAdapterError]
63
+ def lookup(adapter)
64
+ # 1. return if is a class
65
+ return adapter if adapter.is_a?(Class)
66
+ adapter_name = adapter.to_s.underscore
67
+ # 2. return if registered
68
+ adapter_map.fetch(adapter_name) do
69
+ # 3. try to find adapter class from environment
70
+ adapter_class = find_by_name(adapter_name)
71
+ register(adapter_name, adapter_class)
72
+ adapter_class
73
+ end
74
+ rescue NameError, ArgumentError => e
75
+ failure_message =
76
+ "NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
77
+ raise UnknownAdapterError, failure_message, e.backtrace
78
+ end
79
+
80
+ # @api private
81
+ def find_by_name(adapter_name)
82
+ adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
83
+ "ActiveModelSerializers::Adapter::#{adapter_name}".safe_constantize ||
84
+ "ActiveModelSerializers::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
85
+ fail UnknownAdapterError
86
+ end
87
+ private :find_by_name
88
+ end
89
+
90
+ # Gotta be at the bottom to use the code above it :(
91
+ extend ActiveSupport::Autoload
92
+ autoload :Base
93
+ autoload :Null
94
+ autoload :Attributes
95
+ autoload :Json
96
+ autoload :JsonApi
97
+ end
98
+ 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,54 @@
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
+ warn "#{msg.join}."
41
+ send old, *args, &block
42
+ end
43
+ end
44
+ end
45
+
46
+ def delegate_and_deprecate(method, delegee)
47
+ delegate method, to: delegee
48
+ deprecate method, "#{delegee.name}."
49
+ end
50
+
51
+ module_function :deprecate
52
+ module_function :delegate_and_deprecate
53
+ end
54
+ end
@@ -0,0 +1,15 @@
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
+ # :nocov:
10
+ def jsonapi_parse!(*args)
11
+ Adapter::JsonApi::Deserialization.parse!(*args)
12
+ end
13
+ # :nocov:
14
+ end
15
+ 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,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,80 @@
1
+ module ActiveModelSerializers
2
+ module LookupChain
3
+ # Standard appending of Serializer to the resource name.
4
+ #
5
+ # Example:
6
+ # Author => AuthorSerializer
7
+ BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
8
+ serializer_from(resource_class)
9
+ end
10
+
11
+ # Uses the namespace of the resource to find the serializer
12
+ #
13
+ # Example:
14
+ # British::Author => British::AuthorSerializer
15
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
16
+ resource_namespace = namespace_for(resource_class)
17
+ serializer_name = serializer_from(resource_class)
18
+
19
+ "#{resource_namespace}::#{serializer_name}"
20
+ end
21
+
22
+ # Uses the controller namespace of the resource to find the serializer
23
+ #
24
+ # Example:
25
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
26
+ BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
27
+ resource_name = resource_class_name(resource_class)
28
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
29
+ end
30
+
31
+ # Allows for serializers to be defined in parent serializers
32
+ # - useful if a relationship only needs a different set of attributes
33
+ # than if it were rendered independently.
34
+ #
35
+ # Example:
36
+ # class BlogSerializer < ActiveModel::Serializer
37
+ # class AuthorSerialier < ActiveModel::Serializer
38
+ # ...
39
+ # end
40
+ #
41
+ # belongs_to :author
42
+ # ...
43
+ # end
44
+ #
45
+ # The belongs_to relationship would be rendered with
46
+ # BlogSerializer::AuthorSerialier
47
+ BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
48
+ return if serializer_class == ActiveModel::Serializer
49
+
50
+ serializer_name = serializer_from(resource_class)
51
+ "#{serializer_class}::#{serializer_name}"
52
+ end
53
+
54
+ DEFAULT = [
55
+ BY_PARENT_SERIALIZER,
56
+ BY_NAMESPACE,
57
+ BY_RESOURCE_NAMESPACE,
58
+ BY_RESOURCE
59
+ ].freeze
60
+
61
+ module_function
62
+
63
+ def namespace_for(klass)
64
+ klass.name.deconstantize
65
+ end
66
+
67
+ def resource_class_name(klass)
68
+ klass.name.demodulize
69
+ end
70
+
71
+ def serializer_from_resource_name(name)
72
+ "#{name}Serializer"
73
+ end
74
+
75
+ def serializer_from(klass)
76
+ name = resource_class_name(klass)
77
+ serializer_from_resource_name(name)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,130 @@
1
+ # ActiveModelSerializers::Model is a convenient superclass for making your models
2
+ # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
3
+ # that satisfies ActiveModel::Serializer::Lint::Tests.
4
+ require 'active_support/core_ext/hash'
5
+ module ActiveModelSerializers
6
+ class Model
7
+ include ActiveModel::Serializers::JSON
8
+ include ActiveModel::Model
9
+
10
+ # Declare names of attributes to be included in +attributes+ hash.
11
+ # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
12
+ # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
13
+ #
14
+ # @overload attribute_names
15
+ # @return [Array<Symbol>]
16
+ class_attribute :attribute_names, instance_writer: false, instance_reader: false
17
+ # Initialize +attribute_names+ for all subclasses. The array is usually
18
+ # mutated in the +attributes+ method, but can be set directly, as well.
19
+ self.attribute_names = []
20
+
21
+ # Easily declare instance attributes with setters and getters for each.
22
+ #
23
+ # To initialize an instance, all attributes must have setters.
24
+ # However, the hash returned by +attributes+ instance method will ALWAYS
25
+ # be the value of the initial attributes, regardless of what accessors are defined.
26
+ # The only way to change the change the attributes after initialization is
27
+ # to mutate the +attributes+ directly.
28
+ # Accessor methods do NOT mutate the attributes. (This is a bug).
29
+ #
30
+ # @note For now, the Model only supports the notion of 'attributes'.
31
+ # In the tests, there is a special Model that also supports 'associations'. This is
32
+ # important so that we can add accessors for values that should not appear in the
33
+ # attributes hash when modeling associations. It is not yet clear if it
34
+ # makes sense for a PORO to have associations outside of the tests.
35
+ #
36
+ # @overload attributes(names)
37
+ # @param names [Array<String, Symbol>]
38
+ # @param name [String, Symbol]
39
+ def self.attributes(*names)
40
+ self.attribute_names |= names.map(&:to_sym)
41
+ # Silence redefinition of methods warnings
42
+ ActiveModelSerializers.silence_warnings do
43
+ attr_accessor(*names)
44
+ end
45
+ end
46
+
47
+ # Opt-in to breaking change
48
+ def self.derive_attributes_from_names_and_fix_accessors
49
+ unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
50
+ prepend(DeriveAttributesFromNamesAndFixAccessors)
51
+ end
52
+ end
53
+
54
+ module DeriveAttributesFromNamesAndFixAccessors
55
+ def self.included(base)
56
+ # NOTE that +id+ will always be in +attributes+.
57
+ base.attributes :id
58
+ end
59
+
60
+ # Override the +attributes+ method so that the hash is derived from +attribute_names+.
61
+ #
62
+ # The fields in +attribute_names+ determines the returned hash.
63
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
64
+ # the actual values in the model.
65
+ def attributes
66
+ self.class.attribute_names.each_with_object({}) do |attribute_name, result|
67
+ result[attribute_name] = public_send(attribute_name).freeze
68
+ end.with_indifferent_access.freeze
69
+ end
70
+ end
71
+
72
+ # Support for validation and other ActiveModel::Errors
73
+ # @return [ActiveModel::Errors]
74
+ attr_reader :errors
75
+
76
+ # (see #updated_at)
77
+ attr_writer :updated_at
78
+
79
+ # The only way to change the attributes of an instance is to directly mutate the attributes.
80
+ # @example
81
+ #
82
+ # model.attributes[:foo] = :bar
83
+ # @return [Hash]
84
+ attr_reader :attributes
85
+
86
+ # @param attributes [Hash]
87
+ def initialize(attributes = {})
88
+ attributes ||= {} # protect against nil
89
+ @attributes = attributes.symbolize_keys.with_indifferent_access
90
+ @errors = ActiveModel::Errors.new(self)
91
+ super
92
+ end
93
+
94
+ # Defaults to the downcased model name.
95
+ # This probably isn't a good default, since it's not a unique instance identifier,
96
+ # but that's what is currently implemented \_('-')_/.
97
+ #
98
+ # @note Though +id+ is defined, it will only show up
99
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
100
+ # such as <tt>attributes[:id] = 5</tt>.
101
+ # @return [String, Numeric, Symbol]
102
+ def id
103
+ attributes.fetch(:id) do
104
+ defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
105
+ end
106
+ end
107
+
108
+ # When not set, defaults to the time the file was modified.
109
+ #
110
+ # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
111
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
112
+ # such as <tt>attributes[:updated_at] = Time.current</tt>.
113
+ # @return [String, Numeric, Time]
114
+ def updated_at
115
+ attributes.fetch(:updated_at) do
116
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
117
+ end
118
+ end
119
+
120
+ # To customize model behavior, this method must be redefined. However,
121
+ # there are other ways of setting the +cache_key+ a serializer uses.
122
+ # @return [String]
123
+ def cache_key
124
+ ActiveSupport::Cache.expand_cache_key([
125
+ self.class.model_name.name.downcase,
126
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
127
+ ].compact)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,50 @@
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.eager_load_namespaces << ActiveModelSerializers
9
+
10
+ config.to_prepare do
11
+ ActiveModel::Serializer.serializers_cache.clear
12
+ end
13
+
14
+ initializer 'active_model_serializers.action_controller' do
15
+ ActiveSupport.on_load(:action_controller) do
16
+ include(::ActionController::Serialization)
17
+ end
18
+ end
19
+
20
+ initializer 'active_model_serializers.prepare_serialization_context' do
21
+ SerializationContext.url_helpers = Rails.application.routes.url_helpers
22
+ SerializationContext.default_url_options = Rails.application.routes.default_url_options
23
+ end
24
+
25
+ # This hook is run after the action_controller railtie has set the configuration
26
+ # based on the *environment* configuration and before any config/initializers are run
27
+ # and also before eager_loading (if enabled).
28
+ initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
29
+ ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
30
+ ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
31
+ # We want this hook to run after the config has been set, even if ActionController has already loaded.
32
+ ActiveSupport.on_load(:action_controller) do
33
+ ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store
34
+ end
35
+ end
36
+
37
+ # :nocov:
38
+ generators do |app|
39
+ Rails::Generators.configure!(app.config.generators)
40
+ Rails::Generators.hidden_namespaces.uniq!
41
+ require 'generators/rails/resource_override'
42
+ end
43
+ # :nocov:
44
+
45
+ if Rails.env.test?
46
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
47
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer)
48
+ end
49
+ end
50
+ end