active_model_serializers_rails_2.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.
- data/CHANGELOG.md +87 -0
- data/CONTRIBUTING.md +20 -0
- data/DESIGN.textile +586 -0
- data/MIT-LICENSE +21 -0
- data/README.md +793 -0
- data/lib/active_model/array_serializer.rb +60 -0
- data/lib/active_model/default_serializer.rb +27 -0
- data/lib/active_model/serializable.rb +25 -0
- data/lib/active_model/serializer.rb +220 -0
- data/lib/active_model/serializer/associations.rb +98 -0
- data/lib/active_model/serializer/config.rb +31 -0
- data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +14 -0
- data/lib/active_model/serializer/generators/serializer/templates/controller.rb +93 -0
- data/lib/active_model/serializer/railtie.rb +10 -0
- data/lib/active_model/serializer/version.rb +5 -0
- data/lib/active_model/serializer_support.rb +5 -0
- data/lib/active_model_serializers.rb +33 -0
- 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/active_record/active_record_test.rb +77 -0
- data/test/test_app.rb +11 -0
- data/test/test_helper.rb +13 -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 +182 -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 +174 -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 +127 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'active_model/default_serializer'
|
2
|
+
require 'active_model/serializable'
|
3
|
+
require 'active_model/serializer'
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
class ArraySerializer
|
7
|
+
include Serializable
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :_root
|
11
|
+
alias root _root=
|
12
|
+
alias root= _root=
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(object, options={})
|
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
|
29
|
+
else
|
30
|
+
root
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def serializer_for(item)
|
35
|
+
serializer_class = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer
|
36
|
+
serializer_class.new(item, scope: scope)
|
37
|
+
end
|
38
|
+
|
39
|
+
def serializable_object
|
40
|
+
@object.map do |item|
|
41
|
+
serializer_for(item).serializable_object
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias_method :serializable_array, :serializable_object
|
45
|
+
|
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
|
+
next if !objects || objects.flatten.empty?
|
50
|
+
|
51
|
+
if hash.has_key?(type)
|
52
|
+
hash[type].concat(objects).uniq!
|
53
|
+
else
|
54
|
+
hash[type] = objects
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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
|
+
return nil if @object.nil?
|
18
|
+
if @object.is_a?(Struct)
|
19
|
+
Hash[@object.members.zip(@object.values)]
|
20
|
+
else
|
21
|
+
@object.as_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias serializable_hash as_json
|
25
|
+
alias serializable_object as_json
|
26
|
+
end
|
27
|
+
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
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_model/array_serializer'
|
3
|
+
require 'active_model/serializable'
|
4
|
+
require 'active_model/serializer/associations'
|
5
|
+
require 'active_model/serializer/config'
|
6
|
+
|
7
|
+
require 'thread'
|
8
|
+
|
9
|
+
module ActiveRecord
|
10
|
+
class Base
|
11
|
+
alias read_attribute_for_serialization send
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ActiveModel
|
16
|
+
class Serializer
|
17
|
+
include Serializable
|
18
|
+
extend ActiveSupport::Inflector
|
19
|
+
|
20
|
+
@mutex = Mutex.new
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def inherited(base)
|
24
|
+
base._root = _root
|
25
|
+
base._attributes = (_attributes || []).dup
|
26
|
+
base._associations = (_associations || {}).dup
|
27
|
+
end
|
28
|
+
def const_regexp(camel_cased_word) #:nodoc:
|
29
|
+
parts = camel_cased_word.split("::")
|
30
|
+
last = parts.pop
|
31
|
+
|
32
|
+
parts.reverse.inject(last) do |acc, part|
|
33
|
+
part.empty? ? acc : "#{part}(::#{acc})?"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
def safe_constantize(camel_cased_word)
|
37
|
+
begin
|
38
|
+
constantize(camel_cased_word)
|
39
|
+
rescue NameError => e
|
40
|
+
raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
|
41
|
+
e.name.to_s == camel_cased_word.to_s
|
42
|
+
rescue ArgumentError => e
|
43
|
+
raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup
|
48
|
+
@mutex.synchronize do
|
49
|
+
yield CONFIG
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def embed(type, options={})
|
54
|
+
CONFIG.embed = type
|
55
|
+
CONFIG.embed_in_root = true if options[:embed_in_root] || options[:include]
|
56
|
+
ActiveSupport::Deprecation.warn <<-WARN
|
57
|
+
** Notice: embed is deprecated. **
|
58
|
+
The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
|
59
|
+
Please use the global .setup method instead:
|
60
|
+
ActiveModel::Serializer.setup do |config|
|
61
|
+
config.embed = :#{type}
|
62
|
+
config.embed_in_root = #{CONFIG.embed_in_root || false}
|
63
|
+
end
|
64
|
+
WARN
|
65
|
+
end
|
66
|
+
|
67
|
+
if RUBY_VERSION >= '2.0'
|
68
|
+
def serializer_for(resource)
|
69
|
+
if resource.respond_to?(:to_ary)
|
70
|
+
ArraySerializer
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
Object.const_get "#{resource.class.name}Serializer"
|
74
|
+
rescue NameError
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
else
|
80
|
+
def serializer_for(resource)
|
81
|
+
if resource.respond_to?(:to_ary)
|
82
|
+
ArraySerializer
|
83
|
+
else
|
84
|
+
safe_constantize "#{resource.class.name}Serializer"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_accessor :_root, :_attributes, :_associations
|
90
|
+
alias root _root=
|
91
|
+
alias root= _root=
|
92
|
+
|
93
|
+
def root_name
|
94
|
+
name.demodulize.underscore.sub(/_serializer$/, '') if name
|
95
|
+
end
|
96
|
+
|
97
|
+
def attributes(*attrs)
|
98
|
+
@_attributes.concat attrs
|
99
|
+
|
100
|
+
attrs.each do |attr|
|
101
|
+
define_method attr do
|
102
|
+
object.read_attribute_for_serialization attr
|
103
|
+
end unless method_defined?(attr)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def has_one(*attrs)
|
108
|
+
associate(Association::HasOne, *attrs)
|
109
|
+
end
|
110
|
+
|
111
|
+
def has_many(*attrs)
|
112
|
+
associate(Association::HasMany, *attrs)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def associate(klass, *attrs)
|
118
|
+
options = attrs.extract_options!
|
119
|
+
|
120
|
+
attrs.each do |attr|
|
121
|
+
define_method attr do
|
122
|
+
object.send attr
|
123
|
+
end unless method_defined?(attr)
|
124
|
+
|
125
|
+
@_associations[attr] = klass.new(attr, options)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize(object, options={})
|
131
|
+
@object = object
|
132
|
+
@scope = options[:scope]
|
133
|
+
@root = options.fetch(:root, self.class._root)
|
134
|
+
@meta_key = options[:meta_key] || :meta
|
135
|
+
@meta = options[@meta_key]
|
136
|
+
@wrap_in_array = options[:_wrap_in_array]
|
137
|
+
end
|
138
|
+
attr_accessor :object, :scope, :root, :meta_key, :meta
|
139
|
+
|
140
|
+
def json_key
|
141
|
+
if root == true || root.nil?
|
142
|
+
self.class.root_name
|
143
|
+
else
|
144
|
+
root
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def attributes
|
149
|
+
filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
|
150
|
+
hash[name] = send(name)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def associations
|
155
|
+
associations = self.class._associations
|
156
|
+
included_associations = filter(associations.keys)
|
157
|
+
associations.each_with_object({}) do |(name, association), hash|
|
158
|
+
if included_associations.include? name
|
159
|
+
if association.embed_ids?
|
160
|
+
hash[association.key] = serialize_ids association
|
161
|
+
elsif association.embed_objects?
|
162
|
+
hash[association.embedded_key] = serialize association
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def filter(keys)
|
169
|
+
keys
|
170
|
+
end
|
171
|
+
|
172
|
+
def embedded_in_root_associations
|
173
|
+
associations = self.class._associations
|
174
|
+
included_associations = filter(associations.keys)
|
175
|
+
associations.each_with_object({}) do |(name, association), hash|
|
176
|
+
if included_associations.include? name
|
177
|
+
if association.embed_in_root?
|
178
|
+
association_serializer = build_serializer(association)
|
179
|
+
hash.merge! association_serializer.embedded_in_root_associations
|
180
|
+
|
181
|
+
serialized_data = association_serializer.serializable_object
|
182
|
+
key = association.root_key
|
183
|
+
if hash.has_key?(key)
|
184
|
+
hash[key].concat(serialized_data).uniq!
|
185
|
+
else
|
186
|
+
hash[key] = serialized_data
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def build_serializer(association)
|
194
|
+
object = send(association.name)
|
195
|
+
association.build_serializer(object, scope: scope)
|
196
|
+
end
|
197
|
+
|
198
|
+
def serialize(association)
|
199
|
+
build_serializer(association).serializable_object
|
200
|
+
end
|
201
|
+
|
202
|
+
def serialize_ids(association)
|
203
|
+
associated_data = send(association.name)
|
204
|
+
if associated_data.respond_to?(:to_ary)
|
205
|
+
associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
|
206
|
+
else
|
207
|
+
associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def serializable_object(options={})
|
212
|
+
return nil if object.nil?
|
213
|
+
hash = attributes
|
214
|
+
hash.merge! associations
|
215
|
+
@wrap_in_array ? [hash] : hash
|
216
|
+
end
|
217
|
+
alias_method :serializable_hash, :serializable_object
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'active_model/default_serializer'
|
2
|
+
require 'active_model/serializer'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
class Serializer
|
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_from_options = serializer.is_a?(String) ? serializer.constantize : serializer
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :name, :embed_ids, :embed_objects
|
27
|
+
attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :root_key, :serializer_from_options, :options
|
28
|
+
alias embed_ids? embed_ids
|
29
|
+
alias embed_objects? embed_objects
|
30
|
+
alias embed_in_root? embed_in_root
|
31
|
+
|
32
|
+
def embed=(embed)
|
33
|
+
@embed_ids = embed == :id || embed == :ids
|
34
|
+
@embed_objects = embed == :object || embed == :objects
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializer_from_object(object)
|
38
|
+
Serializer.serializer_for(object)
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_serializer
|
42
|
+
DefaultSerializer
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_serializer(object, options = {})
|
46
|
+
serializer_class(object).new(object, options.merge(self.options))
|
47
|
+
end
|
48
|
+
|
49
|
+
class HasOne < Association
|
50
|
+
def initialize(name, *args)
|
51
|
+
super
|
52
|
+
@root_key = @embedded_key.to_s.pluralize
|
53
|
+
@key ||= "#{name}_id"
|
54
|
+
end
|
55
|
+
|
56
|
+
def serializer_class(object)
|
57
|
+
serializer_from_options || serializer_from_object(object) || default_serializer
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_serializer(object, options = {})
|
61
|
+
options[:_wrap_in_array] = embed_in_root?
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class HasMany < Association
|
67
|
+
def initialize(name, *args)
|
68
|
+
super
|
69
|
+
@root_key = @embedded_key
|
70
|
+
@key ||= "#{name.to_s.singularize}_ids"
|
71
|
+
end
|
72
|
+
|
73
|
+
def serializer_class(object)
|
74
|
+
if use_array_serializer?
|
75
|
+
ArraySerializer
|
76
|
+
else
|
77
|
+
serializer_from_options
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def options
|
82
|
+
if use_array_serializer?
|
83
|
+
{ each_serializer: serializer_from_options }.merge! super
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def use_array_serializer?
|
92
|
+
!serializer_from_options ||
|
93
|
+
serializer_from_options && !(serializer_from_options <= ArraySerializer)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
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
|