cm-active_model_serializers 0.10.0.rc1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +8 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +326 -0
- data/Rakefile +12 -0
- data/cm-active_model_serializers.gemspec +26 -0
- data/lib/action_controller/serialization.rb +62 -0
- data/lib/active_model/serializer.rb +261 -0
- data/lib/active_model/serializer/adapter.rb +87 -0
- data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
- data/lib/active_model/serializer/adapter/json.rb +52 -0
- data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
- data/lib/active_model/serializer/adapter/json_api.rb +152 -0
- data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
- data/lib/active_model/serializer/adapter/null.rb +11 -0
- data/lib/active_model/serializer/array_serializer.rb +32 -0
- data/lib/active_model/serializer/configuration.rb +13 -0
- data/lib/active_model/serializer/fieldset.rb +40 -0
- data/lib/active_model/serializer/version.rb +5 -0
- data/lib/active_model_serializers.rb +18 -0
- data/lib/generators/serializer/USAGE +6 -0
- data/lib/generators/serializer/serializer_generator.rb +37 -0
- data/lib/generators/serializer/templates/serializer.rb +8 -0
- data/test/action_controller/adapter_selector_test.rb +51 -0
- data/test/action_controller/explicit_serializer_test.rb +110 -0
- data/test/action_controller/json_api_linked_test.rb +173 -0
- data/test/action_controller/serialization_scope_name_test.rb +63 -0
- data/test/action_controller/serialization_test.rb +365 -0
- data/test/adapter/fragment_cache_test.rb +27 -0
- data/test/adapter/json/belongs_to_test.rb +48 -0
- data/test/adapter/json/collection_test.rb +73 -0
- data/test/adapter/json/has_many_test.rb +36 -0
- data/test/adapter/json_api/belongs_to_test.rb +147 -0
- data/test/adapter/json_api/collection_test.rb +89 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
- data/test/adapter/json_api/has_many_test.rb +106 -0
- data/test/adapter/json_api/has_one_test.rb +59 -0
- data/test/adapter/json_api/linked_test.rb +257 -0
- data/test/adapter/json_test.rb +34 -0
- data/test/adapter/null_test.rb +25 -0
- data/test/adapter_test.rb +43 -0
- data/test/array_serializer_test.rb +43 -0
- data/test/fixtures/poro.rb +213 -0
- data/test/serializers/adapter_for_test.rb +50 -0
- data/test/serializers/associations_test.rb +127 -0
- data/test/serializers/attribute_test.rb +38 -0
- data/test/serializers/attributes_test.rb +63 -0
- data/test/serializers/cache_test.rb +128 -0
- data/test/serializers/configuration_test.rb +15 -0
- data/test/serializers/fieldset_test.rb +26 -0
- data/test/serializers/generators_test.rb +59 -0
- data/test/serializers/meta_test.rb +78 -0
- data/test/serializers/options_test.rb +21 -0
- data/test/serializers/serializer_for_test.rb +65 -0
- data/test/serializers/urls_test.rb +26 -0
- data/test/test_helper.rb +38 -0
- metadata +195 -0
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_model/serializer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cm-active_model_serializers"
|
8
|
+
spec.version = ActiveModel::Serializer::VERSION
|
9
|
+
spec.authors = ["Steve Klabnik"]
|
10
|
+
spec.email = ["steve@steveklabnik.com"]
|
11
|
+
spec.summary = %q{Conventions-based JSON generation for Rails.}
|
12
|
+
spec.description = %q{ActiveModel::Serializers allows you to generate your JSON in an object-oriented and convention-driven manner.}
|
13
|
+
spec.homepage = "https://github.com/rails-api/active_model_serializers"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activemodel", ">= 4.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "rails", ">= 4.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module Serialization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
include ActionController::Renderers
|
8
|
+
|
9
|
+
ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :_serialization_scope
|
13
|
+
self._serialization_scope = :current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialization_scope
|
17
|
+
send(_serialization_scope) if _serialization_scope &&
|
18
|
+
respond_to?(_serialization_scope, true)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_serializer(resource)
|
22
|
+
@_serializer ||= @_serializer_opts.delete(:serializer)
|
23
|
+
@_serializer ||= ActiveModel::Serializer.serializer_for(resource)
|
24
|
+
|
25
|
+
if @_serializer_opts.key?(:each_serializer)
|
26
|
+
@_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
|
27
|
+
end
|
28
|
+
|
29
|
+
@_serializer
|
30
|
+
end
|
31
|
+
|
32
|
+
def use_adapter?
|
33
|
+
!(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
|
34
|
+
end
|
35
|
+
|
36
|
+
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
|
37
|
+
define_method renderer_method do |resource, options|
|
38
|
+
@_adapter_opts, @_serializer_opts =
|
39
|
+
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
|
40
|
+
|
41
|
+
if use_adapter? && (serializer = get_serializer(resource))
|
42
|
+
|
43
|
+
@_serializer_opts[:scope] ||= serialization_scope
|
44
|
+
@_serializer_opts[:scope_name] = _serialization_scope
|
45
|
+
|
46
|
+
# omg hax
|
47
|
+
object = serializer.new(resource, @_serializer_opts)
|
48
|
+
adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
|
49
|
+
super(adapter, options)
|
50
|
+
else
|
51
|
+
super(resource, options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
def serialization_scope(scope)
|
58
|
+
self._serialization_scope = scope
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'thread_safe'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
autoload :Configuration
|
7
|
+
autoload :ArraySerializer
|
8
|
+
autoload :Adapter
|
9
|
+
include Configuration
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :_attributes
|
13
|
+
attr_accessor :_attributes_keys
|
14
|
+
attr_accessor :_associations
|
15
|
+
attr_accessor :_urls
|
16
|
+
attr_accessor :_cache
|
17
|
+
attr_accessor :_fragmented
|
18
|
+
attr_accessor :_cache_key
|
19
|
+
attr_accessor :_cache_only
|
20
|
+
attr_accessor :_cache_except
|
21
|
+
attr_accessor :_cache_options
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.inherited(base)
|
25
|
+
base._attributes = self._attributes.try(:dup) || []
|
26
|
+
base._attributes_keys = self._attributes_keys.try(:dup) || {}
|
27
|
+
base._associations = self._associations.try(:dup) || {}
|
28
|
+
base._urls = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.attributes(*attrs)
|
32
|
+
attrs = attrs.first if attrs.first.class == Array
|
33
|
+
@_attributes.concat attrs
|
34
|
+
@_attributes.uniq!
|
35
|
+
|
36
|
+
attrs.each do |attr|
|
37
|
+
define_method attr do
|
38
|
+
object && object.read_attribute_for_serialization(attr)
|
39
|
+
end unless method_defined?(attr) || _fragmented.respond_to?(attr)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.attribute(attr, options = {})
|
44
|
+
key = options.fetch(:key, attr)
|
45
|
+
@_attributes_keys[attr] = {key: key} if key != attr
|
46
|
+
@_attributes << key unless @_attributes.include?(key)
|
47
|
+
define_method key do
|
48
|
+
object.read_attribute_for_serialization(attr)
|
49
|
+
end unless method_defined?(key) || _fragmented.respond_to?(attr)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.fragmented(serializer)
|
53
|
+
@_fragmented = serializer
|
54
|
+
end
|
55
|
+
|
56
|
+
# Enables a serializer to be automatically cached
|
57
|
+
def self.cache(options = {})
|
58
|
+
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
|
59
|
+
@_cache_key = options.delete(:key)
|
60
|
+
@_cache_only = options.delete(:only)
|
61
|
+
@_cache_except = options.delete(:except)
|
62
|
+
@_cache_options = (options.empty?) ? nil : options
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines an association in the object should be rendered.
|
66
|
+
#
|
67
|
+
# The serializer object should implement the association name
|
68
|
+
# as a method which should return an array when invoked. If a method
|
69
|
+
# with the association name does not exist, the association name is
|
70
|
+
# dispatched to the serialized object.
|
71
|
+
def self.has_many(*attrs)
|
72
|
+
associate(:has_many, attrs)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Defines an association in the object that should be rendered.
|
76
|
+
#
|
77
|
+
# The serializer object should implement the association name
|
78
|
+
# as a method which should return an object when invoked. If a method
|
79
|
+
# with the association name does not exist, the association name is
|
80
|
+
# dispatched to the serialized object.
|
81
|
+
def self.belongs_to(*attrs)
|
82
|
+
associate(:belongs_to, attrs)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Defines an association in the object should be rendered.
|
86
|
+
#
|
87
|
+
# The serializer object should implement the association name
|
88
|
+
# as a method which should return an object when invoked. If a method
|
89
|
+
# with the association name does not exist, the association name is
|
90
|
+
# dispatched to the serialized object.
|
91
|
+
def self.has_one(*attrs)
|
92
|
+
associate(:has_one, attrs)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.associate(type, attrs) #:nodoc:
|
96
|
+
options = attrs.extract_options!
|
97
|
+
self._associations = _associations.dup
|
98
|
+
|
99
|
+
attrs.each do |attr|
|
100
|
+
unless method_defined?(attr)
|
101
|
+
define_method attr do
|
102
|
+
object.send attr
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
self._associations[attr] = {type: type, association_options: options}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.url(attr)
|
111
|
+
@_urls.push attr
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.urls(*attrs)
|
115
|
+
@_urls.concat attrs
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.serializer_for(resource, options = {})
|
119
|
+
if resource.respond_to?(:serializer_class)
|
120
|
+
resource.serializer_class
|
121
|
+
elsif resource.respond_to?(:to_ary)
|
122
|
+
config.array_serializer
|
123
|
+
else
|
124
|
+
options
|
125
|
+
.fetch(:association_options, {})
|
126
|
+
.fetch(:serializer, get_serializer_for(resource.class))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.adapter
|
131
|
+
adapter_class = case config.adapter
|
132
|
+
when Symbol
|
133
|
+
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
|
134
|
+
when Class
|
135
|
+
config.adapter
|
136
|
+
end
|
137
|
+
unless adapter_class
|
138
|
+
valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
|
139
|
+
raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
|
140
|
+
end
|
141
|
+
|
142
|
+
adapter_class
|
143
|
+
end
|
144
|
+
|
145
|
+
def self._root
|
146
|
+
@@root ||= false
|
147
|
+
end
|
148
|
+
|
149
|
+
def self._root=(root)
|
150
|
+
@@root = root
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.root_name
|
154
|
+
name.demodulize.underscore.sub(/_serializer$/, '') if name
|
155
|
+
end
|
156
|
+
|
157
|
+
attr_accessor :object, :root, :meta, :meta_key, :scope
|
158
|
+
|
159
|
+
def initialize(object, options = {})
|
160
|
+
@object = object
|
161
|
+
@options = options
|
162
|
+
@root = options[:root] || (self.class._root ? self.class.root_name : false)
|
163
|
+
@meta = options[:meta]
|
164
|
+
@meta_key = options[:meta_key]
|
165
|
+
@scope = options[:scope]
|
166
|
+
|
167
|
+
scope_name = options[:scope_name]
|
168
|
+
if scope_name && !respond_to?(scope_name)
|
169
|
+
self.class.class_eval do
|
170
|
+
define_method scope_name, lambda { scope }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def json_key
|
176
|
+
if root == true || root.nil?
|
177
|
+
self.class.root_name
|
178
|
+
else
|
179
|
+
root
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def id
|
184
|
+
object.id if object
|
185
|
+
end
|
186
|
+
|
187
|
+
def type
|
188
|
+
object.class.to_s.demodulize.underscore.pluralize
|
189
|
+
end
|
190
|
+
|
191
|
+
def attributes(options = {})
|
192
|
+
attributes =
|
193
|
+
if options[:fields]
|
194
|
+
self.class._attributes & options[:fields]
|
195
|
+
else
|
196
|
+
self.class._attributes.dup
|
197
|
+
end
|
198
|
+
|
199
|
+
attributes += options[:required_fields] if options[:required_fields]
|
200
|
+
|
201
|
+
attributes.each_with_object({}) do |name, hash|
|
202
|
+
unless self.class._fragmented
|
203
|
+
hash[name] = send(name)
|
204
|
+
else
|
205
|
+
hash[name] = self.class._fragmented.public_send(name)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def each_association(&block)
|
211
|
+
self.class._associations.dup.each do |name, association_options|
|
212
|
+
next unless object
|
213
|
+
association_value = send(name)
|
214
|
+
|
215
|
+
serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
|
216
|
+
|
217
|
+
if serializer_class
|
218
|
+
serializer = serializer_class.new(
|
219
|
+
association_value,
|
220
|
+
options.merge(serializer_from_options(association_options))
|
221
|
+
)
|
222
|
+
elsif !association_value.nil? && !association_value.instance_of?(Object)
|
223
|
+
association_options[:association_options][:virtual_value] = association_value
|
224
|
+
end
|
225
|
+
|
226
|
+
if block_given?
|
227
|
+
block.call(name, serializer, association_options[:association_options])
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def serializer_from_options(options)
|
233
|
+
opts = {}
|
234
|
+
serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
|
235
|
+
opts[:serializer] = serializer if serializer
|
236
|
+
opts
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.serializers_cache
|
240
|
+
@serializers_cache ||= ThreadSafe::Cache.new
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
attr_reader :options
|
246
|
+
|
247
|
+
def self.get_serializer_for(klass)
|
248
|
+
serializers_cache.fetch_or_store(klass) do
|
249
|
+
serializer_class_name = "#{klass.name}Serializer"
|
250
|
+
serializer_class = serializer_class_name.safe_constantize
|
251
|
+
|
252
|
+
if serializer_class
|
253
|
+
serializer_class
|
254
|
+
elsif klass.superclass
|
255
|
+
get_serializer_for(klass.superclass)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'active_model/serializer/adapter/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
autoload :Json
|
8
|
+
autoload :Null
|
9
|
+
autoload :JsonApi
|
10
|
+
|
11
|
+
attr_reader :serializer
|
12
|
+
|
13
|
+
def initialize(serializer, options = {})
|
14
|
+
@serializer = serializer
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def serializable_hash(options = {})
|
19
|
+
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
20
|
+
end
|
21
|
+
|
22
|
+
def as_json(options = {})
|
23
|
+
hash = serializable_hash(options)
|
24
|
+
include_meta(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create(resource, options = {})
|
28
|
+
override = options.delete(:adapter)
|
29
|
+
klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
|
30
|
+
klass.new(resource, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.adapter_class(adapter)
|
34
|
+
"ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_cache(*args)
|
38
|
+
raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def cache_check(serializer)
|
44
|
+
@cached_serializer = serializer
|
45
|
+
@klass = @cached_serializer.class
|
46
|
+
if is_cached?
|
47
|
+
@klass._cache.fetch(cache_key, @klass._cache_options) do
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
elsif is_fragment_cached?
|
51
|
+
FragmentCache.new(self, @cached_serializer, @options, @root).fetch
|
52
|
+
else
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_cached?
|
58
|
+
@klass._cache && !@klass._cache_only && !@klass._cache_except
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_fragment_cached?
|
62
|
+
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
|
63
|
+
end
|
64
|
+
|
65
|
+
def cache_key
|
66
|
+
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{@cached_serializer.object.updated_at}" : @cached_serializer.object.cache_key
|
67
|
+
end
|
68
|
+
|
69
|
+
def meta
|
70
|
+
serializer.meta if serializer.respond_to?(:meta)
|
71
|
+
end
|
72
|
+
|
73
|
+
def meta_key
|
74
|
+
serializer.meta_key || "meta"
|
75
|
+
end
|
76
|
+
|
77
|
+
def root
|
78
|
+
serializer.json_key
|
79
|
+
end
|
80
|
+
|
81
|
+
def include_meta(json)
|
82
|
+
json[meta_key] = meta if meta && root
|
83
|
+
json
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|