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.
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