active_model_serializers 0.9.13 → 0.10.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +21 -0
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +8 -236
  5. data/CONTRIBUTING.md +23 -12
  6. data/Gemfile +17 -0
  7. data/{MIT-LICENSE → LICENSE.txt} +3 -2
  8. data/README.md +151 -781
  9. data/Rakefile +12 -0
  10. data/active_model_serializers.gemspec +26 -0
  11. data/lib/action_controller/serialization.rb +30 -84
  12. data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
  13. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
  14. data/lib/active_model/serializer/adapter/json.rb +52 -0
  15. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
  16. data/lib/active_model/serializer/adapter/json_api.rb +152 -0
  17. data/lib/active_model/serializer/adapter/null.rb +11 -0
  18. data/lib/active_model/serializer/adapter.rb +87 -0
  19. data/lib/active_model/serializer/array_serializer.rb +32 -0
  20. data/lib/active_model/serializer/configuration.rb +13 -0
  21. data/lib/active_model/serializer/fieldset.rb +40 -0
  22. data/lib/active_model/serializer/version.rb +1 -3
  23. data/lib/active_model/serializer.rb +193 -276
  24. data/lib/active_model_serializers.rb +5 -19
  25. data/lib/generators/serializer/USAGE +6 -0
  26. data/lib/{active_model/serializer/generators → generators}/serializer/serializer_generator.rb +8 -10
  27. data/lib/{active_model/serializer/generators → generators}/serializer/templates/serializer.rb +2 -2
  28. data/test/action_controller/adapter_selector_test.rb +51 -0
  29. data/test/action_controller/explicit_serializer_test.rb +110 -0
  30. data/test/action_controller/json_api_linked_test.rb +173 -0
  31. data/test/action_controller/serialization_scope_name_test.rb +63 -0
  32. data/test/action_controller/serialization_test.rb +365 -0
  33. data/test/adapter/fragment_cache_test.rb +27 -0
  34. data/test/adapter/json/belongs_to_test.rb +41 -0
  35. data/test/adapter/json/collection_test.rb +59 -0
  36. data/test/adapter/json/has_many_test.rb +36 -0
  37. data/test/adapter/json_api/belongs_to_test.rb +147 -0
  38. data/test/adapter/json_api/collection_test.rb +89 -0
  39. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  40. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  41. data/test/adapter/json_api/has_many_test.rb +106 -0
  42. data/test/adapter/json_api/has_one_test.rb +59 -0
  43. data/test/adapter/json_api/linked_test.rb +257 -0
  44. data/test/adapter/json_test.rb +34 -0
  45. data/test/adapter/null_test.rb +25 -0
  46. data/test/adapter_test.rb +43 -0
  47. data/test/array_serializer_test.rb +29 -0
  48. data/test/fixtures/poro.rb +123 -172
  49. data/test/serializers/adapter_for_test.rb +50 -0
  50. data/test/serializers/associations_test.rb +106 -0
  51. data/test/serializers/attribute_test.rb +23 -0
  52. data/test/serializers/attributes_test.rb +28 -0
  53. data/test/serializers/cache_test.rb +128 -0
  54. data/test/serializers/configuration_test.rb +15 -0
  55. data/test/serializers/fieldset_test.rb +26 -0
  56. data/test/serializers/generators_test.rb +59 -0
  57. data/test/serializers/meta_test.rb +78 -0
  58. data/test/serializers/options_test.rb +21 -0
  59. data/test/serializers/serializer_for_test.rb +56 -0
  60. data/test/serializers/urls_test.rb +26 -0
  61. data/test/test_helper.rb +20 -10
  62. metadata +121 -148
  63. data/DESIGN.textile +0 -586
  64. data/lib/action_controller/serialization_test_case.rb +0 -82
  65. data/lib/active_model/array_serializer.rb +0 -70
  66. data/lib/active_model/default_serializer.rb +0 -30
  67. data/lib/active_model/serializable/utils.rb +0 -18
  68. data/lib/active_model/serializable.rb +0 -61
  69. data/lib/active_model/serializer/association/has_many.rb +0 -41
  70. data/lib/active_model/serializer/association/has_one.rb +0 -27
  71. data/lib/active_model/serializer/association.rb +0 -58
  72. data/lib/active_model/serializer/config.rb +0 -33
  73. data/lib/active_model/serializer/generators/resource_override.rb +0 -15
  74. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  75. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -16
  76. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  77. data/lib/active_model/serializer/railtie.rb +0 -24
  78. data/lib/active_model/serializer_support.rb +0 -7
  79. data/test/benchmark/app.rb +0 -60
  80. data/test/benchmark/benchmarking_support.rb +0 -67
  81. data/test/benchmark/bm_active_record.rb +0 -41
  82. data/test/benchmark/setup.rb +0 -75
  83. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  84. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  85. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  86. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  87. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  88. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  93. data/test/fixtures/active_record.rb +0 -96
  94. data/test/fixtures/template.html.erb +0 -1
  95. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  96. data/test/integration/action_controller/serialization_test.rb +0 -287
  97. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  98. data/test/integration/active_record/active_record_test.rb +0 -94
  99. data/test/integration/generators/resource_generator_test.rb +0 -26
  100. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  101. data/test/integration/generators/serializer_generator_test.rb +0 -41
  102. data/test/test_app.rb +0 -18
  103. data/test/tmp/app/serializers/account_serializer.rb +0 -3
  104. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  105. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  106. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  107. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  108. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  109. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  110. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  111. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -239
  112. data/test/unit/active_model/default_serializer_test.rb +0 -13
  113. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  114. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  115. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  116. data/test/unit/active_model/serializer/config_test.rb +0 -91
  117. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  118. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  119. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  120. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  121. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  122. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  123. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  124. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  125. data/test/unit/active_model/serializer/options_test.rb +0 -42
  126. data/test/unit/active_model/serializer/root_test.rb +0 -117
  127. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  128. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -36
  129. data/test/unit/active_model/serilizable_test.rb +0 -50
@@ -0,0 +1,40 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Fieldset
4
+
5
+ def initialize(fields, root = nil)
6
+ @root = root
7
+ @raw_fields = fields
8
+ end
9
+
10
+ def fields
11
+ @fields ||= parsed_fields
12
+ end
13
+
14
+ def fields_for(serializer)
15
+ key = serializer.json_key || serializer.class.root_name
16
+ fields[key.to_sym]
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :raw_fields, :root
22
+
23
+ def parsed_fields
24
+ if raw_fields.is_a?(Hash)
25
+ raw_fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
26
+ elsif raw_fields.is_a?(Array)
27
+ if root.nil?
28
+ raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.'
29
+ end
30
+ hash = {}
31
+ hash[root.to_sym] = raw_fields.map(&:to_sym)
32
+ hash
33
+ else
34
+ {}
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ActiveModel
4
2
  class Serializer
5
- VERSION = '0.9.13'
3
+ VERSION = "0.10.0.rc1"
6
4
  end
7
5
  end
@@ -1,341 +1,258 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_model/array_serializer'
4
- require 'active_model/serializable'
5
- require 'active_model/serializer/association'
6
- require 'active_model/serializer/config'
7
-
8
- require 'thread'
9
- require 'concurrent/map'
1
+ require 'thread_safe'
10
2
 
11
3
  module ActiveModel
12
4
  class Serializer
13
- include Serializable
14
-
15
- @mutex = Mutex.new
5
+ extend ActiveSupport::Autoload
6
+ autoload :Configuration
7
+ autoload :ArraySerializer
8
+ autoload :Adapter
9
+ include Configuration
16
10
 
17
11
  class << self
18
- def inherited(base)
19
- base._root = _root
20
- base._attributes = (_attributes || []).dup
21
- base._associations = (_associations || {}).dup
22
- end
23
-
24
- def setup
25
- @mutex.synchronize do
26
- yield CONFIG
27
- end
28
- end
29
-
30
- EMBED_IN_ROOT_OPTIONS = [
31
- :include,
32
- :embed_in_root,
33
- :embed_in_root_key,
34
- :embed_namespace
35
- ].freeze
36
-
37
- def embed(type, options={})
38
- CONFIG.embed = type
39
- if EMBED_IN_ROOT_OPTIONS.any? { |opt| options[opt].present? }
40
- CONFIG.embed_in_root = true
41
- end
42
- if options[:embed_in_root_key].present?
43
- CONFIG.embed_in_root_key = options[:embed_in_root_key]
44
- end
45
- ActiveSupport::Deprecation.warn <<-WARN
46
- ** Notice: embed is deprecated. **
47
- The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
48
- Please use the global .setup method instead:
49
- ActiveModel::Serializer.setup do |config|
50
- config.embed = :#{type}
51
- config.embed_in_root = #{CONFIG.embed_in_root || false}
52
- end
53
- WARN
54
- end
55
-
56
- def format_keys(format)
57
- @key_format = format
58
- end
59
- attr_reader :key_format
60
-
61
- def serializer_for(resource, options = {})
62
- if resource.respond_to?(:serializer_class)
63
- resource.serializer_class
64
- elsif resource.respond_to?(:to_ary)
65
- if defined?(::ArraySerializer)
66
- ::ArraySerializer
67
- else
68
- ArraySerializer
69
- end
70
- else
71
- each_possible_serializer(resource, options) do |klass_name|
72
- serializer = Serializer.serializers_cache.fetch_or_store(klass_name) do
73
- _const_get(klass_name)
74
- end
75
- return serializer unless serializer.nil?
76
- end
77
- nil
78
- end
79
- end
80
-
81
- attr_accessor :_root, :_attributes, :_associations
82
- alias root _root=
83
- alias root= _root=
84
-
85
- def root_name
86
- if name
87
- root_name = name.demodulize.underscore.sub(/_serializer$/, '')
88
- CONFIG.plural_default_root ? root_name.pluralize : root_name
89
- end
90
- end
12
+ attr_accessor :_attributes
13
+ attr_accessor :_attributes_keys
14
+ attr_accessor :_associations
15
+ attr_accessor :_urls
16
+ attr_accessor :_cache
17
+ attr_accessor :_fragmented
18
+ attr_accessor :_cache_key
19
+ attr_accessor :_cache_only
20
+ attr_accessor :_cache_except
21
+ attr_accessor :_cache_options
22
+ end
91
23
 
92
- def attributes(*attrs)
93
- # Use `class_eval` rather than `define_method` for faster access
94
- # and avoid retaining objects in the closure.
95
- # Batch all methods in a single `class_eval` for efficiceny,
96
- # and define all methods on the same line so the eventual backtrace
97
- # properly maps to the `attributes` call.
98
- source = ["# frozen_string_literal: true\n"]
99
- attrs.each do |attr|
100
- striped_attr = strip_attribute attr
101
-
102
- @_attributes << striped_attr
103
-
104
- unless method_defined?(attr)
105
- source <<
106
- "def #{striped_attr}" <<
107
- "object.read_attribute_for_serialization(#{attr.inspect})" <<
108
- "end" <<
109
- "alias_method :#{striped_attr}, :#{striped_attr}" # suppress method redefinition warning
110
- end
111
- end
112
- caller = caller_locations(1, 1).first
113
- class_eval(source.join(";"), caller.path, caller.lineno - 1)
114
- end
24
+ def self.inherited(base)
25
+ base._attributes = []
26
+ base._attributes_keys = {}
27
+ base._associations = {}
28
+ base._urls = []
29
+ end
115
30
 
116
- def has_one(*attrs)
117
- associate(Association::HasOne, *attrs)
118
- end
31
+ def self.attributes(*attrs)
32
+ attrs = attrs.first if attrs.first.class == Array
33
+ @_attributes.concat attrs
119
34
 
120
- def has_many(*attrs)
121
- associate(Association::HasMany, *attrs)
35
+ attrs.each do |attr|
36
+ define_method attr do
37
+ object && object.read_attribute_for_serialization(attr)
38
+ end unless method_defined?(attr) || _fragmented.respond_to?(attr)
122
39
  end
40
+ end
123
41
 
124
- def serializers_cache
125
- @serializers_cache ||= Concurrent::Map.new
126
- end
42
+ def self.attribute(attr, options = {})
43
+ key = options.fetch(:key, attr)
44
+ @_attributes_keys[attr] = {key: key} if key != attr
45
+ @_attributes.concat [key]
46
+ define_method key do
47
+ object.read_attribute_for_serialization(attr)
48
+ end unless method_defined?(key) || _fragmented.respond_to?(attr)
49
+ end
127
50
 
128
- private
51
+ def self.fragmented(serializer)
52
+ @_fragmented = serializer
53
+ end
129
54
 
130
- def strip_attribute(attr)
131
- symbolized = attr.is_a?(Symbol)
55
+ # Enables a serializer to be automatically cached
56
+ def self.cache(options = {})
57
+ @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
58
+ @_cache_key = options.delete(:key)
59
+ @_cache_only = options.delete(:only)
60
+ @_cache_except = options.delete(:except)
61
+ @_cache_options = (options.empty?) ? nil : options
62
+ end
132
63
 
133
- attr = attr.to_s.gsub(/\?\Z/, '')
134
- attr = attr.to_sym if symbolized
135
- attr
136
- end
64
+ # Defines an association in the object should be rendered.
65
+ #
66
+ # The serializer object should implement the association name
67
+ # as a method which should return an array when invoked. If a method
68
+ # with the association name does not exist, the association name is
69
+ # dispatched to the serialized object.
70
+ def self.has_many(*attrs)
71
+ associate(:has_many, attrs)
72
+ end
137
73
 
138
- def each_possible_serializer(resource, options)
139
- yield build_serializer_class(resource, options)
140
- yield build_serializer_class(resource, {})
141
- yield build_serializer_class(resource.class.name.demodulize, {})
142
- end
74
+ # Defines an association in the object that should be rendered.
75
+ #
76
+ # The serializer object should implement the association name
77
+ # as a method which should return an object when invoked. If a method
78
+ # with the association name does not exist, the association name is
79
+ # dispatched to the serialized object.
80
+ def self.belongs_to(*attrs)
81
+ associate(:belongs_to, attrs)
82
+ end
143
83
 
144
- def build_serializer_class(resource, options)
145
- klass_name = +""
146
- klass_name << "#{options[:namespace]}::" if options[:namespace]
147
- klass_name << options[:prefix].to_s.classify if options[:prefix]
148
- klass_name << "#{resource.class.name}Serializer"
149
- end
84
+ # Defines an association in the object should be rendered.
85
+ #
86
+ # The serializer object should implement the association name
87
+ # as a method which should return an object when invoked. If a method
88
+ # with the association name does not exist, the association name is
89
+ # dispatched to the serialized object.
90
+ def self.has_one(*attrs)
91
+ associate(:has_one, attrs)
92
+ end
150
93
 
151
- def associate(klass, *attrs)
152
- options = attrs.extract_options!
94
+ def self.associate(type, attrs) #:nodoc:
95
+ options = attrs.extract_options!
96
+ self._associations = _associations.dup
153
97
 
154
- attrs.each do |attr|
98
+ attrs.each do |attr|
99
+ unless method_defined?(attr)
155
100
  define_method attr do
156
101
  object.send attr
157
- end unless method_defined?(attr)
158
-
159
- @_associations[attr] = klass.new(attr, options)
102
+ end
160
103
  end
104
+
105
+ self._associations[attr] = {type: type, association_options: options}
161
106
  end
162
107
  end
163
108
 
164
- def initialize(object, options={})
165
- @object = object
166
- @scope = options[:scope]
167
- @root = options.fetch(:root, self.class._root)
168
- @polymorphic = options.fetch(:polymorphic, false)
169
- @meta_key = options[:meta_key] || :meta
170
- @meta = options[@meta_key]
171
- @wrap_in_array = options[:_wrap_in_array]
172
- @only = options[:only] ? Array(options[:only]) : nil
173
- @except = options[:except] ? Array(options[:except]) : nil
174
- @key_format = options[:key_format]
175
- @context = options[:context]
176
- @namespace = options[:namespace]
109
+ def self.url(attr)
110
+ @_urls.push attr
177
111
  end
178
- attr_accessor :object, :scope, :root, :meta_key, :meta, :context, :polymorphic
179
- attr_writer :key_format
180
112
 
181
- def json_key
182
- key = if root == true || root.nil?
183
- self.class.root_name
184
- else
185
- root
186
- end
187
-
188
- key_format == :lower_camel && key.present? ? key.camelize(:lower) : key
113
+ def self.urls(*attrs)
114
+ @_urls.concat attrs
189
115
  end
190
116
 
191
- def attributes
192
- filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
193
- hash[name] = send(name)
117
+ def self.serializer_for(resource, options = {})
118
+ if resource.respond_to?(:to_ary)
119
+ config.array_serializer
120
+ else
121
+ options
122
+ .fetch(:association_options, {})
123
+ .fetch(:serializer, get_serializer_for(resource.class))
194
124
  end
195
125
  end
196
126
 
197
- def associations(options={})
198
- associations = self.class._associations
199
- included_associations = filter(associations.keys)
200
- associations.each_with_object({}) do |(name, association), hash|
201
- if included_associations.include? name
202
- if association.embed_ids?
203
- ids = serialize_ids association
204
- if association.embed_namespace?
205
- hash = hash[association.embed_namespace] ||= {}
206
- hash[association.key] = ids
207
- else
208
- hash[association.key] = ids
209
- end
210
- elsif association.embed_objects?
211
- if association.embed_namespace?
212
- hash = hash[association.embed_namespace] ||= {}
213
- end
214
- hash[association.embedded_key] = serialize association, options
215
- end
216
- end
127
+ def self.adapter
128
+ adapter_class = case config.adapter
129
+ when Symbol
130
+ ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
131
+ when Class
132
+ config.adapter
217
133
  end
218
- end
219
-
220
- def filter(keys)
221
- if @only
222
- keys & @only
223
- elsif @except
224
- keys - @except
225
- else
226
- keys
134
+ unless adapter_class
135
+ valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
136
+ raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
227
137
  end
138
+
139
+ adapter_class
228
140
  end
229
141
 
230
- def embedded_in_root_associations
231
- associations = self.class._associations
232
- included_associations = filter(associations.keys)
233
- associations.each_with_object({}) do |(name, association), hash|
234
- if included_associations.include? name
235
- association_serializer = build_serializer(association)
236
- # we must do this always because even if the current association is not
237
- # embedded in root, it might have its own associations that are embedded in root
238
- hash.merge!(association_serializer.embedded_in_root_associations) do |key, oldval, newval|
239
- if oldval.respond_to?(:to_ary)
240
- [oldval, newval].flatten.uniq
241
- else
242
- oldval.merge(newval) { |_, oldval, newval| [oldval, newval].flatten.uniq }
243
- end
244
- end
142
+ def self._root
143
+ @@root ||= false
144
+ end
245
145
 
246
- if association.embed_in_root?
247
- if association.embed_in_root_key?
248
- hash = hash[association.embed_in_root_key] ||= {}
249
- end
250
-
251
- serialized_data = association_serializer.serializable_object
252
- key = association.root_key
253
- if hash.has_key?(key)
254
- hash[key].concat(serialized_data).uniq!
255
- else
256
- hash[key] = serialized_data
257
- end
258
- end
259
- end
260
- end
146
+ def self._root=(root)
147
+ @@root = root
261
148
  end
262
149
 
263
- def build_serializer(association)
264
- object = send(association.name)
265
- association.build_serializer(object, association_options_for_serializer(association))
150
+ def self.root_name
151
+ name.demodulize.underscore.sub(/_serializer$/, '') if name
266
152
  end
267
153
 
268
- def association_options_for_serializer(association)
269
- prefix = association.options[:prefix]
270
- namespace = association.options[:namespace] || @namespace || self.namespace
154
+ attr_accessor :object, :root, :meta, :meta_key, :scope
271
155
 
272
- { scope: scope }.tap do |opts|
273
- opts[:namespace] = namespace if namespace
274
- opts[:prefix] = prefix if prefix
275
- end
276
- end
156
+ def initialize(object, options = {})
157
+ @object = object
158
+ @options = options
159
+ @root = options[:root] || (self.class._root ? self.class.root_name : false)
160
+ @meta = options[:meta]
161
+ @meta_key = options[:meta_key]
162
+ @scope = options[:scope]
277
163
 
278
- def serialize(association,options={})
279
- build_serializer(association).serializable_object(options)
164
+ scope_name = options[:scope_name]
165
+ if scope_name && !respond_to?(scope_name)
166
+ self.class.class_eval do
167
+ define_method scope_name, lambda { scope }
168
+ end
169
+ end
280
170
  end
281
171
 
282
- def serialize_ids(association)
283
- associated_data = send(association.name)
284
- if associated_data.respond_to?(:to_ary)
285
- associated_data.map { |elem| serialize_id(elem, association) }
172
+ def json_key
173
+ if root == true || root.nil?
174
+ self.class.root_name
286
175
  else
287
- serialize_id(associated_data, association) if associated_data
176
+ root
288
177
  end
289
178
  end
290
179
 
291
- def key_format
292
- @key_format || self.class.key_format || CONFIG.key_format
180
+ def id
181
+ object.id if object
293
182
  end
294
183
 
295
- def format_key(key)
296
- if key_format == :lower_camel
297
- key.to_s.camelize(:lower)
298
- else
299
- key
300
- end
184
+ def type
185
+ object.class.to_s.demodulize.underscore.pluralize
301
186
  end
302
187
 
303
- def convert_keys(hash)
304
- Hash[hash.map do |k,v|
305
- key = if k.is_a?(Symbol)
306
- format_key(k).to_sym
188
+ def attributes(options = {})
189
+ attributes =
190
+ if options[:fields]
191
+ self.class._attributes & options[:fields]
307
192
  else
308
- format_key(k)
193
+ self.class._attributes.dup
309
194
  end
310
195
 
311
- [key ,v]
312
- end]
196
+ attributes += options[:required_fields] if options[:required_fields]
197
+
198
+ attributes.each_with_object({}) do |name, hash|
199
+ unless self.class._fragmented
200
+ hash[name] = send(name)
201
+ else
202
+ hash[name] = self.class._fragmented.public_send(name)
203
+ end
204
+ end
313
205
  end
314
206
 
315
- attr_writer :serialization_options
316
- def serialization_options
317
- @serialization_options || {}
207
+ def each_association(&block)
208
+ self.class._associations.dup.each do |name, association_options|
209
+ next unless object
210
+ association_value = send(name)
211
+
212
+ serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
213
+
214
+ if serializer_class
215
+ serializer = serializer_class.new(
216
+ association_value,
217
+ options.merge(serializer_from_options(association_options))
218
+ )
219
+ elsif !association_value.nil? && !association_value.instance_of?(Object)
220
+ association_options[:association_options][:virtual_value] = association_value
221
+ end
222
+
223
+ if block_given?
224
+ block.call(name, serializer, association_options[:association_options])
225
+ end
226
+ end
318
227
  end
319
228
 
320
- def serializable_object(options={})
321
- self.serialization_options = options
322
- return @wrap_in_array ? [] : nil if @object.nil?
323
- hash = attributes
324
- hash.merge! associations(options)
325
- hash = convert_keys(hash) if key_format.present?
326
- hash = { :type => type_name(@object), type_name(@object) => hash } if @polymorphic
327
- @wrap_in_array ? [hash] : hash
229
+ def serializer_from_options(options)
230
+ opts = {}
231
+ serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
232
+ opts[:serializer] = serializer if serializer
233
+ opts
328
234
  end
329
- alias_method :serializable_hash, :serializable_object
330
235
 
331
- def serialize_id(elem, association)
332
- id = elem.read_attribute_for_serialization(association.embed_key)
333
- association.polymorphic? ? { id: id, type: type_name(elem) } : id
236
+ def self.serializers_cache
237
+ @serializers_cache ||= ThreadSafe::Cache.new
334
238
  end
335
239
 
336
- def type_name(elem)
337
- elem.class.to_s.demodulize.underscore.to_sym
240
+ private
241
+
242
+ attr_reader :options
243
+
244
+ def self.get_serializer_for(klass)
245
+ serializers_cache.fetch_or_store(klass) do
246
+ serializer_class_name = "#{klass.name}Serializer"
247
+ serializer_class = serializer_class_name.safe_constantize
248
+
249
+ if serializer_class
250
+ serializer_class
251
+ elsif klass.superclass
252
+ get_serializer_for(klass.superclass)
253
+ end
254
+ end
338
255
  end
339
- end
340
256
 
257
+ end
341
258
  end
@@ -1,30 +1,16 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'active_model'
4
- require 'active_model/serializer'
5
- require 'active_model/serializer_support'
6
2
  require 'active_model/serializer/version'
7
- require 'active_model/serializer/railtie' if defined?(Rails)
3
+ require 'active_model/serializer'
4
+ require 'active_model/serializer/fieldset'
8
5
 
9
6
  begin
10
7
  require 'action_controller'
11
8
  require 'action_controller/serialization'
12
9
 
13
10
  ActiveSupport.on_load(:action_controller) do
14
- if ::ActionController::Serialization.enabled
15
- ActionController::Base.send(:include, ::ActionController::Serialization)
16
-
17
- # action_controller_test_case load hook was added in Rails 5.1
18
- # https://github.com/rails/rails/commit/0510208dd1ff23baa619884c0abcae4d141fae53
19
- if ActiveSupport::VERSION::STRING < '5.1'
20
- require 'action_controller/serialization_test_case'
21
- ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions)
22
- else
23
- ActiveSupport.on_load(:action_controller_test_case) do
24
- require 'action_controller/serialization_test_case'
25
- ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions)
26
- end
27
- end
11
+ include ::ActionController::Serialization
12
+ ActionDispatch::Reloader.to_prepare do
13
+ ActiveModel::Serializer.serializers_cache.clear
28
14
  end
29
15
  end
30
16
  rescue LoadError
@@ -0,0 +1,6 @@
1
+ Description:
2
+ Generates a serializer for the given resource with tests.
3
+
4
+ Example:
5
+ `rails generate serializer Account name created_at`
6
+
@@ -1,14 +1,12 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Generators
5
3
  class SerializerGenerator < NamedBase
6
- source_root File.expand_path('../templates', __FILE__)
7
- check_class_collision suffix: 'Serializer'
4
+ source_root File.expand_path("../templates", __FILE__)
5
+ check_class_collision :suffix => "Serializer"
8
6
 
9
- argument :attributes, type: :array, default: [], banner: 'field:type field:type'
7
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
10
8
 
11
- class_option :parent, type: :string, desc: 'The parent class for the generated serializer'
9
+ class_option :parent, :type => :string, :desc => "The parent class for the generated serializer"
12
10
 
13
11
  def create_serializer_file
14
12
  template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
@@ -27,13 +25,13 @@ module Rails
27
25
  def parent_class_name
28
26
  if options[:parent]
29
27
  options[:parent]
30
- elsif (ns = Rails::Generators.namespace) && ns.const_defined?(:ApplicationSerializer) ||
31
- (Object.const_get(:ApplicationSerializer) rescue nil)
32
- 'ApplicationSerializer'
28
+ elsif defined?(::ApplicationSerializer)
29
+ "ApplicationSerializer"
33
30
  else
34
- 'ActiveModel::Serializer'
31
+ "ActiveModel::Serializer"
35
32
  end
36
33
  end
37
34
  end
38
35
  end
39
36
  end
37
+
@@ -1,8 +1,8 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= class_name %>Serializer < <%= parent_class_name %>
3
3
  attributes <%= attributes_names.map(&:inspect).join(", ") %>
4
+ end
4
5
  <% association_names.each do |attribute| -%>
5
- has_one :<%= attribute %>
6
+ attribute :<%= attribute %>
6
7
  <% end -%>
7
- end
8
8
  <% end -%>