active_model_serializers 0.10.0.rc3 → 0.10.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +37 -33
  4. data/.rubocop_todo.yml +13 -88
  5. data/.simplecov +23 -11
  6. data/.travis.yml +17 -12
  7. data/CHANGELOG.md +417 -12
  8. data/CONTRIBUTING.md +206 -17
  9. data/Gemfile +12 -11
  10. data/README.md +78 -286
  11. data/Rakefile +44 -8
  12. data/active_model_serializers.gemspec +9 -1
  13. data/appveyor.yml +6 -4
  14. data/docs/ARCHITECTURE.md +120 -0
  15. data/docs/DESIGN.textile +8 -0
  16. data/docs/README.md +21 -15
  17. data/docs/general/adapters.md +79 -27
  18. data/docs/general/caching.md +52 -0
  19. data/docs/general/configuration_options.md +18 -2
  20. data/docs/general/getting_started.md +44 -19
  21. data/docs/general/instrumentation.md +40 -0
  22. data/docs/general/logging.md +14 -0
  23. data/docs/general/rendering.md +153 -0
  24. data/docs/general/serializers.md +207 -0
  25. data/docs/how-open-source-maintained.jpg +0 -0
  26. data/docs/howto/add_pagination_links.md +16 -7
  27. data/docs/howto/add_root_key.md +3 -3
  28. data/docs/howto/outside_controller_use.md +25 -9
  29. data/docs/howto/test.md +152 -0
  30. data/docs/integrations/ember-and-json-api.md +112 -0
  31. data/docs/integrations/grape.md +19 -0
  32. data/docs/jsonapi/schema.md +140 -0
  33. data/docs/jsonapi/schema/schema.json +366 -0
  34. data/lib/action_controller/serialization.rb +13 -9
  35. data/lib/active_model/serializable_resource.rb +9 -7
  36. data/lib/active_model/serializer.rb +93 -129
  37. data/lib/active_model/serializer/adapter.rb +37 -105
  38. data/lib/active_model/serializer/adapter/attributes.rb +66 -0
  39. data/lib/active_model/serializer/adapter/base.rb +58 -0
  40. data/lib/active_model/serializer/adapter/cached_serializer.rb +45 -0
  41. data/lib/active_model/serializer/adapter/fragment_cache.rb +43 -7
  42. data/lib/active_model/serializer/adapter/json.rb +11 -37
  43. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +9 -1
  44. data/lib/active_model/serializer/adapter/json_api.rb +127 -62
  45. data/lib/active_model/serializer/adapter/json_api/deserialization.rb +207 -0
  46. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +9 -1
  47. data/lib/active_model/serializer/adapter/json_api/link.rb +44 -0
  48. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +12 -4
  49. data/lib/active_model/serializer/adapter/null.rb +7 -1
  50. data/lib/active_model/serializer/array_serializer.rb +6 -37
  51. data/lib/active_model/serializer/associations.rb +21 -18
  52. data/lib/active_model/serializer/attribute.rb +25 -0
  53. data/lib/active_model/serializer/attributes.rb +82 -0
  54. data/lib/active_model/serializer/caching.rb +100 -0
  55. data/lib/active_model/serializer/collection_serializer.rb +47 -0
  56. data/lib/active_model/serializer/configuration.rb +17 -3
  57. data/lib/active_model/serializer/field.rb +56 -0
  58. data/lib/active_model/serializer/fieldset.rb +9 -18
  59. data/lib/active_model/serializer/include_tree.rb +111 -0
  60. data/lib/active_model/serializer/links.rb +33 -0
  61. data/lib/active_model/serializer/lint.rb +16 -3
  62. data/lib/active_model/serializer/reflection.rb +25 -8
  63. data/lib/active_model/serializer/type.rb +25 -0
  64. data/lib/active_model/serializer/version.rb +1 -1
  65. data/lib/active_model_serializers.rb +16 -26
  66. data/lib/active_model_serializers/callbacks.rb +55 -0
  67. data/lib/active_model_serializers/deserialization.rb +13 -0
  68. data/lib/active_model_serializers/logging.rb +119 -0
  69. data/lib/active_model_serializers/model.rb +39 -0
  70. data/lib/active_model_serializers/railtie.rb +38 -0
  71. data/lib/active_model_serializers/serialization_context.rb +10 -0
  72. data/lib/active_model_serializers/test.rb +7 -0
  73. data/lib/active_model_serializers/test/schema.rb +103 -0
  74. data/lib/active_model_serializers/test/serializer.rb +125 -0
  75. data/lib/generators/{serializer → rails}/USAGE +1 -1
  76. data/lib/generators/{serializer → rails}/resource_override.rb +1 -3
  77. data/lib/generators/{serializer → rails}/serializer_generator.rb +2 -3
  78. data/lib/generators/{serializer → rails}/templates/serializer.rb.erb +0 -0
  79. data/lib/grape/active_model_serializers.rb +14 -0
  80. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  81. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  82. data/test/action_controller/adapter_selector_test.rb +1 -1
  83. data/test/action_controller/json/include_test.rb +167 -0
  84. data/test/action_controller/json_api/deserialization_test.rb +59 -0
  85. data/test/action_controller/json_api/linked_test.rb +20 -3
  86. data/test/action_controller/json_api/pagination_test.rb +7 -7
  87. data/test/action_controller/serialization_scope_name_test.rb +8 -12
  88. data/test/action_controller/serialization_test.rb +44 -29
  89. data/test/active_model_serializers/logging_test.rb +77 -0
  90. data/test/active_model_serializers/model_test.rb +9 -0
  91. data/test/active_model_serializers/railtie_test_isolated.rb +57 -0
  92. data/test/active_model_serializers/serialization_context_test.rb +18 -0
  93. data/test/active_model_serializers/test/schema_test.rb +128 -0
  94. data/test/active_model_serializers/test/serializer_test.rb +63 -0
  95. data/test/active_record_test.rb +1 -1
  96. data/test/adapter/fragment_cache_test.rb +3 -2
  97. data/test/adapter/json/belongs_to_test.rb +2 -2
  98. data/test/adapter/json/collection_test.rb +15 -5
  99. data/test/adapter/json/has_many_test.rb +3 -3
  100. data/test/adapter/json_api/belongs_to_test.rb +3 -3
  101. data/test/adapter/json_api/collection_test.rb +8 -6
  102. data/test/adapter/json_api/fields_test.rb +89 -0
  103. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  104. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +2 -2
  105. data/test/adapter/json_api/has_many_test.rb +3 -3
  106. data/test/adapter/json_api/has_one_test.rb +2 -2
  107. data/test/adapter/json_api/json_api_test.rb +2 -2
  108. data/test/adapter/json_api/linked_test.rb +116 -5
  109. data/test/adapter/json_api/links_test.rb +68 -0
  110. data/test/adapter/json_api/pagination_links_test.rb +4 -4
  111. data/test/adapter/json_api/parse_test.rb +139 -0
  112. data/test/adapter/json_api/resource_type_config_test.rb +27 -15
  113. data/test/adapter/json_api/toplevel_jsonapi_test.rb +84 -0
  114. data/test/adapter/json_test.rb +2 -2
  115. data/test/adapter/null_test.rb +2 -2
  116. data/test/adapter_test.rb +3 -3
  117. data/test/array_serializer_test.rb +30 -93
  118. data/test/collection_serializer_test.rb +100 -0
  119. data/test/fixtures/poro.rb +19 -51
  120. data/test/generators/scaffold_controller_generator_test.rb +1 -0
  121. data/test/generators/serializer_generator_test.rb +2 -1
  122. data/test/grape_test.rb +82 -0
  123. data/test/include_tree/from_include_args_test.rb +26 -0
  124. data/test/include_tree/from_string_test.rb +94 -0
  125. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  126. data/test/lint_test.rb +4 -1
  127. data/test/logger_test.rb +2 -2
  128. data/test/poro_test.rb +1 -1
  129. data/test/serializable_resource_test.rb +1 -1
  130. data/test/serializers/adapter_for_test.rb +24 -28
  131. data/test/serializers/association_macros_test.rb +1 -1
  132. data/test/serializers/associations_test.rb +143 -26
  133. data/test/serializers/attribute_test.rb +64 -3
  134. data/test/serializers/attributes_test.rb +1 -6
  135. data/test/serializers/cache_test.rb +46 -2
  136. data/test/serializers/configuration_test.rb +20 -3
  137. data/test/serializers/fieldset_test.rb +5 -16
  138. data/test/serializers/meta_test.rb +38 -29
  139. data/test/serializers/options_test.rb +1 -1
  140. data/test/serializers/root_test.rb +1 -1
  141. data/test/serializers/serializer_for_test.rb +78 -9
  142. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  143. data/test/support/isolated_unit.rb +77 -0
  144. data/test/support/rails5_shims.rb +29 -0
  145. data/test/support/rails_app.rb +7 -3
  146. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  147. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  148. data/test/support/schemas/custom/show.json +7 -0
  149. data/test/support/schemas/hyper_schema.json +93 -0
  150. data/test/support/schemas/render_using_json_api.json +43 -0
  151. data/test/support/schemas/simple_json_pointers.json +10 -0
  152. data/test/support/serialization_testing.rb +46 -6
  153. data/test/support/test_case.rb +14 -0
  154. data/test/test_helper.rb +21 -14
  155. metadata +160 -16
  156. data/lib/active_model/serializer/adapter/flatten_json.rb +0 -12
  157. data/lib/active_model/serializer/railtie.rb +0 -15
  158. data/lib/active_model/serializer/utils.rb +0 -35
  159. data/lib/tasks/rubocop.rake +0 -0
  160. data/test/capture_warnings.rb +0 -65
  161. data/test/utils/include_args_to_hash_test.rb +0 -79
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/class/attribute'
2
+ require 'active_model_serializers/serialization_context'
2
3
 
3
4
  module ActionController
4
5
  module Serialization
@@ -9,6 +10,12 @@ module ActionController
9
10
  # Deprecated
10
11
  ADAPTER_OPTION_KEYS = ActiveModel::SerializableResource::ADAPTER_OPTION_KEYS
11
12
 
13
+ module ClassMethods
14
+ def serialization_scope(scope)
15
+ self._serialization_scope = scope
16
+ end
17
+ end
18
+
12
19
  included do
13
20
  class_attribute :_serialization_scope
14
21
  self._serialization_scope = :current_user
@@ -30,8 +37,11 @@ module ActionController
30
37
  serializable_resource.serialization_scope ||= serialization_scope
31
38
  serializable_resource.serialization_scope_name = _serialization_scope
32
39
  begin
33
- serializable_resource.adapter
34
- rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
40
+ # Necessary to ensure we have an adapter for the serializable resource
41
+ # after it has been figured.
42
+ # TODO: This logic should be less opaque and probably moved into the SerializableResource.
43
+ serializable_resource.tap(&:adapter)
44
+ rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
35
45
  resource
36
46
  end
37
47
  else
@@ -46,16 +56,10 @@ module ActionController
46
56
 
47
57
  [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
48
58
  define_method renderer_method do |resource, options|
49
- options.fetch(:context) { options[:context] = request }
59
+ options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) }
50
60
  serializable_resource = get_serializer(resource, options)
51
61
  super(serializable_resource, options)
52
62
  end
53
63
  end
54
-
55
- module ClassMethods
56
- def serialization_scope(scope)
57
- self._serialization_scope = scope
58
- end
59
- end
60
64
  end
61
65
  end
@@ -1,7 +1,13 @@
1
1
  require 'set'
2
2
  module ActiveModel
3
3
  class SerializableResource
4
- ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter])
4
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links])
5
+ include ActiveModelSerializers::Logging
6
+
7
+ delegate :serializable_hash, :as_json, :to_json, to: :adapter
8
+ notify :serializable_hash, :render
9
+ notify :as_json, :render
10
+ notify :to_json, :render
5
11
 
6
12
  # Primary interface to composing a resource with a serializer and adapter.
7
13
  # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
@@ -11,8 +17,6 @@ module ActiveModel
11
17
  options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
12
18
  end
13
19
 
14
- delegate :serializable_hash, :as_json, :to_json, to: :adapter
15
-
16
20
  def serialization_scope=(scope)
17
21
  serializer_opts[:scope] = scope
18
22
  end
@@ -61,10 +65,8 @@ module ActiveModel
61
65
  use_adapter? && !!(serializer)
62
66
  end
63
67
 
64
- private
68
+ protected
65
69
 
66
- ActiveModelSerializers.silence_warnings do
67
- attr_reader :resource, :adapter_opts, :serializer_opts
68
- end
70
+ attr_reader :resource, :adapter_opts, :serializer_opts
69
71
  end
70
72
  end
@@ -1,120 +1,111 @@
1
1
  require 'thread_safe'
2
-
2
+ require 'active_model/serializer/collection_serializer'
3
+ require 'active_model/serializer/array_serializer'
4
+ require 'active_model/serializer/include_tree'
5
+ require 'active_model/serializer/associations'
6
+ require 'active_model/serializer/attributes'
7
+ require 'active_model/serializer/caching'
8
+ require 'active_model/serializer/configuration'
9
+ require 'active_model/serializer/fieldset'
10
+ require 'active_model/serializer/lint'
11
+ require 'active_model/serializer/links'
12
+ require 'active_model/serializer/type'
13
+
14
+ # ActiveModel::Serializer is an abstract class that is
15
+ # reified when subclassed to decorate a resource.
3
16
  module ActiveModel
4
17
  class Serializer
5
- extend ActiveSupport::Autoload
6
-
7
- autoload :Configuration
8
- autoload :ArraySerializer
9
- autoload :Adapter
10
- autoload :Lint
11
- autoload :Associations
12
- autoload :Fieldset
13
- autoload :Utils
14
18
  include Configuration
15
19
  include Associations
16
-
17
- # Matches
18
- # "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
19
- # AND
20
- # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
21
- # AS
22
- # c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
23
- CALLER_FILE = /
24
- \A # start of string
25
- \S+ # one or more non-spaces
26
- (?= # stop previous match when
27
- :\d+ # a colon is followed by one or more digits
28
- :in # followed by a colon followed by in
29
- )
30
- /x
31
-
32
- class << self
33
- attr_accessor :_attributes
34
- attr_accessor :_attributes_keys
35
- attr_accessor :_cache
36
- attr_accessor :_fragmented
37
- attr_accessor :_cache_key
38
- attr_accessor :_cache_only
39
- attr_accessor :_cache_except
40
- attr_accessor :_cache_options
41
- attr_accessor :_cache_digest
20
+ include Attributes
21
+ include Caching
22
+ include Links
23
+ include Type
24
+ require 'active_model/serializer/adapter'
25
+
26
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
27
+ # @return [ActiveModel::Serializer]
28
+ # Preferentially returns
29
+ # 1. resource.serializer
30
+ # 2. ArraySerializer when resource is a collection
31
+ # 3. options[:serializer]
32
+ # 4. lookup serializer when resource is a Class
33
+ def self.serializer_for(resource, options = {})
34
+ if resource.respond_to?(:serializer_class)
35
+ resource.serializer_class
36
+ elsif resource.respond_to?(:to_ary)
37
+ config.collection_serializer
38
+ else
39
+ options.fetch(:serializer) { get_serializer_for(resource.class) }
40
+ end
42
41
  end
43
42
 
44
- def self.inherited(base)
45
- base._attributes = self._attributes.try(:dup) || []
46
- base._attributes_keys = self._attributes_keys.try(:dup) || {}
47
- base._cache_digest = digest_caller_file(caller.first)
48
- super
43
+ # @see ActiveModel::Serializer::Adapter.lookup
44
+ def self.adapter
45
+ ActiveModel::Serializer::Adapter.lookup(config.adapter)
49
46
  end
50
47
 
51
- def self.attributes(*attrs)
52
- attrs = attrs.first if attrs.first.class == Array
53
- @_attributes.concat attrs
54
- @_attributes.uniq!
48
+ # @api private
49
+ def self.serializer_lookup_chain_for(klass)
50
+ chain = []
55
51
 
56
- attrs.each do |attr|
57
- define_method attr do
58
- object && object.read_attribute_for_serialization(attr)
59
- end unless method_defined?(attr) || _fragmented.respond_to?(attr)
60
- end
61
- end
52
+ resource_class_name = klass.name.demodulize
53
+ resource_namespace = klass.name.deconstantize
54
+ serializer_class_name = "#{resource_class_name}Serializer"
62
55
 
63
- def self.attribute(attr, options = {})
64
- key = options.fetch(:key, attr)
65
- @_attributes_keys[attr] = { key: key } if key != attr
66
- @_attributes << key unless @_attributes.include?(key)
56
+ chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
57
+ chain.push("#{resource_namespace}::#{serializer_class_name}")
67
58
 
68
- ActiveModelSerializers.silence_warnings do
69
- define_method key do
70
- object.read_attribute_for_serialization(attr)
71
- end unless (key != :id && method_defined?(key)) || _fragmented.respond_to?(attr)
72
- end
59
+ chain
73
60
  end
74
61
 
75
- def self.fragmented(serializer)
76
- @_fragmented = serializer
62
+ # Used to cache serializer name => serializer class
63
+ # when looked up by Serializer.get_serializer_for.
64
+ def self.serializers_cache
65
+ @serializers_cache ||= ThreadSafe::Cache.new
77
66
  end
78
67
 
79
- # Enables a serializer to be automatically cached
80
- def self.cache(options = {})
81
- @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
82
- @_cache_key = options.delete(:key)
83
- @_cache_only = options.delete(:only)
84
- @_cache_except = options.delete(:except)
85
- @_cache_options = (options.empty?) ? nil : options
86
- end
68
+ # @api private
69
+ # Find a serializer from a class and caches the lookup.
70
+ # Preferentially retuns:
71
+ # 1. class name appended with "Serializer"
72
+ # 2. try again with superclass, if present
73
+ # 3. nil
74
+ def self.get_serializer_for(klass)
75
+ return nil unless config.serializer_lookup_enabled
76
+ serializers_cache.fetch_or_store(klass) do
77
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
78
+ serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
87
79
 
88
- def self.serializer_for(resource, options = {})
89
- if resource.respond_to?(:serializer_class)
90
- resource.serializer_class
91
- elsif resource.respond_to?(:to_ary)
92
- config.array_serializer
93
- else
94
- options.fetch(:serializer, get_serializer_for(resource.class))
80
+ if serializer_class
81
+ serializer_class
82
+ elsif klass.superclass
83
+ get_serializer_for(klass.superclass)
84
+ end
95
85
  end
96
86
  end
97
87
 
98
- # @see ActiveModel::Serializer::Adapter.lookup
99
- def self.adapter
100
- ActiveModel::Serializer::Adapter.lookup(config.adapter)
88
+ def self._serializer_instance_method_defined?(name)
89
+ _serializer_instance_methods.include?(name)
101
90
  end
102
91
 
103
- def self.root_name
104
- name.demodulize.underscore.sub(/_serializer$/, '') if name
92
+ def self._serializer_instance_methods
93
+ @_serializer_instance_methods ||= (public_instance_methods - Object.public_instance_methods).to_set
105
94
  end
95
+ private_class_method :_serializer_instance_methods
106
96
 
107
- attr_accessor :object, :root, :meta, :meta_key, :scope
97
+ attr_accessor :object, :root, :scope
108
98
 
99
+ # `scope_name` is set as :current_user by default in the controller.
100
+ # If the instance does not have a method named `scope_name`, it
101
+ # defines the method so that it calls the +scope+.
109
102
  def initialize(object, options = {})
110
- @object = object
111
- @options = options
112
- @root = options[:root]
113
- @meta = options[:meta]
114
- @meta_key = options[:meta_key]
115
- @scope = options[:scope]
116
-
117
- scope_name = options[:scope_name]
103
+ self.object = object
104
+ self.instance_options = options
105
+ self.root = instance_options[:root]
106
+ self.scope = instance_options[:scope]
107
+
108
+ scope_name = instance_options[:scope_name]
118
109
  if scope_name && !respond_to?(scope_name)
119
110
  self.class.class_eval do
120
111
  define_method scope_name, lambda { scope }
@@ -122,50 +113,23 @@ module ActiveModel
122
113
  end
123
114
  end
124
115
 
116
+ # Used by adapter as resource root.
125
117
  def json_key
126
- @root || object.class.model_name.to_s.underscore
118
+ root || object.class.model_name.to_s.underscore
127
119
  end
128
120
 
129
- def attributes(options = {})
130
- attributes =
131
- if options[:fields]
132
- self.class._attributes & options[:fields]
133
- else
134
- self.class._attributes.dup
135
- end
136
-
137
- attributes.each_with_object({}) do |name, hash|
138
- unless self.class._fragmented
139
- hash[name] = send(name)
140
- else
141
- hash[name] = self.class._fragmented.public_send(name)
142
- end
121
+ def read_attribute_for_serialization(attr)
122
+ if self.class._serializer_instance_method_defined?(attr)
123
+ send(attr)
124
+ elsif self.class._fragmented
125
+ self.class._fragmented.read_attribute_for_serialization(attr)
126
+ else
127
+ object.read_attribute_for_serialization(attr)
143
128
  end
144
129
  end
145
130
 
146
- def self.serializers_cache
147
- @serializers_cache ||= ThreadSafe::Cache.new
148
- end
149
-
150
- def self.digest_caller_file(caller_line)
151
- serializer_file_path = caller_line[CALLER_FILE]
152
- serializer_file_contents = IO.read(serializer_file_path)
153
- Digest::MD5.hexdigest(serializer_file_contents)
154
- end
155
-
156
- attr_reader :options
131
+ protected
157
132
 
158
- def self.get_serializer_for(klass)
159
- serializers_cache.fetch_or_store(klass) do
160
- serializer_class_name = "#{klass.name}Serializer"
161
- serializer_class = serializer_class_name.safe_constantize
162
-
163
- if serializer_class
164
- serializer_class
165
- elsif klass.superclass
166
- get_serializer_for(klass.superclass)
167
- end
168
- end
169
- end
133
+ attr_accessor :instance_options
170
134
  end
171
135
  end
@@ -1,30 +1,30 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- class Adapter
3
+ module Adapter
4
4
  UnknownAdapterError = Class.new(ArgumentError)
5
5
  ADAPTER_MAP = {}
6
6
  private_constant :ADAPTER_MAP if defined?(private_constant)
7
- extend ActiveSupport::Autoload
8
- autoload :FragmentCache
9
- autoload :Json
10
- autoload :JsonApi
11
- autoload :Null
12
- autoload :FlattenJson
7
+ require 'active_model/serializer/adapter/fragment_cache'
8
+ require 'active_model/serializer/adapter/cached_serializer'
9
+
10
+ class << self # All methods are class functions
11
+ def new(*args)
12
+ fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
13
+ "Adapter.new called with args: '#{args.inspect}', from" \
14
+ "'caller[0]'."
15
+ end
13
16
 
14
- def self.create(resource, options = {})
15
- override = options.delete(:adapter)
16
- klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
17
- klass.new(resource, options)
18
- end
17
+ def create(resource, options = {})
18
+ override = options.delete(:adapter)
19
+ klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
20
+ klass.new(resource, options)
21
+ end
19
22
 
20
- # @see ActiveModel::Serializer::Adapter.lookup
21
- def self.adapter_class(adapter)
22
- ActiveModel::Serializer::Adapter.lookup(adapter)
23
- end
23
+ # @see ActiveModel::Serializer::Adapter.lookup
24
+ def adapter_class(adapter)
25
+ ActiveModel::Serializer::Adapter.lookup(adapter)
26
+ end
24
27
 
25
- # Only the Adapter class has these methods.
26
- # None of the sublasses have them.
27
- class << ActiveModel::Serializer::Adapter
28
28
  # @return Hash<adapter_name, adapter_class>
29
29
  def adapter_map
30
30
  ADAPTER_MAP
@@ -37,12 +37,16 @@ module ActiveModel
37
37
 
38
38
  # Adds an adapter 'klass' with 'name' to the 'adapter_map'
39
39
  # Names are stringified and underscored
40
- # @param [Symbol, String] name of the registered adapter
41
- # @param [Class] klass - adapter class itself
40
+ # @param name [Symbol, String, Class] name of the registered adapter
41
+ # @param klass [Class] adapter class itself, optional if name is the class
42
42
  # @example
43
43
  # AMS::Adapter.register(:my_adapter, MyAdapter)
44
- def register(name, klass)
45
- adapter_map.update(name.to_s.underscore => klass)
44
+ # @note The registered name strips out 'ActiveModel::Serializer::Adapter::'
45
+ # so that registering 'ActiveModel::Serializer::Adapter::Json' and
46
+ # 'Json' will both register as 'json'.
47
+ def register(name, klass = name)
48
+ name = name.to_s.gsub(/\AActiveModel::Serializer::Adapter::/, ''.freeze)
49
+ adapter_map.update(name.underscore => klass)
46
50
  self
47
51
  end
48
52
 
@@ -54,12 +58,12 @@ module ActiveModel
54
58
  return adapter if adapter.is_a?(Class)
55
59
  adapter_name = adapter.to_s.underscore
56
60
  # 2. return if registered
57
- adapter_map.fetch(adapter_name) {
61
+ adapter_map.fetch(adapter_name) do
58
62
  # 3. try to find adapter class from environment
59
63
  adapter_class = find_by_name(adapter_name)
60
64
  register(adapter_name, adapter_class)
61
65
  adapter_class
62
- }
66
+ end
63
67
  rescue NameError, ArgumentError => e
64
68
  failure_message =
65
69
  "NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
@@ -69,91 +73,19 @@ module ActiveModel
69
73
  # @api private
70
74
  def find_by_name(adapter_name)
71
75
  adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
72
- ActiveModel::Serializer::Adapter.const_get(adapter_name.to_sym) or # rubocop:disable Style/AndOr
76
+ "ActiveModel::Serializer::Adapter::#{adapter_name}".safe_constantize ||
77
+ "ActiveModel::Serializer::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
73
78
  fail UnknownAdapterError
74
79
  end
75
80
  private :find_by_name
76
81
  end
77
82
 
78
- # Automatically register adapters when subclassing
79
- def self.inherited(subclass)
80
- ActiveModel::Serializer::Adapter.register(subclass.to_s.demodulize, subclass)
81
- end
82
-
83
- attr_reader :serializer
84
-
85
- def initialize(serializer, options = {})
86
- @serializer = serializer
87
- @options = options
88
- end
89
-
90
- def serializable_hash(options = nil)
91
- raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
92
- end
93
-
94
- def as_json(options = nil)
95
- hash = serializable_hash(options)
96
- include_meta(hash)
97
- hash
98
- end
99
-
100
- def fragment_cache(*args)
101
- raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
102
- end
103
-
104
- private
105
-
106
- def cache_check(serializer)
107
- @cached_serializer = serializer
108
- @klass = @cached_serializer.class
109
- if is_cached?
110
- @klass._cache.fetch(cache_key, @klass._cache_options) do
111
- yield
112
- end
113
- elsif is_fragment_cached?
114
- FragmentCache.new(self, @cached_serializer, @options).fetch
115
- else
116
- yield
117
- end
118
- end
119
-
120
- def is_cached?
121
- @klass._cache && !@klass._cache_only && !@klass._cache_except
122
- end
123
-
124
- def is_fragment_cached?
125
- @klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
126
- end
127
-
128
- def cache_key
129
- parts = []
130
- parts << object_cache_key
131
- parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
132
- parts.join('/')
133
- end
134
-
135
- def object_cache_key
136
- object_time_safe = @cached_serializer.object.updated_at
137
- object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
138
- (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key
139
- end
140
-
141
- def meta
142
- serializer.meta if serializer.respond_to?(:meta)
143
- end
144
-
145
- def meta_key
146
- serializer.meta_key || 'meta'
147
- end
148
-
149
- def root
150
- serializer.json_key.to_sym if serializer.json_key
151
- end
152
-
153
- def include_meta(json)
154
- json[meta_key] = meta if meta
155
- json
156
- end
83
+ # Gotta be at the bottom to use the code above it :(
84
+ require 'active_model/serializer/adapter/base'
85
+ require 'active_model/serializer/adapter/null'
86
+ require 'active_model/serializer/adapter/attributes'
87
+ require 'active_model/serializer/adapter/json'
88
+ require 'active_model/serializer/adapter/json_api'
157
89
  end
158
90
  end
159
91
  end