active_model_serializers_rails_2.3 0.9.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG.md +87 -0
  2. data/CONTRIBUTING.md +20 -0
  3. data/DESIGN.textile +586 -0
  4. data/MIT-LICENSE +21 -0
  5. data/README.md +793 -0
  6. data/lib/active_model/array_serializer.rb +60 -0
  7. data/lib/active_model/default_serializer.rb +27 -0
  8. data/lib/active_model/serializable.rb +25 -0
  9. data/lib/active_model/serializer.rb +220 -0
  10. data/lib/active_model/serializer/associations.rb +98 -0
  11. data/lib/active_model/serializer/config.rb +31 -0
  12. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +14 -0
  13. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +93 -0
  14. data/lib/active_model/serializer/railtie.rb +10 -0
  15. data/lib/active_model/serializer/version.rb +5 -0
  16. data/lib/active_model/serializer_support.rb +5 -0
  17. data/lib/active_model_serializers.rb +33 -0
  18. data/test/coverage_setup.rb +15 -0
  19. data/test/fixtures/active_record.rb +92 -0
  20. data/test/fixtures/poro.rb +64 -0
  21. data/test/integration/active_record/active_record_test.rb +77 -0
  22. data/test/test_app.rb +11 -0
  23. data/test/test_helper.rb +13 -0
  24. data/test/unit/active_model/array_serializer/meta_test.rb +53 -0
  25. data/test/unit/active_model/array_serializer/root_test.rb +102 -0
  26. data/test/unit/active_model/array_serializer/scope_test.rb +24 -0
  27. data/test/unit/active_model/array_serializer/serialization_test.rb +182 -0
  28. data/test/unit/active_model/default_serializer_test.rb +13 -0
  29. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +21 -0
  30. data/test/unit/active_model/serializer/associations_test.rb +19 -0
  31. data/test/unit/active_model/serializer/attributes_test.rb +41 -0
  32. data/test/unit/active_model/serializer/config_test.rb +86 -0
  33. data/test/unit/active_model/serializer/filter_test.rb +49 -0
  34. data/test/unit/active_model/serializer/has_many_test.rb +174 -0
  35. data/test/unit/active_model/serializer/has_one_test.rb +151 -0
  36. data/test/unit/active_model/serializer/meta_test.rb +39 -0
  37. data/test/unit/active_model/serializer/root_test.rb +117 -0
  38. data/test/unit/active_model/serializer/scope_test.rb +49 -0
  39. 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