active_model_serializers 0.8.3 → 0.10.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) 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 +7 -0
  5. data/.rubocop.yml +104 -0
  6. data/.rubocop_todo.yml +167 -0
  7. data/.simplecov +110 -0
  8. data/.travis.yml +46 -23
  9. data/CHANGELOG.md +442 -6
  10. data/CONTRIBUTING.md +95 -0
  11. data/Gemfile +51 -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 +28 -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 +39 -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 +339 -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/test.md +152 -0
  39. data/docs/integrations/ember-and-json-api.md +112 -0
  40. data/docs/integrations/grape.md +19 -0
  41. data/docs/jsonapi/errors.md +56 -0
  42. data/docs/jsonapi/schema/schema.json +366 -0
  43. data/docs/jsonapi/schema.md +151 -0
  44. data/docs/rfcs/0000-namespace.md +106 -0
  45. data/docs/rfcs/template.md +15 -0
  46. data/lib/action_controller/serialization.rb +31 -36
  47. data/lib/active_model/serializable_resource.rb +11 -0
  48. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  49. data/lib/active_model/serializer/adapter/base.rb +16 -0
  50. data/lib/active_model/serializer/adapter/json.rb +15 -0
  51. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  52. data/lib/active_model/serializer/adapter/null.rb +15 -0
  53. data/lib/active_model/serializer/adapter.rb +24 -0
  54. data/lib/active_model/serializer/array_serializer.rb +9 -0
  55. data/lib/active_model/serializer/association.rb +19 -0
  56. data/lib/active_model/serializer/associations.rb +87 -220
  57. data/lib/active_model/serializer/attribute.rb +25 -0
  58. data/lib/active_model/serializer/attributes.rb +82 -0
  59. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  60. data/lib/active_model/serializer/caching.rb +151 -0
  61. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  62. data/lib/active_model/serializer/collection_serializer.rb +64 -0
  63. data/lib/active_model/serializer/configuration.rb +35 -0
  64. data/lib/active_model/serializer/error_serializer.rb +10 -0
  65. data/lib/active_model/serializer/errors_serializer.rb +27 -0
  66. data/lib/active_model/serializer/field.rb +56 -0
  67. data/lib/active_model/serializer/fieldset.rb +31 -0
  68. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  69. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  70. data/lib/active_model/serializer/include_tree.rb +111 -0
  71. data/lib/active_model/serializer/links.rb +35 -0
  72. data/lib/active_model/serializer/lint.rb +156 -0
  73. data/lib/active_model/serializer/meta.rb +29 -0
  74. data/lib/active_model/serializer/null.rb +17 -0
  75. data/lib/active_model/serializer/reflection.rb +147 -0
  76. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  77. data/lib/active_model/serializer/type.rb +25 -0
  78. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  79. data/lib/active_model/serializer.rb +156 -481
  80. data/lib/active_model_serializers/adapter/attributes.rb +94 -0
  81. data/lib/active_model_serializers/adapter/base.rb +90 -0
  82. data/lib/active_model_serializers/adapter/json.rb +11 -0
  83. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  84. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  85. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  86. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  87. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  88. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +57 -0
  89. data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
  90. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
  91. data/lib/active_model_serializers/adapter/json_api.rb +513 -0
  92. data/lib/active_model_serializers/adapter/null.rb +10 -0
  93. data/lib/active_model_serializers/adapter.rb +92 -0
  94. data/lib/active_model_serializers/cached_serializer.rb +87 -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/fragment_cache.rb +118 -0
  99. data/lib/active_model_serializers/json_pointer.rb +14 -0
  100. data/lib/active_model_serializers/key_transform.rb +70 -0
  101. data/lib/active_model_serializers/logging.rb +122 -0
  102. data/lib/active_model_serializers/model.rb +49 -0
  103. data/lib/active_model_serializers/railtie.rb +46 -0
  104. data/lib/active_model_serializers/register_jsonapi_renderer.rb +64 -0
  105. data/lib/active_model_serializers/serializable_resource.rb +81 -0
  106. data/lib/active_model_serializers/serialization_context.rb +32 -0
  107. data/lib/active_model_serializers/test/schema.rb +103 -0
  108. data/lib/active_model_serializers/test/serializer.rb +125 -0
  109. data/lib/active_model_serializers/test.rb +7 -0
  110. data/lib/active_model_serializers.rb +34 -89
  111. data/lib/generators/rails/USAGE +6 -0
  112. data/lib/generators/rails/resource_override.rb +10 -0
  113. data/lib/generators/rails/serializer_generator.rb +36 -0
  114. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  115. data/lib/grape/active_model_serializers.rb +14 -0
  116. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  117. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  118. data/test/action_controller/adapter_selector_test.rb +53 -0
  119. data/test/action_controller/explicit_serializer_test.rb +134 -0
  120. data/test/action_controller/json/include_test.rb +167 -0
  121. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  122. data/test/action_controller/json_api/errors_test.rb +41 -0
  123. data/test/action_controller/json_api/linked_test.rb +197 -0
  124. data/test/action_controller/json_api/pagination_test.rb +116 -0
  125. data/test/action_controller/json_api/transform_test.rb +180 -0
  126. data/test/action_controller/serialization_scope_name_test.rb +229 -0
  127. data/test/action_controller/serialization_test.rb +467 -0
  128. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  129. data/test/active_model_serializers/cached_serializer_test.rb +80 -0
  130. data/test/active_model_serializers/fragment_cache_test.rb +34 -0
  131. data/test/active_model_serializers/json_pointer_test.rb +20 -0
  132. data/test/active_model_serializers/key_transform_test.rb +263 -0
  133. data/test/active_model_serializers/logging_test.rb +77 -0
  134. data/test/active_model_serializers/model_test.rb +9 -0
  135. data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
  136. data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
  137. data/test/active_model_serializers/test/schema_test.rb +128 -0
  138. data/test/active_model_serializers/test/serializer_test.rb +63 -0
  139. data/test/active_record_test.rb +9 -0
  140. data/test/adapter/deprecation_test.rb +100 -0
  141. data/test/adapter/json/belongs_to_test.rb +45 -0
  142. data/test/adapter/json/collection_test.rb +90 -0
  143. data/test/adapter/json/has_many_test.rb +45 -0
  144. data/test/adapter/json/transform_test.rb +93 -0
  145. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  146. data/test/adapter/json_api/collection_test.rb +95 -0
  147. data/test/adapter/json_api/errors_test.rb +78 -0
  148. data/test/adapter/json_api/fields_test.rb +87 -0
  149. data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
  150. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  151. data/test/adapter/json_api/has_many_test.rb +143 -0
  152. data/test/adapter/json_api/has_one_test.rb +79 -0
  153. data/test/adapter/json_api/json_api_test.rb +35 -0
  154. data/test/adapter/json_api/linked_test.rb +392 -0
  155. data/test/adapter/json_api/links_test.rb +93 -0
  156. data/test/adapter/json_api/pagination_links_test.rb +148 -0
  157. data/test/adapter/json_api/parse_test.rb +137 -0
  158. data/test/adapter/json_api/relationship_test.rb +161 -0
  159. data/test/adapter/json_api/relationships_test.rb +199 -0
  160. data/test/adapter/json_api/resource_identifier_test.rb +85 -0
  161. data/test/adapter/json_api/resource_meta_test.rb +100 -0
  162. data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
  163. data/test/adapter/json_api/transform_test.rb +500 -0
  164. data/test/adapter/json_api/type_test.rb +61 -0
  165. data/test/adapter/json_test.rb +45 -0
  166. data/test/adapter/null_test.rb +23 -0
  167. data/test/adapter/polymorphic_test.rb +72 -0
  168. data/test/adapter_test.rb +40 -0
  169. data/test/array_serializer_test.rb +35 -73
  170. data/test/benchmark/app.rb +65 -0
  171. data/test/benchmark/benchmarking_support.rb +67 -0
  172. data/test/benchmark/bm_caching.rb +117 -0
  173. data/test/benchmark/bm_transform.rb +34 -0
  174. data/test/benchmark/config.ru +3 -0
  175. data/test/benchmark/controllers.rb +77 -0
  176. data/test/benchmark/fixtures.rb +167 -0
  177. data/test/cache_test.rb +388 -0
  178. data/test/collection_serializer_test.rb +110 -0
  179. data/test/fixtures/active_record.rb +68 -0
  180. data/test/fixtures/poro.rb +254 -0
  181. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  182. data/test/generators/serializer_generator_test.rb +57 -0
  183. data/test/grape_test.rb +82 -0
  184. data/test/include_tree/from_include_args_test.rb +26 -0
  185. data/test/include_tree/from_string_test.rb +94 -0
  186. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  187. data/test/lint_test.rb +49 -0
  188. data/test/logger_test.rb +18 -0
  189. data/test/poro_test.rb +9 -0
  190. data/test/serializable_resource_test.rb +83 -0
  191. data/test/serializers/association_macros_test.rb +36 -0
  192. data/test/serializers/associations_test.rb +267 -0
  193. data/test/serializers/attribute_test.rb +123 -0
  194. data/test/serializers/attributes_test.rb +52 -0
  195. data/test/serializers/caching_configuration_test_isolated.rb +170 -0
  196. data/test/serializers/configuration_test.rb +32 -0
  197. data/test/serializers/fieldset_test.rb +14 -0
  198. data/test/serializers/meta_test.rb +198 -0
  199. data/test/serializers/options_test.rb +21 -0
  200. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  201. data/test/serializers/root_test.rb +21 -0
  202. data/test/serializers/serialization_test.rb +55 -0
  203. data/test/serializers/serializer_for_test.rb +134 -0
  204. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  205. data/test/support/isolated_unit.rb +80 -0
  206. data/test/support/rails5_shims.rb +47 -0
  207. data/test/support/rails_app.rb +45 -0
  208. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  209. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  210. data/test/support/schemas/custom/show.json +7 -0
  211. data/test/support/schemas/hyper_schema.json +93 -0
  212. data/test/support/schemas/render_using_json_api.json +43 -0
  213. data/test/support/schemas/simple_json_pointers.json +10 -0
  214. data/test/support/serialization_testing.rb +53 -0
  215. data/test/test_helper.rb +51 -24
  216. metadata +456 -45
  217. data/DESIGN.textile +0 -586
  218. data/Gemfile.edge +0 -9
  219. data/bench/perf.rb +0 -43
  220. data/cruft.md +0 -19
  221. data/lib/active_model/array_serializer.rb +0 -104
  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,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,87 @@
1
+ module ActiveModelSerializers
2
+ class CachedSerializer
3
+ UndefinedCacheKey = Class.new(StandardError)
4
+
5
+ def initialize(serializer)
6
+ @cached_serializer = serializer
7
+ @klass = @cached_serializer.class
8
+ end
9
+
10
+ def cache_check(adapter_instance)
11
+ if cached?
12
+ @klass._cache.fetch(cache_key(adapter_instance), @klass._cache_options) do
13
+ yield
14
+ end
15
+ elsif fragment_cached?
16
+ FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch
17
+ else
18
+ yield
19
+ end
20
+ end
21
+
22
+ def cached?
23
+ @klass.cache_enabled?
24
+ end
25
+
26
+ def fragment_cached?
27
+ @klass.fragment_cache_enabled?
28
+ end
29
+
30
+ def cache_key(adapter_instance)
31
+ return @cache_key if defined?(@cache_key)
32
+
33
+ parts = []
34
+ parts << object_cache_key
35
+ parts << adapter_instance.cached_name
36
+ parts << @klass._cache_digest unless @klass._skip_digest?
37
+ @cache_key = parts.join('/')
38
+ end
39
+
40
+ # Use object's cache_key if available, else derive a key from the object
41
+ # Pass the `key` option to the `cache` declaration or override this method to customize the cache key
42
+ def object_cache_key
43
+ if @cached_serializer.object.respond_to?(:cache_key)
44
+ @cached_serializer.object.cache_key
45
+ elsif (cache_key = (@klass._cache_key || @klass._cache_options[:key]))
46
+ object_time_safe = @cached_serializer.object.updated_at
47
+ object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
48
+ "#{cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}"
49
+ else
50
+ fail UndefinedCacheKey, "#{@cached_serializer.object.class} must define #cache_key, or the 'key:' option must be passed into '#{@klass}.cache'"
51
+ end
52
+ end
53
+
54
+ # find all cache_key for the collection_serializer
55
+ # @param serializers [ActiveModel::Serializer::CollectionSerializer]
56
+ # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
57
+ # @param include_tree [ActiveModel::Serializer::IncludeTree]
58
+ # @return [Array] all cache_key of collection_serializer
59
+ def self.object_cache_keys(serializers, adapter_instance, include_tree)
60
+ cache_keys = []
61
+
62
+ serializers.each do |serializer|
63
+ cache_keys << object_cache_key(serializer, adapter_instance)
64
+
65
+ serializer.associations(include_tree).each do |association|
66
+ if association.serializer.respond_to?(:each)
67
+ association.serializer.each do |sub_serializer|
68
+ cache_keys << object_cache_key(sub_serializer, adapter_instance)
69
+ end
70
+ else
71
+ cache_keys << object_cache_key(association.serializer, adapter_instance)
72
+ end
73
+ end
74
+ end
75
+
76
+ cache_keys.compact.uniq
77
+ end
78
+
79
+ # @return [String, nil] the cache_key of the serializer or nil
80
+ def self.object_cache_key(serializer, adapter_instance)
81
+ return unless serializer.present? && serializer.object.present?
82
+
83
+ cached_serializer = new(serializer)
84
+ cached_serializer.cached? ? cached_serializer.cache_key(adapter_instance) : nil
85
+ end
86
+ end
87
+ 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,118 @@
1
+ module ActiveModelSerializers
2
+ class FragmentCache
3
+ attr_reader :serializer
4
+
5
+ def initialize(adapter, serializer, options)
6
+ @instance_options = options
7
+ @adapter = adapter
8
+ @serializer = serializer
9
+ end
10
+
11
+ # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
12
+ # 2. Serialize the above two with the given adapter
13
+ # 3. Pass their serializations to the adapter +::fragment_cache+
14
+ def fetch
15
+ object = serializer.object
16
+
17
+ # It will split the serializer into two, one that will be cached and one that will not
18
+ serializers = fragment_serializer
19
+
20
+ # Get serializable hash from both
21
+ cached_hash = serialize(object, serializers[:cached])
22
+ non_cached_hash = serialize(object, serializers[:non_cached])
23
+
24
+ # Merge both results
25
+ adapter.fragment_cache(cached_hash, non_cached_hash)
26
+ end
27
+
28
+ protected
29
+
30
+ attr_reader :instance_options, :adapter
31
+
32
+ private
33
+
34
+ def serialize(object, serializer_class)
35
+ SerializableResource.new(
36
+ object,
37
+ serializer: serializer_class,
38
+ adapter: adapter.class
39
+ ).serializable_hash
40
+ end
41
+
42
+ # Given a hash of its cached and non-cached serializers
43
+ # 1. Determine cached attributes from serializer class options
44
+ # 2. Add cached attributes to cached Serializer
45
+ # 3. Add non-cached attributes to non-cached Serializer
46
+ def cache_attributes(serializers)
47
+ klass = serializer.class
48
+ attributes = klass._attributes
49
+ cache_only = klass._cache_only
50
+ cached_attributes = cache_only ? cache_only : attributes - klass._cache_except
51
+ non_cached_attributes = attributes - cached_attributes
52
+ attributes_keys = klass._attributes_keys
53
+
54
+ add_attributes_to_serializer(serializers[:cached], cached_attributes, attributes_keys)
55
+ add_attributes_to_serializer(serializers[:non_cached], non_cached_attributes, attributes_keys)
56
+ end
57
+
58
+ def add_attributes_to_serializer(serializer, attributes, attributes_keys)
59
+ attributes.each do |attribute|
60
+ options = attributes_keys[attribute] || {}
61
+ serializer.attribute(attribute, options)
62
+ end
63
+ end
64
+
65
+ # Given a resource name
66
+ # 1. Dynamically creates a CachedSerializer and NonCachedSerializer
67
+ # for a given class 'name'
68
+ # 2. Call
69
+ # CachedSerializer.cache(serializer._cache_options)
70
+ # CachedSerializer.fragmented(serializer)
71
+ # NonCachedSerializer.cache(serializer._cache_options)
72
+ # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
73
+ # 4. Call +cached_attributes+ on the serializer class and the above hash
74
+ # 5. Return the hash
75
+ #
76
+ # @example
77
+ # When +name+ is <tt>User::Admin</tt>
78
+ # creates the Serializer classes (if they don't exist).
79
+ # CachedUser_AdminSerializer
80
+ # NonCachedUser_AdminSerializer
81
+ #
82
+ def fragment_serializer
83
+ klass = serializer.class
84
+ serializer_class_name = to_valid_const_name(klass.name)
85
+
86
+ cached = "Cached#{serializer_class_name}"
87
+ non_cached = "NonCached#{serializer_class_name}"
88
+
89
+ cached_serializer = get_or_create_serializer(cached)
90
+ non_cached_serializer = get_or_create_serializer(non_cached)
91
+
92
+ klass._cache_options ||= {}
93
+ cache_key = klass._cache_key
94
+ klass._cache_options[:key] = cache_key if cache_key
95
+ cached_serializer.cache(klass._cache_options)
96
+
97
+ type = klass._type
98
+ cached_serializer.type(type)
99
+ non_cached_serializer.type(type)
100
+
101
+ non_cached_serializer.fragmented(serializer)
102
+ cached_serializer.fragmented(serializer)
103
+
104
+ serializers = { cached: cached_serializer, non_cached: non_cached_serializer }
105
+ cache_attributes(serializers)
106
+ serializers
107
+ end
108
+
109
+ def get_or_create_serializer(name)
110
+ return Object.const_get(name) if Object.const_defined?(name)
111
+ Object.const_set(name, Class.new(ActiveModel::Serializer))
112
+ end
113
+
114
+ def to_valid_const_name(name)
115
+ name.gsub('::', '_')
116
+ end
117
+ end
118
+ 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