active_model_serializers 0.8.4 → 0.9.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -45
- data/CONTRIBUTING.md +20 -0
- data/DESIGN.textile +4 -4
- data/{MIT-LICENSE.txt → MIT-LICENSE} +0 -0
- data/README.md +187 -113
- data/lib/action_controller/serialization.rb +30 -16
- data/lib/active_model/array_serializer.rb +36 -82
- data/lib/active_model/default_serializer.rb +22 -0
- data/lib/active_model/serializable.rb +25 -0
- data/lib/active_model/serializer.rb +126 -447
- data/lib/active_model/serializer/associations.rb +53 -211
- data/lib/active_model/serializer/config.rb +31 -0
- data/lib/active_model/serializer/generators/resource_override.rb +13 -0
- data/lib/{generators → active_model/serializer/generators}/serializer/USAGE +0 -0
- data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +14 -0
- data/lib/active_model/serializer/generators/serializer/serializer_generator.rb +37 -0
- data/lib/active_model/serializer/generators/serializer/templates/controller.rb +93 -0
- data/lib/active_model/serializer/generators/serializer/templates/serializer.rb +8 -0
- data/lib/active_model/serializer/railtie.rb +10 -0
- data/lib/active_model/{serializers → serializer}/version.rb +1 -1
- data/lib/active_model/serializer_support.rb +5 -0
- data/lib/active_model_serializers.rb +7 -86
- data/test/coverage_setup.rb +15 -0
- data/test/fixtures/active_record.rb +92 -0
- data/test/fixtures/poro.rb +64 -0
- data/test/integration/action_controller/serialization_test.rb +234 -0
- data/test/integration/active_record/active_record_test.rb +77 -0
- data/test/integration/generators/resource_generator_test.rb +26 -0
- data/test/integration/generators/scaffold_controller_generator_test.rb +67 -0
- data/test/integration/generators/serializer_generator_test.rb +41 -0
- data/test/test_app.rb +11 -0
- data/test/test_helper.rb +7 -41
- data/test/tmp/app/serializers/account_serializer.rb +3 -0
- data/test/unit/active_model/array_serializer/meta_test.rb +53 -0
- data/test/unit/active_model/array_serializer/root_test.rb +102 -0
- data/test/unit/active_model/array_serializer/scope_test.rb +24 -0
- data/test/unit/active_model/array_serializer/serialization_test.rb +83 -0
- data/test/unit/active_model/default_serializer_test.rb +13 -0
- data/test/unit/active_model/serializer/associations/build_serializer_test.rb +21 -0
- data/test/unit/active_model/serializer/associations_test.rb +19 -0
- data/test/unit/active_model/serializer/attributes_test.rb +41 -0
- data/test/unit/active_model/serializer/config_test.rb +86 -0
- data/test/unit/active_model/serializer/filter_test.rb +49 -0
- data/test/unit/active_model/serializer/has_many_test.rb +173 -0
- data/test/unit/active_model/serializer/has_one_test.rb +151 -0
- data/test/unit/active_model/serializer/meta_test.rb +39 -0
- data/test/unit/active_model/serializer/root_test.rb +117 -0
- data/test/unit/active_model/serializer/scope_test.rb +49 -0
- metadata +78 -74
- data/.gitignore +0 -18
- data/.travis.yml +0 -34
- data/Gemfile +0 -38
- data/Rakefile +0 -22
- data/active_model_serializers.gemspec +0 -24
- data/appveyor.yml +0 -27
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/array_serializer_test.rb +0 -75
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -177
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -396
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1466
- data/test/test_fakes.rb +0 -218
@@ -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
|
-
|
36
|
-
|
37
|
-
|
37
|
+
module ClassMethods
|
38
|
+
def serialization_scope(scope)
|
39
|
+
self._serialization_scope = scope
|
40
|
+
end
|
38
41
|
end
|
39
42
|
|
40
|
-
def
|
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
|
-
|
44
|
-
define_method renderer_method do |resource, options|
|
45
|
-
json = ActiveModel::Serializer.build_json(self, resource, options)
|
53
|
+
private
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
else
|
50
|
-
super(resource, options)
|
51
|
-
end
|
52
|
-
end
|
55
|
+
def default_serializer_options
|
56
|
+
{}
|
53
57
|
end
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
2
|
-
require '
|
3
|
-
require '
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
@options[
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
30
|
+
root
|
54
31
|
end
|
55
32
|
end
|
56
33
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
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
|
68
|
-
|
69
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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,514 +1,193 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require '
|
4
|
-
require '
|
1
|
+
require 'active_model/array_serializer'
|
2
|
+
require 'active_model/serializable'
|
3
|
+
require 'active_model/serializer/associations'
|
4
|
+
require 'active_model/serializer/config'
|
5
|
+
|
6
|
+
require 'thread'
|
5
7
|
|
6
8
|
module ActiveModel
|
7
|
-
# Active Model Serializer
|
8
|
-
#
|
9
|
-
# Provides a basic serializer implementation that allows you to easily
|
10
|
-
# control how a given object is going to be serialized. On initialization,
|
11
|
-
# it expects two objects as arguments, a resource and options. For example,
|
12
|
-
# one may do in a controller:
|
13
|
-
#
|
14
|
-
# PostSerializer.new(@post, :scope => current_user).to_json
|
15
|
-
#
|
16
|
-
# The object to be serialized is the +@post+ and the current user is passed
|
17
|
-
# in for authorization purposes.
|
18
|
-
#
|
19
|
-
# We use the scope to check if a given attribute should be serialized or not.
|
20
|
-
# For example, some attributes may only be returned if +current_user+ is the
|
21
|
-
# author of the post:
|
22
|
-
#
|
23
|
-
# class PostSerializer < ActiveModel::Serializer
|
24
|
-
# attributes :title, :body
|
25
|
-
# has_many :comments
|
26
|
-
#
|
27
|
-
# private
|
28
|
-
#
|
29
|
-
# def attributes
|
30
|
-
# hash = super
|
31
|
-
# hash.merge!(:email => post.email) if author?
|
32
|
-
# hash
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# def author?
|
36
|
-
# post.author == scope
|
37
|
-
# end
|
38
|
-
# end
|
39
|
-
#
|
40
9
|
class Serializer
|
41
|
-
|
42
|
-
|
43
|
-
INCLUDE_METHODS = {}
|
44
|
-
INSTRUMENT = { :serialize => :"serialize.serializer", :associations => :"associations.serializer" }
|
45
|
-
|
46
|
-
class IncludeError < StandardError
|
47
|
-
attr_reader :source, :association
|
48
|
-
|
49
|
-
def initialize(source, association)
|
50
|
-
@source, @association = source, association
|
51
|
-
end
|
52
|
-
|
53
|
-
def to_s
|
54
|
-
"Cannot serialize #{association} when #{source} does not have a root!"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
class_attribute :_attributes
|
59
|
-
self._attributes = {}
|
10
|
+
include Serializable
|
60
11
|
|
61
|
-
|
62
|
-
self._associations = {}
|
63
|
-
|
64
|
-
class_attribute :_root
|
65
|
-
class_attribute :_embed
|
66
|
-
self._embed = :objects
|
67
|
-
class_attribute :_root_embed
|
68
|
-
|
69
|
-
class_attribute :cache
|
70
|
-
class_attribute :perform_caching
|
12
|
+
@mutex = Mutex.new
|
71
13
|
|
72
14
|
class << self
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# Define attributes to be used in the serialization.
|
79
|
-
def attributes(*attrs)
|
80
|
-
|
81
|
-
self._attributes = _attributes.dup
|
82
|
-
|
83
|
-
attrs.each do |attr|
|
84
|
-
if Hash === attr
|
85
|
-
attr.each {|attr_real, key| attribute attr_real, :key => key }
|
86
|
-
else
|
87
|
-
attribute attr
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def attribute(attr, options={})
|
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
|
96
|
-
|
97
|
-
unless method_defined?(attr)
|
98
|
-
define_method attr do
|
99
|
-
object.read_attribute_for_serialization(attr.to_sym)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
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
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def associate(klass, attrs) #:nodoc:
|
116
|
-
options = attrs.extract_options!
|
117
|
-
self._associations = _associations.dup
|
118
|
-
|
119
|
-
attrs.each do |attr|
|
120
|
-
unless method_defined?(attr)
|
121
|
-
define_method attr do
|
122
|
-
object.send attr
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
define_include_method attr
|
127
|
-
|
128
|
-
self._associations[attr] = klass.refine(attr, options)
|
129
|
-
end
|
15
|
+
def inherited(base)
|
16
|
+
base._root = _root
|
17
|
+
base._attributes = (_attributes || []).dup
|
18
|
+
base._associations = (_associations || {}).dup
|
130
19
|
end
|
131
20
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
INCLUDE_METHODS[name] = method
|
136
|
-
|
137
|
-
unless method_defined?(method)
|
138
|
-
define_method method do
|
139
|
-
true
|
140
|
-
end
|
21
|
+
def setup
|
22
|
+
@mutex.synchronize do
|
23
|
+
yield CONFIG
|
141
24
|
end
|
142
25
|
end
|
143
26
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
# The serializer object should implement the association name
|
157
|
-
# as a method which should return an object when invoked. If a method
|
158
|
-
# with the association name does not exist, the association name is
|
159
|
-
# dispatched to the serialized object.
|
160
|
-
def has_one(*attrs)
|
161
|
-
associate(Associations::HasOne, attrs)
|
27
|
+
def embed(type, options={})
|
28
|
+
CONFIG.embed = type
|
29
|
+
CONFIG.embed_in_root = true if options[:embed_in_root] || options[:include]
|
30
|
+
ActiveSupport::Deprecation.warn <<-WARN
|
31
|
+
** Notice: embed is deprecated. **
|
32
|
+
The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
|
33
|
+
Please use the global .setup method instead:
|
34
|
+
ActiveModel::Serializer.setup do |config|
|
35
|
+
config.embed = :#{type}
|
36
|
+
config.embed_in_root = #{CONFIG.embed_in_root || false}
|
37
|
+
end
|
38
|
+
WARN
|
162
39
|
end
|
163
40
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
#
|
169
|
-
# The +attributes+ hash looks like this:
|
170
|
-
#
|
171
|
-
# { :name => :string, :age => :integer }
|
172
|
-
#
|
173
|
-
# The +associations+ hash looks like this:
|
174
|
-
# { :posts => { :has_many => :posts } }
|
175
|
-
#
|
176
|
-
# If :key is used:
|
177
|
-
#
|
178
|
-
# class PostsSerializer < ActiveModel::Serializer
|
179
|
-
# has_many :posts, :key => :my_posts
|
180
|
-
# end
|
181
|
-
#
|
182
|
-
# the hash looks like this:
|
183
|
-
#
|
184
|
-
# { :my_posts => { :has_many => :posts }
|
185
|
-
#
|
186
|
-
# This information is extracted from the serializer's model class,
|
187
|
-
# which is provided by +SerializerClass.model_class+.
|
188
|
-
#
|
189
|
-
# The schema method uses the +columns_hash+ and +reflect_on_association+
|
190
|
-
# methods, provided by default by ActiveRecord. You can implement these
|
191
|
-
# methods on your custom models if you want the serializer's schema method
|
192
|
-
# to work.
|
193
|
-
#
|
194
|
-
# TODO: This is currently coupled to Active Record. We need to
|
195
|
-
# figure out a way to decouple those two.
|
196
|
-
def schema
|
197
|
-
klass = model_class
|
198
|
-
columns = klass.columns_hash
|
199
|
-
|
200
|
-
attrs = {}
|
201
|
-
_attributes.each do |name, key|
|
202
|
-
if column = columns[name.to_s]
|
203
|
-
attrs[key] = column.type
|
41
|
+
if RUBY_VERSION >= '2.0'
|
42
|
+
def serializer_for(resource)
|
43
|
+
if resource.respond_to?(:to_ary)
|
44
|
+
ArraySerializer
|
204
45
|
else
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
else
|
210
|
-
attrs[key] = nil
|
46
|
+
begin
|
47
|
+
Object.const_get "#{resource.class.name}Serializer"
|
48
|
+
rescue NameError
|
49
|
+
nil
|
211
50
|
end
|
212
51
|
end
|
213
52
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
if model_association = klass.reflect_on_association(association.name)
|
220
|
-
# Real association.
|
221
|
-
associations[association.key] = { model_association.macro => model_association.name }
|
53
|
+
else
|
54
|
+
def serializer_for(resource)
|
55
|
+
if resource.respond_to?(:to_ary)
|
56
|
+
ArraySerializer
|
222
57
|
else
|
223
|
-
#
|
224
|
-
# the association class, but that would make it different from
|
225
|
-
# real associations, which read has_one vs. belongs_to from the
|
226
|
-
# model.
|
227
|
-
associations[association.key] = nil
|
58
|
+
"#{resource.class.name}Serializer".safe_constantize
|
228
59
|
end
|
229
60
|
end
|
230
|
-
|
231
|
-
{ :attributes => attrs, :associations => associations }
|
232
|
-
end
|
233
|
-
|
234
|
-
# The model class associated with this serializer.
|
235
|
-
def model_class
|
236
|
-
name.sub(/Serializer$/, '').constantize
|
237
61
|
end
|
238
62
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
# embed :ids # Embed only the association ids
|
243
|
-
# embed :ids, :include => true # Embed the association ids and include objects in the root
|
244
|
-
#
|
245
|
-
def embed(type, options={})
|
246
|
-
self._embed = type
|
247
|
-
self._root_embed = true if options[:include]
|
248
|
-
end
|
63
|
+
attr_accessor :_root, :_attributes, :_associations
|
64
|
+
alias root _root=
|
65
|
+
alias root= _root=
|
249
66
|
|
250
|
-
|
251
|
-
|
252
|
-
self._root = name
|
67
|
+
def root_name
|
68
|
+
name.demodulize.underscore.sub(/_serializer$/, '') if name
|
253
69
|
end
|
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
70
|
|
268
|
-
|
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
|
71
|
+
def attributes(*attrs)
|
72
|
+
@_attributes.concat attrs
|
275
73
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
end
|
74
|
+
attrs.each do |attr|
|
75
|
+
define_method attr do
|
76
|
+
object.read_attribute_for_serialization attr
|
77
|
+
end unless method_defined?(attr)
|
280
78
|
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
79
|
end
|
288
|
-
end
|
289
80
|
|
290
|
-
|
291
|
-
|
292
|
-
def initialize(object, options={})
|
293
|
-
@object, @options = object, options
|
294
|
-
|
295
|
-
scope_name = @options[:scope_name]
|
296
|
-
if scope_name && !respond_to?(scope_name)
|
297
|
-
self.singleton_class.send :alias_method, scope_name, :scope
|
81
|
+
def has_one(*attrs)
|
82
|
+
associate(Association::HasOne, *attrs)
|
298
83
|
end
|
299
|
-
end
|
300
|
-
|
301
|
-
def root_name
|
302
|
-
return false if self._root == false
|
303
84
|
|
304
|
-
|
305
|
-
|
306
|
-
if self._root == true
|
307
|
-
class_name
|
308
|
-
else
|
309
|
-
self._root || class_name
|
85
|
+
def has_many(*attrs)
|
86
|
+
associate(Association::HasMany, *attrs)
|
310
87
|
end
|
311
|
-
end
|
312
88
|
|
313
|
-
|
314
|
-
@options[:url_options] || {}
|
315
|
-
end
|
89
|
+
private
|
316
90
|
|
317
|
-
|
318
|
-
|
319
|
-
end
|
91
|
+
def associate(klass, *attrs)
|
92
|
+
options = attrs.extract_options!
|
320
93
|
|
321
|
-
|
322
|
-
|
323
|
-
|
94
|
+
attrs.each do |attr|
|
95
|
+
define_method attr do
|
96
|
+
object.send attr
|
97
|
+
end unless method_defined?(attr)
|
324
98
|
|
325
|
-
|
326
|
-
if perform_caching?
|
327
|
-
cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do
|
328
|
-
super
|
99
|
+
@_associations[attr] = klass.new(attr, options)
|
329
100
|
end
|
330
|
-
else
|
331
|
-
super
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
# Returns a json representation of the serializable
|
336
|
-
# object including the root.
|
337
|
-
def as_json(options={})
|
338
|
-
options ||= {}
|
339
|
-
if root = options.fetch(:root, @options.fetch(:root, root_name))
|
340
|
-
@options[:hash] = hash = {}
|
341
|
-
@options[:unique_values] = {}
|
342
|
-
|
343
|
-
hash.merge!(root => serializable_hash)
|
344
|
-
include_meta hash
|
345
|
-
hash
|
346
|
-
else
|
347
|
-
serializable_hash
|
348
101
|
end
|
349
102
|
end
|
350
103
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
@
|
355
|
-
|
356
|
-
|
357
|
-
|
104
|
+
def initialize(object, options={})
|
105
|
+
@object = object
|
106
|
+
@scope = options[:scope]
|
107
|
+
@root = options.fetch(:root, self.class._root)
|
108
|
+
@meta_key = options[:meta_key] || :meta
|
109
|
+
@meta = options[@meta_key]
|
110
|
+
@wrap_in_array = options[:_wrap_in_array]
|
111
|
+
end
|
112
|
+
attr_accessor :object, :scope, :root, :meta_key, :meta
|
113
|
+
|
114
|
+
def json_key
|
115
|
+
if root == true || root.nil?
|
116
|
+
self.class.root_name
|
358
117
|
else
|
359
|
-
|
118
|
+
root
|
360
119
|
end
|
361
|
-
|
362
|
-
include_associations! if @object.present? && _embed
|
363
|
-
@node
|
364
120
|
end
|
365
121
|
|
366
|
-
def
|
367
|
-
|
368
|
-
|
122
|
+
def attributes
|
123
|
+
filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
|
124
|
+
hash[name] = send(name)
|
369
125
|
end
|
370
126
|
end
|
371
127
|
|
372
|
-
def
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
# passed in is the same as the current options hash, use the current
|
382
|
-
# unique values.
|
383
|
-
#
|
384
|
-
# TODO: Should passing in a Hash even be public API here?
|
385
|
-
unique_values =
|
386
|
-
if hash = options[:hash]
|
387
|
-
if @options[:hash] == hash
|
388
|
-
@options[:unique_values] ||= {}
|
389
|
-
else
|
390
|
-
{}
|
128
|
+
def associations
|
129
|
+
associations = self.class._associations
|
130
|
+
included_associations = filter(associations.keys)
|
131
|
+
associations.each_with_object({}) do |(name, association), hash|
|
132
|
+
if included_associations.include? name
|
133
|
+
if association.embed_ids?
|
134
|
+
hash[association.key] = serialize_ids association
|
135
|
+
elsif association.embed_objects?
|
136
|
+
hash[association.embedded_key] = serialize association
|
391
137
|
end
|
392
|
-
else
|
393
|
-
hash = @options[:hash]
|
394
|
-
@options[:unique_values] ||= {}
|
395
|
-
end
|
396
|
-
|
397
|
-
node = options[:node] ||= @node
|
398
|
-
value = options[:value]
|
399
|
-
|
400
|
-
if options[:include] == nil
|
401
|
-
if @options.key?(:include)
|
402
|
-
options[:include] = @options[:include].include?(name)
|
403
|
-
elsif @options.include?(:exclude)
|
404
|
-
options[:include] = !@options[:exclude].include?(name)
|
405
138
|
end
|
406
139
|
end
|
407
|
-
|
408
|
-
association_class =
|
409
|
-
if klass = _associations[name]
|
410
|
-
klass
|
411
|
-
elsif value.respond_to?(:to_ary)
|
412
|
-
Associations::HasMany
|
413
|
-
else
|
414
|
-
Associations::HasOne
|
415
|
-
end
|
416
|
-
|
417
|
-
association = association_class.new(name, self, options)
|
418
|
-
|
419
|
-
if association.embed_ids?
|
420
|
-
node[association.key] = association.serialize_ids
|
421
|
-
|
422
|
-
if association.embed_in_root? && hash.nil?
|
423
|
-
raise IncludeError.new(self.class, association.name)
|
424
|
-
elsif association.embed_in_root? && association.embeddable?
|
425
|
-
merge_association hash, association.root, association.serializables, unique_values
|
426
|
-
end
|
427
|
-
elsif association.embed_objects?
|
428
|
-
node[association.key] = association.serialize
|
429
|
-
end
|
430
140
|
end
|
431
141
|
|
432
|
-
|
433
|
-
|
434
|
-
# which has_many tags, the top-level :tags key will contain the merged list
|
435
|
-
# of all tags for all comments of the post.
|
436
|
-
#
|
437
|
-
# In order to make this efficient, we store a :unique_values hash containing
|
438
|
-
# a unique list of all of the objects that are already in the Array. This
|
439
|
-
# avoids the need to scan through the Array looking for entries every time
|
440
|
-
# we want to merge a new list of values.
|
441
|
-
def merge_association(hash, key, serializables, unique_values)
|
442
|
-
already_serialized = (unique_values[key] ||= {})
|
443
|
-
serializable_hashes = (hash[key] ||= [])
|
444
|
-
|
445
|
-
serializables.each do |serializable|
|
446
|
-
unless already_serialized.include? serializable.object
|
447
|
-
already_serialized[serializable.object] = true
|
448
|
-
serializable_hashes << serializable.serializable_hash
|
449
|
-
end
|
450
|
-
end
|
142
|
+
def filter(keys)
|
143
|
+
keys
|
451
144
|
end
|
452
145
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
146
|
+
def embedded_in_root_associations
|
147
|
+
associations = self.class._associations
|
148
|
+
included_associations = filter(associations.keys)
|
149
|
+
associations.each_with_object({}) do |(name, association), hash|
|
150
|
+
if included_associations.include? name
|
151
|
+
if association.embed_in_root?
|
152
|
+
association_serializer = build_serializer(association)
|
153
|
+
hash.merge! association_serializer.embedded_in_root_associations
|
461
154
|
|
462
|
-
|
463
|
-
|
155
|
+
serialized_data = association_serializer.serializable_object
|
156
|
+
key = association.root_key
|
157
|
+
if hash.has_key?(key)
|
158
|
+
hash[key].concat(serialized_data).uniq!
|
159
|
+
else
|
160
|
+
hash[key] = serialized_data
|
161
|
+
end
|
162
|
+
end
|
464
163
|
end
|
465
|
-
|
466
|
-
|
467
|
-
self.class.class_eval method
|
468
|
-
_fast_attributes
|
469
|
-
end
|
470
|
-
|
471
|
-
# Returns options[:scope]
|
472
|
-
def scope
|
473
|
-
@options[:scope]
|
474
|
-
end
|
475
|
-
|
476
|
-
alias :read_attribute_for_serialization :send
|
477
|
-
|
478
|
-
def _serializable_hash
|
479
|
-
return nil if @object.nil?
|
480
|
-
attributes
|
481
|
-
end
|
482
|
-
|
483
|
-
def perform_caching?
|
484
|
-
perform_caching && cache && respond_to?(:cache_key)
|
164
|
+
end
|
485
165
|
end
|
486
166
|
|
487
|
-
def
|
488
|
-
|
167
|
+
def build_serializer(association)
|
168
|
+
object = send(association.name)
|
169
|
+
association.build_serializer(object, scope: scope)
|
489
170
|
end
|
490
171
|
|
491
|
-
|
492
|
-
|
493
|
-
def instrument(name, payload = {}, &block)
|
494
|
-
event_name = INSTRUMENT[name]
|
495
|
-
ActiveSupport::Notifications.instrument(event_name, payload, &block)
|
172
|
+
def serialize(association)
|
173
|
+
build_serializer(association).serializable_object
|
496
174
|
end
|
497
|
-
end
|
498
175
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
def initialize(object, options={})
|
507
|
-
@object, @options = object, options
|
176
|
+
def serialize_ids(association)
|
177
|
+
associated_data = send(association.name)
|
178
|
+
if associated_data.respond_to?(:to_ary)
|
179
|
+
associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
|
180
|
+
else
|
181
|
+
associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
|
182
|
+
end
|
508
183
|
end
|
509
184
|
|
510
|
-
def
|
511
|
-
|
185
|
+
def serializable_object(options={})
|
186
|
+
return nil if object.nil?
|
187
|
+
hash = attributes
|
188
|
+
hash.merge! associations
|
189
|
+
@wrap_in_array ? [hash] : hash
|
512
190
|
end
|
191
|
+
alias_method :serializable_hash, :serializable_object
|
513
192
|
end
|
514
193
|
end
|