active_model_serializers 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,7 +66,15 @@ module ActiveModel
66
66
  self._embed = :objects
67
67
  class_attribute :_root_embed
68
68
 
69
+ class_attribute :cache
70
+ class_attribute :perform_caching
71
+
69
72
  class << self
73
+ # set perform caching like root
74
+ def cached(value = true)
75
+ self.perform_caching = value
76
+ end
77
+
70
78
  # Define attributes to be used in the serialization.
71
79
  def attributes(*attrs)
72
80
 
@@ -82,7 +90,9 @@ module ActiveModel
82
90
  end
83
91
 
84
92
  def attribute(attr, options={})
85
- self._attributes = _attributes.merge(attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym)
93
+ self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
94
+
95
+ attr = attr.keys[0] if attr.is_a? Hash
86
96
 
87
97
  unless method_defined?(attr)
88
98
  define_method attr do
@@ -91,6 +101,15 @@ module ActiveModel
91
101
  end
92
102
 
93
103
  define_include_method attr
104
+
105
+ # protect inheritance chains and open classes
106
+ # if a serializer inherits from another OR
107
+ # attributes are added later in a classes lifecycle
108
+ # poison the cache
109
+ define_method :_fast_attributes do
110
+ raise NameError
111
+ end
112
+
94
113
  end
95
114
 
96
115
  def associate(klass, attrs) #:nodoc:
@@ -184,8 +203,12 @@ module ActiveModel
184
203
  attrs[key] = column.type
185
204
  else
186
205
  # Computed attribute (method on serializer or model). We cannot
187
- # infer the type, so we put nil.
188
- attrs[key] = nil
206
+ # infer the type, so we put nil, unless specified in the attribute declaration
207
+ if name != key
208
+ attrs[name] = key
209
+ else
210
+ attrs[key] = nil
211
+ end
189
212
  end
190
213
  end
191
214
 
@@ -229,19 +252,64 @@ module ActiveModel
229
252
  self._root = name
230
253
  end
231
254
  alias_method :root=, :root
255
+
256
+ # Used internally to create a new serializer object based on controller
257
+ # settings and options for a given resource. These settings are typically
258
+ # set during the request lifecycle or by the controller class, and should
259
+ # not be manually defined for this method.
260
+ def build_json(controller, resource, options)
261
+ default_options = controller.send(:default_serializer_options) || {}
262
+ options = default_options.merge(options || {})
263
+
264
+ serializer = options.delete(:serializer) ||
265
+ (resource.respond_to?(:active_model_serializer) &&
266
+ resource.active_model_serializer)
267
+
268
+ return serializer unless serializer
269
+
270
+ if resource.respond_to?(:to_ary)
271
+ unless serializer <= ActiveModel::ArraySerializer
272
+ raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " +
273
+ "You may want to use the :each_serializer option instead.")
274
+ end
275
+
276
+ if options[:root] != false && serializer.root != false
277
+ # the serializer for an Array is ActiveModel::ArraySerializer
278
+ options[:root] ||= serializer.root || controller.controller_name
279
+ end
280
+ end
281
+
282
+ options[:scope] = controller.serialization_scope unless options.has_key?(:scope)
283
+ options[:scope_name] = controller._serialization_scope
284
+ options[:url_options] = controller.url_options
285
+
286
+ serializer.new(resource, options)
287
+ end
232
288
  end
233
289
 
234
290
  attr_reader :object, :options
235
291
 
236
292
  def initialize(object, options={})
237
293
  @object, @options = object, options
294
+
295
+ scope_name = @options[:scope_name]
296
+ if scope_name && !respond_to?(scope_name)
297
+ self.class.class_eval do
298
+ define_method scope_name, lambda { scope }
299
+ end
300
+ end
238
301
  end
239
302
 
240
303
  def root_name
241
304
  return false if self._root == false
242
-
305
+
243
306
  class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank?
244
- self._root || class_name
307
+
308
+ if self._root == true
309
+ class_name
310
+ else
311
+ self._root || class_name
312
+ end
245
313
  end
246
314
 
247
315
  def url_options
@@ -256,6 +324,16 @@ module ActiveModel
256
324
  hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
257
325
  end
258
326
 
327
+ def to_json(*args)
328
+ if perform_caching?
329
+ cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do
330
+ super
331
+ end
332
+ else
333
+ super
334
+ end
335
+ end
336
+
259
337
  # Returns a json representation of the serializable
260
338
  # object including the root.
261
339
  def as_json(options={})
@@ -274,13 +352,12 @@ module ActiveModel
274
352
  # Returns a hash representation of the serializable
275
353
  # object without the root.
276
354
  def serializable_hash
277
- return nil if @object.nil?
278
- instrument(:serialize, :serializer => self.class.name) do
279
- @node = attributes
280
- instrument :associations do
281
- include_associations! if _embed
355
+ if perform_caching?
356
+ cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-hash']) do
357
+ _serializable_hash
282
358
  end
283
- @node
359
+ else
360
+ _serializable_hash
284
361
  end
285
362
  end
286
363
 
@@ -291,6 +368,8 @@ module ActiveModel
291
368
  end
292
369
 
293
370
  def include?(name)
371
+ return false if options.key?(:only) && !Array(options[:only]).include?(name)
372
+ return false if options.key?(:except) && Array(options[:except]).include?(name)
294
373
  send INCLUDE_METHODS[name]
295
374
  end
296
375
 
@@ -372,13 +451,19 @@ module ActiveModel
372
451
  # Returns a hash representation of the serializable
373
452
  # object attributes.
374
453
  def attributes
375
- hash = {}
454
+ _fast_attributes
455
+ rescue NameError
456
+ method = "def _fast_attributes\n"
376
457
 
377
- _attributes.each do |name,key|
378
- hash[key] = read_attribute_for_serialization(name) if include?(name)
379
- end
458
+ method << " h = {}\n"
380
459
 
381
- hash
460
+ _attributes.each do |name,key|
461
+ method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n"
462
+ end
463
+ method << " h\nend"
464
+
465
+ self.class.class_eval method
466
+ _fast_attributes
382
467
  end
383
468
 
384
469
  # Returns options[:scope]
@@ -388,6 +473,21 @@ module ActiveModel
388
473
 
389
474
  alias :read_attribute_for_serialization :send
390
475
 
476
+ def _serializable_hash
477
+ return nil if @object.nil?
478
+ @node = attributes
479
+ include_associations! if _embed
480
+ @node
481
+ end
482
+
483
+ def perform_caching?
484
+ perform_caching && cache && respond_to?(:cache_key)
485
+ end
486
+
487
+ def expand_cache_key(*args)
488
+ ActiveSupport::Cache.expand_cache_key(args)
489
+ end
490
+
391
491
  # Use ActiveSupport::Notifications to send events to external systems.
392
492
  # The event name is: name.class_name.serializer
393
493
  def instrument(name, payload = {}, &block)
@@ -395,4 +495,20 @@ module ActiveModel
395
495
  ActiveSupport::Notifications.instrument(event_name, payload, &block)
396
496
  end
397
497
  end
498
+
499
+ # DefaultSerializer
500
+ #
501
+ # Provides a constant interface for all items, particularly
502
+ # for ArraySerializer.
503
+ class DefaultSerializer
504
+ attr_reader :object, :options
505
+
506
+ def initialize(object, options={})
507
+ @object, @options = object, options
508
+ end
509
+
510
+ def serializable_hash
511
+ @object.as_json(@options)
512
+ end
513
+ end
398
514
  end
@@ -17,6 +17,14 @@ module ActiveModel
17
17
  end
18
18
 
19
19
  self.options = class_options
20
+
21
+ # cache the root so we can reuse it without falling back on a per-instance basis
22
+ begin
23
+ self.options[:root] ||= self.new(name, nil).root
24
+ rescue
25
+ # this could fail if it needs a valid source, for example a polymorphic association
26
+ end
27
+
20
28
  end
21
29
  end
22
30
 
@@ -64,11 +72,11 @@ module ActiveModel
64
72
  end
65
73
 
66
74
  def embed_ids?
67
- option(:embed, source_serializer._embed) == :ids
75
+ [:id, :ids].include? option(:embed, source_serializer._embed)
68
76
  end
69
77
 
70
78
  def embed_objects?
71
- option(:embed, source_serializer._embed) == :objects
79
+ [:object, :objects].include? option(:embed, source_serializer._embed)
72
80
  end
73
81
 
74
82
  def embed_in_root?
@@ -124,12 +132,9 @@ module ActiveModel
124
132
  end
125
133
 
126
134
  def serialize_ids
127
- # Use pluck or select_columns if available
128
- # return collection.ids if collection.respond_to?(:ids)
129
- ids_key = "#{key.to_s.singularize}_ids"
130
-
131
- if !option(:include) && !option(:embed_key) && source_serializer.object.respond_to?(ids_key)
132
- source_serializer.object.send(ids_key)
135
+ ids_key = "#{@name.to_s.singularize}_ids".to_sym
136
+ if !option(:embed_key) && !source_serializer.respond_to?(@name.to_s) && source_serializer.object.respond_to?(ids_key)
137
+ source_serializer.object.read_attribute_for_serialization(ids_key)
133
138
  else
134
139
  associated_object.map do |item|
135
140
  item.read_attribute_for_serialization(embed_key)
@@ -203,6 +208,8 @@ module ActiveModel
203
208
  end
204
209
 
205
210
  def serialize_ids
211
+ id_key = "#{@name}_id".to_sym
212
+
206
213
  if polymorphic?
207
214
  if associated_object
208
215
  {
@@ -212,8 +219,8 @@ module ActiveModel
212
219
  else
213
220
  nil
214
221
  end
215
- elsif !option(:embed_key) && source_serializer.object.respond_to?("#{name}_id")
216
- source_serializer.object.send("#{name}_id")
222
+ elsif !option(:embed_key) && !source_serializer.respond_to?(@name.to_s) && source_serializer.object.respond_to?(id_key)
223
+ source_serializer.object.read_attribute_for_serialization(id_key)
217
224
  elsif associated_object
218
225
  associated_object.read_attribute_for_serialization(embed_key)
219
226
  else
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- VERSION = "0.7.0"
3
+ VERSION = "0.8.0"
4
4
  end
5
5
  end
@@ -15,14 +15,24 @@ if defined?(Rails)
15
15
 
16
16
  Rails::Generators.configure!(app.config.generators)
17
17
  Rails::Generators.hidden_namespaces.uniq!
18
- require "generators/resource_override"
18
+ require_relative "generators/resource_override"
19
19
  end
20
20
 
21
21
  initializer "include_routes.active_model_serializer" do |app|
22
22
  ActiveSupport.on_load(:active_model_serializers) do
23
- include app.routes.url_helpers
23
+ include AbstractController::UrlFor
24
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
25
+ include app.routes.mounted_helpers
24
26
  end
25
27
  end
28
+
29
+ initializer "caching.active_model_serializer" do |app|
30
+ ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching
31
+ ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching
32
+
33
+ ActiveModel::Serializer.cache = Rails.cache
34
+ ActiveModel::ArraySerializer.cache = Rails.cache
35
+ end
26
36
  end
27
37
  end
28
38
  end
@@ -54,10 +64,6 @@ module ActiveModel::SerializerSupport
54
64
  alias :read_attribute_for_serialization :send
55
65
  end
56
66
 
57
- ActiveSupport.on_load(:active_record) do
58
- include ActiveModel::SerializerSupport
59
- end
60
-
61
67
  module ActiveModel::ArraySerializerSupport
62
68
  def active_model_serializer
63
69
  ActiveModel::ArraySerializer
@@ -67,8 +73,14 @@ end
67
73
  Array.send(:include, ActiveModel::ArraySerializerSupport)
68
74
  Set.send(:include, ActiveModel::ArraySerializerSupport)
69
75
 
70
- ActiveSupport.on_load(:active_record) do
71
- ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport)
76
+ {
77
+ :active_record => 'ActiveRecord::Relation',
78
+ :mongoid => 'Mongoid::Criteria'
79
+ }.each do |orm, rel_class|
80
+ ActiveSupport.on_load(orm) do
81
+ include ActiveModel::SerializerSupport
82
+ rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport)
83
+ end
72
84
  end
73
85
 
74
86
  begin
@@ -0,0 +1,16 @@
1
+ # We do not recommend that you use AM::S in this way, but if you must, here
2
+ # is a mixin that overrides ActiveRecord::Base#to_json and #as_json.
3
+
4
+ module ActiveRecord
5
+ module SerializerOverride
6
+ def to_json options = {}
7
+ active_model_serializer.new(self).to_json options
8
+ end
9
+
10
+ def as_json options={}
11
+ active_model_serializer.new(self).as_json options
12
+ end
13
+ end
14
+
15
+ Base.send(:include, SerializerOverride)
16
+ end
@@ -12,9 +12,10 @@ module Rails
12
12
  template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
13
13
  end
14
14
 
15
- # hook_for :test_framework
16
-
17
15
  private
16
+ def generate_id_method
17
+ RUBY_VERSION =~ /1\.8/
18
+ end
18
19
 
19
20
  def attributes_names
20
21
  [:id] + attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym }
@@ -4,5 +4,16 @@ class <%= class_name %>Serializer < <%= parent_class_name %>
4
4
  <% association_names.each do |attribute| -%>
5
5
  has_one :<%= attribute %>
6
6
  <% end -%>
7
+ <% if generate_id_method %>
8
+
9
+ # due to the difference between 1.8 and 1.9 with respect to #id and
10
+ # #object_id, we recommend that if you wish to serialize id columns, you
11
+ # do this. Feel free to remove this if you don't feel that it's appropriate.
12
+ #
13
+ # For more: https://github.com/rails-api/active_model_serializers/issues/127
14
+ def id
15
+ object.read_attribute_for_serialization(:id)
16
+ end
17
+ <% end -%>
7
18
  end
8
19
  <% end -%>
@@ -51,5 +51,4 @@ class ArraySerializerTest < ActiveModel::TestCase
51
51
  { :title => "Post2" }
52
52
  ], serializer.as_json)
53
53
  end
54
-
55
54
  end
@@ -379,6 +379,109 @@ class AssociationTest < ActiveModel::TestCase
379
379
 
380
380
  assert_equal 1, serialized_times
381
381
  end
382
+
383
+ def test_include_with_read_association_id_for_serialization_hook
384
+ @post_serializer_class.class_eval do
385
+ has_one :comment, :embed => :ids, :include => true
386
+ end
387
+
388
+ association_name = nil
389
+ @post.class_eval do
390
+ define_method :read_attribute_for_serialization, lambda { |name|
391
+ association_name = name
392
+ send(name)
393
+ }
394
+ define_method :comment_id, lambda {
395
+ @attributes[:comment].id
396
+ }
397
+ end
398
+
399
+ include_bare! :comment
400
+
401
+ assert_equal({
402
+ :comment_id => 1
403
+ }, @hash)
404
+ end
405
+
406
+ def test_include_with_read_association_ids_for_serialization_hook
407
+ @post_serializer_class.class_eval do
408
+ has_many :comments, :embed => :ids, :include => false
409
+ end
410
+
411
+ association_name = nil
412
+ @post.class_eval do
413
+ define_method :read_attribute_for_serialization, lambda { |name|
414
+ association_name = name
415
+ send(name)
416
+ }
417
+ define_method :comment_ids, lambda {
418
+ @attributes[:comments].map(&:id)
419
+ }
420
+ end
421
+
422
+ include_bare! :comments
423
+
424
+ assert_equal({
425
+ :comment_ids => [1]
426
+ }, @hash)
427
+ end
428
+ end
429
+
430
+ class RecursiveTest < AssociationTest
431
+ class BarSerializer < ActiveModel::Serializer; end
432
+
433
+ class FooSerializer < ActiveModel::Serializer
434
+ root :foos
435
+ attributes :id
436
+ has_many :bars, :serializer => BarSerializer, :root => :bars, :embed => :ids, :include => true
437
+ end
438
+
439
+ class BarSerializer < ActiveModel::Serializer
440
+ root :bars
441
+ attributes :id
442
+ has_many :foos, :serializer => FooSerializer, :root => :foos, :embed => :ids, :include => true
443
+ end
444
+
445
+ class Foo < Model
446
+ def active_model_serializer; FooSerializer; end
447
+ end
448
+
449
+ class Bar < Model
450
+ def active_model_serializer; BarSerializer; end
451
+ end
452
+
453
+ def setup
454
+ super
455
+
456
+ foo = Foo.new(:id => 1)
457
+ bar = Bar.new(:id => 2)
458
+
459
+ foo.bars = [ bar ]
460
+ bar.foos = [ foo ]
461
+
462
+ collection = [ foo ]
463
+
464
+ @serializer = collection.active_model_serializer.new(collection, :root => :foos)
465
+ end
466
+
467
+ def test_mutual_relation_result
468
+ assert_equal({
469
+ :foos => [{
470
+ :bar_ids => [ 2 ],
471
+ :id => 1
472
+ }],
473
+ :bars => [{
474
+ :foo_ids => [ 1 ],
475
+ :id => 2
476
+ }]
477
+ }, @serializer.as_json)
478
+ end
479
+
480
+ def test_mutual_relation_does_not_raise_error
481
+ assert_nothing_raised SystemStackError, 'stack level too deep' do
482
+ @serializer.as_json
483
+ end
484
+ end
382
485
  end
383
486
 
384
487
  class InclusionTest < AssociationTest