active_model_serializers 0.8.3 → 0.9.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -5
  3. data/CONTRIBUTING.md +20 -0
  4. data/DESIGN.textile +4 -4
  5. data/{MIT-LICENSE.txt → MIT-LICENSE} +0 -0
  6. data/README.md +187 -96
  7. data/lib/action_controller/serialization.rb +30 -16
  8. data/lib/active_model/array_serializer.rb +36 -82
  9. data/lib/active_model/default_serializer.rb +22 -0
  10. data/lib/active_model/serializable.rb +25 -0
  11. data/lib/active_model/serializer/associations.rb +53 -211
  12. data/lib/active_model/serializer/config.rb +31 -0
  13. data/lib/active_model/serializer/generators/resource_override.rb +13 -0
  14. data/lib/{generators → active_model/serializer/generators}/serializer/USAGE +0 -0
  15. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +14 -0
  16. data/lib/active_model/serializer/generators/serializer/serializer_generator.rb +37 -0
  17. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +93 -0
  18. data/lib/active_model/serializer/generators/serializer/templates/serializer.rb +8 -0
  19. data/lib/active_model/serializer/railtie.rb +10 -0
  20. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  21. data/lib/active_model/serializer.rb +126 -448
  22. data/lib/active_model/serializer_support.rb +5 -0
  23. data/lib/active_model_serializers.rb +7 -86
  24. data/test/coverage_setup.rb +15 -0
  25. data/test/fixtures/active_record.rb +92 -0
  26. data/test/fixtures/poro.rb +64 -0
  27. data/test/integration/action_controller/serialization_test.rb +234 -0
  28. data/test/integration/active_record/active_record_test.rb +77 -0
  29. data/test/integration/generators/resource_generator_test.rb +26 -0
  30. data/test/integration/generators/scaffold_controller_generator_test.rb +67 -0
  31. data/test/integration/generators/serializer_generator_test.rb +41 -0
  32. data/test/test_app.rb +11 -0
  33. data/test/test_helper.rb +8 -19
  34. data/test/tmp/app/serializers/account_serializer.rb +3 -0
  35. data/test/unit/active_model/array_serializer/meta_test.rb +53 -0
  36. data/test/unit/active_model/array_serializer/root_test.rb +102 -0
  37. data/test/unit/active_model/array_serializer/scope_test.rb +24 -0
  38. data/test/unit/active_model/array_serializer/serialization_test.rb +83 -0
  39. data/test/unit/active_model/default_serializer_test.rb +13 -0
  40. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +21 -0
  41. data/test/unit/active_model/serializer/associations_test.rb +19 -0
  42. data/test/unit/active_model/serializer/attributes_test.rb +41 -0
  43. data/test/unit/active_model/serializer/config_test.rb +86 -0
  44. data/test/unit/active_model/serializer/filter_test.rb +49 -0
  45. data/test/unit/active_model/serializer/has_many_test.rb +173 -0
  46. data/test/unit/active_model/serializer/has_one_test.rb +151 -0
  47. data/test/unit/active_model/serializer/meta_test.rb +39 -0
  48. data/test/unit/active_model/serializer/root_test.rb +117 -0
  49. data/test/unit/active_model/serializer/scope_test.rb +49 -0
  50. metadata +80 -65
  51. data/.gitignore +0 -18
  52. data/.travis.yml +0 -28
  53. data/Gemfile +0 -4
  54. data/Gemfile.edge +0 -9
  55. data/Rakefile +0 -18
  56. data/active_model_serializers.gemspec +0 -24
  57. data/bench/perf.rb +0 -43
  58. data/cruft.md +0 -19
  59. data/lib/active_record/serializer_override.rb +0 -16
  60. data/lib/generators/resource_override.rb +0 -13
  61. data/lib/generators/serializer/serializer_generator.rb +0 -42
  62. data/lib/generators/serializer/templates/serializer.rb +0 -19
  63. data/test/array_serializer_test.rb +0 -75
  64. data/test/association_test.rb +0 -592
  65. data/test/caching_test.rb +0 -96
  66. data/test/generators_test.rb +0 -85
  67. data/test/no_serialization_scope_test.rb +0 -34
  68. data/test/serialization_scope_name_test.rb +0 -67
  69. data/test/serialization_test.rb +0 -392
  70. data/test/serializer_support_test.rb +0 -51
  71. data/test/serializer_test.rb +0 -1465
  72. data/test/test_fakes.rb +0 -217
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
1
3
  module ActionController
2
4
  # Action Controller Serialization
3
5
  #
@@ -32,29 +34,41 @@ module ActionController
32
34
  self._serialization_scope = :current_user
33
35
  end
34
36
 
35
- def serialization_scope
36
- send(_serialization_scope) if _serialization_scope &&
37
- respond_to?(_serialization_scope, true)
37
+ module ClassMethods
38
+ def serialization_scope(scope)
39
+ self._serialization_scope = scope
40
+ end
38
41
  end
39
42
 
40
- def default_serializer_options
43
+ def _render_option_json(resource, options)
44
+ serializer = build_json_serializer(resource, options)
45
+
46
+ if serializer
47
+ super(serializer, options)
48
+ else
49
+ super
50
+ end
41
51
  end
42
52
 
43
- [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
44
- define_method renderer_method do |resource, options|
45
- json = ActiveModel::Serializer.build_json(self, resource, options)
53
+ private
46
54
 
47
- if json
48
- super(json, options)
49
- else
50
- super(resource, options)
51
- end
52
- end
55
+ def default_serializer_options
56
+ {}
53
57
  end
54
58
 
55
- module ClassMethods
56
- def serialization_scope(scope)
57
- self._serialization_scope = scope
59
+ def serialization_scope
60
+ _serialization_scope = self.class._serialization_scope
61
+ send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
62
+ end
63
+
64
+ def build_json_serializer(resource, options = {})
65
+ options = default_serializer_options.merge(options)
66
+
67
+ if serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource))
68
+ options[:scope] = serialization_scope unless options.has_key?(:scope)
69
+ options[:resource_name] = controller_name if resource.respond_to?(:to_ary)
70
+
71
+ serializer.new(resource, options)
58
72
  end
59
73
  end
60
74
  end
@@ -1,104 +1,58 @@
1
- require "active_support/core_ext/class/attribute"
2
- require 'active_support/dependencies'
3
- require 'active_support/descendants_tracker'
1
+ require 'active_model/default_serializer'
2
+ require 'active_model/serializable'
3
+ require 'active_model/serializer'
4
4
 
5
5
  module ActiveModel
6
- # Active Model Array Serializer
7
- #
8
- # Serializes an Array, checking if each element implements
9
- # the +active_model_serializer+ method.
10
- #
11
- # To disable serialization of root elements:
12
- #
13
- # ActiveModel::ArraySerializer.root = false
14
- #
15
6
  class ArraySerializer
16
- extend ActiveSupport::DescendantsTracker
17
-
18
- attr_reader :object, :options
19
-
20
- class_attribute :root
21
-
22
- class_attribute :cache
23
- class_attribute :perform_caching
7
+ include Serializable
24
8
 
25
9
  class << self
26
- # set perform caching like root
27
- def cached(value = true)
28
- self.perform_caching = value
29
- end
10
+ attr_accessor :_root
11
+ alias root _root=
12
+ alias root= _root=
30
13
  end
31
14
 
32
15
  def initialize(object, options={})
33
- @object, @options = object, options
34
- end
35
-
36
- def meta_key
37
- @options[:meta_key].try(:to_sym) || :meta
38
- end
39
-
40
- def include_meta(hash)
41
- hash[meta_key] = @options[:meta] if @options.has_key?(:meta)
42
- end
43
-
44
- def as_json(*args)
45
- @options[:hash] = hash = {}
46
- @options[:unique_values] = {}
47
-
48
- if root = @options[:root]
49
- hash.merge!(root => serializable_array)
50
- include_meta hash
51
- hash
16
+ @object = object
17
+ @scope = options[:scope]
18
+ @root = options.fetch(:root, self.class._root)
19
+ @meta_key = options[:meta_key] || :meta
20
+ @meta = options[@meta_key]
21
+ @each_serializer = options[:each_serializer]
22
+ @resource_name = options[:resource_name]
23
+ end
24
+ attr_accessor :object, :scope, :root, :meta_key, :meta
25
+
26
+ def json_key
27
+ if root.nil?
28
+ @resource_name
52
29
  else
53
- serializable_array
30
+ root
54
31
  end
55
32
  end
56
33
 
57
- def to_json(*args)
58
- if perform_caching?
59
- cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do
60
- super
61
- end
62
- else
63
- super
64
- end
34
+ def serializer_for(item)
35
+ serializer_class = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer
36
+ serializer_class.new(item, scope: scope)
65
37
  end
66
38
 
67
- def serializable_array
68
- if perform_caching?
69
- cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-array']) do
70
- _serializable_array
71
- end
72
- else
73
- _serializable_array
39
+ def serializable_object
40
+ @object.map do |item|
41
+ serializer_for(item).serializable_object
74
42
  end
75
43
  end
44
+ alias_method :serializable_array, :serializable_object
76
45
 
77
- private
78
- def _serializable_array
79
- @object.map do |item|
80
- if @options.has_key? :each_serializer
81
- serializer = @options[:each_serializer]
82
- elsif item.respond_to?(:active_model_serializer)
83
- serializer = item.active_model_serializer
84
- end
85
-
86
- serializable = serializer ? serializer.new(item, @options) : DefaultSerializer.new(item, @options.merge(:root => false))
87
-
88
- if serializable.respond_to?(:serializable_hash)
89
- serializable.serializable_hash
90
- else
91
- serializable.as_json
46
+ def embedded_in_root_associations
47
+ @object.each_with_object({}) do |item, hash|
48
+ serializer_for(item).embedded_in_root_associations.each_pair do |type, objects|
49
+ if hash.has_key?(type)
50
+ hash[type].concat(objects).uniq!
51
+ else
52
+ hash[type] = objects
53
+ end
92
54
  end
93
55
  end
94
56
  end
95
-
96
- def expand_cache_key(*args)
97
- ActiveSupport::Cache.expand_cache_key(args)
98
- end
99
-
100
- def perform_caching?
101
- perform_caching && cache && respond_to?(:cache_key)
102
- end
103
57
  end
104
58
  end
@@ -0,0 +1,22 @@
1
+ require 'active_model/serializable'
2
+
3
+ module ActiveModel
4
+ # DefaultSerializer
5
+ #
6
+ # Provides a constant interface for all items
7
+ class DefaultSerializer
8
+ include ActiveModel::Serializable
9
+
10
+ attr_reader :object
11
+
12
+ def initialize(object, options=nil)
13
+ @object = object
14
+ end
15
+
16
+ def as_json(options={})
17
+ @object.as_json
18
+ end
19
+ alias serializable_hash as_json
20
+ alias serializable_object as_json
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveModel
2
+ module Serializable
3
+ def as_json(options={})
4
+ if root = options.fetch(:root, json_key)
5
+ hash = { root => serializable_object }
6
+ hash.merge!(serializable_data)
7
+ hash
8
+ else
9
+ serializable_object
10
+ end
11
+ end
12
+
13
+ def serializable_data
14
+ embedded_in_root_associations.tap do |hash|
15
+ if respond_to?(:meta) && meta
16
+ hash[meta_key] = meta
17
+ end
18
+ end
19
+ end
20
+
21
+ def embedded_in_root_associations
22
+ {}
23
+ end
24
+ end
25
+ end
@@ -1,231 +1,73 @@
1
+ require 'active_model/default_serializer'
2
+ require 'active_model/serializer'
3
+
1
4
  module ActiveModel
2
5
  class Serializer
3
- module Associations #:nodoc:
4
- class Config #:nodoc:
5
- class_attribute :options
6
-
7
- def self.refine(name, class_options)
8
- current_class = self
9
-
10
- Class.new(self) do
11
- singleton_class.class_eval do
12
- define_method(:to_s) do
13
- "(subclass of #{current_class.name})"
14
- end
15
-
16
- alias inspect to_s
17
- end
18
-
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
-
28
- end
29
- end
30
-
31
- self.options = {}
32
-
33
- def initialize(name, source, options={})
34
- @name = name
35
- @source = source
36
- @options = options
37
- end
38
-
39
- def option(key, default=nil)
40
- if @options.key?(key)
41
- @options[key]
42
- elsif self.class.options.key?(key)
43
- self.class.options[key]
44
- else
45
- default
46
- end
47
- end
48
-
49
- def target_serializer
50
- serializer = option(:serializer)
51
- serializer.is_a?(String) ? serializer.constantize : serializer
52
- end
53
-
54
- def source_serializer
55
- @source
56
- end
57
-
58
- def key
59
- option(:key) || @name
60
- end
61
-
62
- def root
63
- option(:root) || @name
64
- end
65
-
66
- def name
67
- option(:name) || @name
68
- end
69
-
70
- def associated_object
71
- option(:value) || source_serializer.send(name)
72
- end
73
-
74
- def embed_ids?
75
- [:id, :ids].include? option(:embed, source_serializer._embed)
76
- end
77
-
78
- def embed_objects?
79
- [:object, :objects].include? option(:embed, source_serializer._embed)
80
- end
81
-
82
- def embed_in_root?
83
- option(:include, source_serializer._root_embed)
84
- end
85
-
86
- def embeddable?
87
- !associated_object.nil?
88
- end
89
-
90
- protected
91
-
92
- def find_serializable(object)
93
- if target_serializer
94
- target_serializer.new(object, source_serializer.options)
95
- elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer)
96
- ams.new(object, source_serializer.options)
97
- else
98
- object
99
- end
100
- end
6
+ class Association
7
+ def initialize(name, options={})
8
+ if options.has_key?(:include)
9
+ ActiveSupport::Deprecation.warn <<-WARN
10
+ ** Notice: include was renamed to embed_in_root. **
11
+ WARN
12
+ end
13
+
14
+ @name = name.to_s
15
+ @options = options
16
+ self.embed = options.fetch(:embed) { CONFIG.embed }
17
+ @embed_in_root = options.fetch(:embed_in_root) { options.fetch(:include) { CONFIG.embed_in_root } }
18
+ @embed_key = options[:embed_key] || :id
19
+ @key = options[:key]
20
+ @embedded_key = options[:root] || name
21
+
22
+ serializer = @options[:serializer]
23
+ @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer
101
24
  end
102
25
 
103
- class HasMany < Config #:nodoc:
104
- def key
105
- if key = option(:key)
106
- key
107
- elsif embed_ids?
108
- "#{@name.to_s.singularize}_ids".to_sym
109
- else
110
- @name
111
- end
112
- end
113
-
114
- def embed_key
115
- if key = option(:embed_key)
116
- key
117
- else
118
- :id
119
- end
120
- end
121
-
122
- def serialize
123
- associated_object.map do |item|
124
- find_serializable(item).serializable_hash
125
- end
126
- end
127
-
128
- def serializables
129
- associated_object.map do |item|
130
- find_serializable(item)
131
- end
132
- end
26
+ attr_reader :name, :embed_ids, :embed_objects
27
+ attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :root_key, :serializer_class, :options
28
+ alias embed_ids? embed_ids
29
+ alias embed_objects? embed_objects
30
+ alias embed_in_root? embed_in_root
133
31
 
134
- def serialize_ids
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)
138
- else
139
- associated_object.map do |item|
140
- item.read_attribute_for_serialization(embed_key)
141
- end
142
- end
143
- end
32
+ def embed=(embed)
33
+ @embed_ids = embed == :id || embed == :ids
34
+ @embed_objects = embed == :object || embed == :objects
144
35
  end
145
36
 
146
- class HasOne < Config #:nodoc:
147
- def embeddable?
148
- if polymorphic? && associated_object.nil?
149
- false
150
- else
151
- true
152
- end
153
- end
37
+ def build_serializer(object, options = {})
38
+ @serializer_class.new(object, options.merge(@options))
39
+ end
154
40
 
155
- def polymorphic?
156
- option :polymorphic
41
+ class HasOne < Association
42
+ def initialize(name, *args)
43
+ super
44
+ @root_key = @embedded_key.to_s.pluralize
45
+ @key ||= "#{name}_id"
157
46
  end
158
47
 
159
- def root
160
- if root = option(:root)
161
- root
162
- elsif polymorphic?
163
- associated_object.class.to_s.pluralize.demodulize.underscore.to_sym
164
- else
165
- @name.to_s.pluralize.to_sym
166
- end
48
+ def build_serializer(object, options = {})
49
+ @serializer_class ||= Serializer.serializer_for(object) || DefaultSerializer
50
+ options[:_wrap_in_array] = embed_in_root?
51
+ super
167
52
  end
53
+ end
168
54
 
169
- def key
170
- if key = option(:key)
171
- key
172
- elsif embed_ids? && !polymorphic?
173
- "#{@name}_id".to_sym
174
- else
175
- @name
176
- end
55
+ class HasMany < Association
56
+ def initialize(name, *args)
57
+ super
58
+ @root_key = @embedded_key
59
+ @key ||= "#{name.to_s.singularize}_ids"
177
60
  end
178
61
 
179
- def embed_key
180
- if key = option(:embed_key)
181
- key
62
+ def build_serializer(object, options = {})
63
+ if @serializer_class && !(@serializer_class <= ArraySerializer)
64
+ @options[:each_serializer] = @serializer_class
65
+ @serializer_class = ArraySerializer
182
66
  else
183
- :id
67
+ @serializer_class ||= ArraySerializer
184
68
  end
185
- end
186
-
187
- def polymorphic_key
188
- associated_object.class.to_s.demodulize.underscore.to_sym
189
- end
190
69
 
191
- def serialize
192
- object = associated_object
193
-
194
- if object && polymorphic?
195
- {
196
- :type => polymorphic_key,
197
- polymorphic_key => find_serializable(object).serializable_hash
198
- }
199
- elsif object
200
- find_serializable(object).serializable_hash
201
- end
202
- end
203
-
204
- def serializables
205
- object = associated_object
206
- value = object && find_serializable(object)
207
- value ? [value] : []
208
- end
209
-
210
- def serialize_ids
211
- id_key = "#{@name}_id".to_sym
212
-
213
- if polymorphic?
214
- if associated_object
215
- {
216
- :type => polymorphic_key,
217
- :id => associated_object.read_attribute_for_serialization(embed_key)
218
- }
219
- else
220
- nil
221
- end
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)
224
- elsif associated_object
225
- associated_object.read_attribute_for_serialization(embed_key)
226
- else
227
- nil
228
- end
70
+ super
229
71
  end
230
72
  end
231
73
  end
@@ -0,0 +1,31 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Config
4
+ def initialize(data = {})
5
+ @data = data
6
+ end
7
+
8
+ def each(&block)
9
+ @data.each(&block)
10
+ end
11
+
12
+ def clear
13
+ @data.clear
14
+ end
15
+
16
+ def method_missing(name, *args)
17
+ name = name.to_s
18
+ return @data[name] if @data.include?(name)
19
+ match = name.match(/\A(.*?)([?=]?)\Z/)
20
+ case match[2]
21
+ when "="
22
+ @data[match[1]] = args.first
23
+ when "?"
24
+ !!@data[match[1]]
25
+ end
26
+ end
27
+ end
28
+
29
+ CONFIG = Config.new('embed' => :objects) # :nodoc:
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/rails/resource/resource_generator'
3
+
4
+ module Rails
5
+ module Generators
6
+ class ResourceGenerator
7
+ def add_serializer
8
+ invoke 'serializer'
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,14 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
3
+
4
+ module Rails
5
+ module Generators
6
+ class ScaffoldControllerGenerator
7
+ if Rails::VERSION::MAJOR >= 4
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ hook_for :serializer, default: true
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module Rails
2
+ module Generators
3
+ class SerializerGenerator < NamedBase
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ check_class_collision suffix: 'Serializer'
6
+
7
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
8
+
9
+ class_option :parent, type: :string, desc: 'The parent class for the generated serializer'
10
+
11
+ def create_serializer_file
12
+ template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
13
+ end
14
+
15
+ private
16
+
17
+ def attributes_names
18
+ [:id] + attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym }
19
+ end
20
+
21
+ def association_names
22
+ attributes.select { |attr| attr.reference? }.map { |a| a.name.to_sym }
23
+ end
24
+
25
+ def parent_class_name
26
+ if options[:parent]
27
+ options[:parent]
28
+ elsif (ns = Rails::Generators.namespace) && ns.const_defined?(:ApplicationSerializer) ||
29
+ defined?(::ApplicationSerializer)
30
+ 'ApplicationSerializer'
31
+ else
32
+ 'ActiveModel::Serializer'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end