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
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class FragmentCache
|
5
|
+
|
6
|
+
attr_reader :serializer
|
7
|
+
|
8
|
+
def initialize(adapter, serializer, options, root)
|
9
|
+
@root = root
|
10
|
+
@options = options
|
11
|
+
@adapter = adapter
|
12
|
+
@serializer = serializer
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch
|
16
|
+
klass = serializer.class
|
17
|
+
# It will split the serializer into two, one that will be cached and other wont
|
18
|
+
serializers = fragment_serializer(serializer.object.class.name, klass)
|
19
|
+
|
20
|
+
# Instanciate both serializers
|
21
|
+
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
22
|
+
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
23
|
+
|
24
|
+
cached_adapter = @adapter.class.new(cached_serializer, @options)
|
25
|
+
non_cached_adapter = @adapter.class.new(non_cached_serializer, @options)
|
26
|
+
|
27
|
+
# Get serializable hash from both
|
28
|
+
cached_hash = cached_adapter.serializable_hash
|
29
|
+
non_cached_hash = non_cached_adapter.serializable_hash
|
30
|
+
|
31
|
+
# Merge both results
|
32
|
+
@adapter.fragment_cache(cached_hash, non_cached_hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cached_attributes(klass, serializers)
|
38
|
+
cached_attributes = (klass._cache_only) ? klass._cache_only : serializer.attributes.keys.delete_if {|attr| klass._cache_except.include?(attr) }
|
39
|
+
non_cached_attributes = serializer.attributes.keys.delete_if {|attr| cached_attributes.include?(attr) }
|
40
|
+
|
41
|
+
cached_attributes.each do |attribute|
|
42
|
+
options = serializer.class._attributes_keys[attribute]
|
43
|
+
options ||= {}
|
44
|
+
# Add cached attributes to cached Serializer
|
45
|
+
serializers[:cached].constantize.attribute(attribute, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
non_cached_attributes.each do |attribute|
|
49
|
+
options = serializer.class._attributes_keys[attribute]
|
50
|
+
options ||= {}
|
51
|
+
# Add non-cached attributes to non-cached Serializer
|
52
|
+
serializers[:non_cached].constantize.attribute(attribute, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fragment_serializer(name, klass)
|
57
|
+
cached = "#{name.capitalize}CachedSerializer"
|
58
|
+
non_cached = "#{name.capitalize}NonCachedSerializer"
|
59
|
+
|
60
|
+
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
|
61
|
+
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
|
62
|
+
|
63
|
+
klass._cache_options ||= {}
|
64
|
+
klass._cache_options[:key] = klass._cache_key if klass._cache_key
|
65
|
+
|
66
|
+
cached.constantize.cache(klass._cache_options)
|
67
|
+
|
68
|
+
cached.constantize.fragmented(serializer)
|
69
|
+
non_cached.constantize.fragmented(serializer)
|
70
|
+
|
71
|
+
serializers = {cached: cached, non_cached: non_cached}
|
72
|
+
cached_attributes(klass, serializers)
|
73
|
+
serializers
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_model/serializer/adapter/json/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
class Json < Adapter
|
7
|
+
def serializable_hash(options = {})
|
8
|
+
if serializer.respond_to?(:each)
|
9
|
+
@result = serializer.map{|s| self.class.new(s).serializable_hash }
|
10
|
+
else
|
11
|
+
@hash = {}
|
12
|
+
|
13
|
+
@core = cache_check(serializer) do
|
14
|
+
serializer.attributes(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
serializer.each_association do |name, association, opts|
|
18
|
+
if association.respond_to?(:each)
|
19
|
+
array_serializer = association
|
20
|
+
@hash[name] = array_serializer.map do |item|
|
21
|
+
cache_check(item) do
|
22
|
+
item.attributes(opts)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
if association && association.object
|
27
|
+
@hash[name] = cache_check(association) do
|
28
|
+
association.attributes(options)
|
29
|
+
end
|
30
|
+
elsif opts[:virtual_value]
|
31
|
+
@hash[name] = opts[:virtual_value]
|
32
|
+
else
|
33
|
+
@hash[name] = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@result = @core.merge @hash
|
38
|
+
end
|
39
|
+
|
40
|
+
if root = options.fetch(:root, serializer.json_key)
|
41
|
+
@result = { root => @result }
|
42
|
+
end
|
43
|
+
@result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
48
|
+
Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'active_model/serializer/adapter/json_api/fragment_cache'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class Serializer
|
5
|
+
class Adapter
|
6
|
+
class JsonApi < Adapter
|
7
|
+
def initialize(serializer, options = {})
|
8
|
+
super
|
9
|
+
serializer.root = true
|
10
|
+
@hash = { data: [] }
|
11
|
+
|
12
|
+
if fields = options.delete(:fields)
|
13
|
+
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
|
14
|
+
else
|
15
|
+
@fieldset = options[:fieldset]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serializable_hash(options = {})
|
20
|
+
if serializer.respond_to?(:each)
|
21
|
+
serializer.each do |s|
|
22
|
+
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash
|
23
|
+
@hash[:data] << result[:data]
|
24
|
+
|
25
|
+
if result[:included]
|
26
|
+
@hash[:included] ||= []
|
27
|
+
@hash[:included] |= result[:included]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
@hash[:data] = attributes_for_serializer(serializer, @options)
|
32
|
+
add_resource_links(@hash[:data], serializer)
|
33
|
+
end
|
34
|
+
@hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
38
|
+
root = false if @options.include?(:include)
|
39
|
+
JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add_links(resource, name, serializers)
|
45
|
+
resource[:links] ||= {}
|
46
|
+
resource[:links][name] ||= { linkage: [] }
|
47
|
+
resource[:links][name][:linkage] += serializers.map { |serializer| { type: serializer.type, id: serializer.id.to_s } }
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_link(resource, name, serializer, val=nil)
|
51
|
+
resource[:links] ||= {}
|
52
|
+
resource[:links][name] = { linkage: nil }
|
53
|
+
|
54
|
+
if serializer && serializer.object
|
55
|
+
resource[:links][name][:linkage] = { type: serializer.type, id: serializer.id.to_s }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_included(resource_name, serializers, parent = nil)
|
60
|
+
unless serializers.respond_to?(:each)
|
61
|
+
return unless serializers.object
|
62
|
+
serializers = Array(serializers)
|
63
|
+
end
|
64
|
+
resource_path = [parent, resource_name].compact.join('.')
|
65
|
+
if include_assoc?(resource_path)
|
66
|
+
@hash[:included] ||= []
|
67
|
+
|
68
|
+
serializers.each do |serializer|
|
69
|
+
attrs = attributes_for_serializer(serializer, @options)
|
70
|
+
|
71
|
+
add_resource_links(attrs, serializer, add_included: false)
|
72
|
+
|
73
|
+
@hash[:included].push(attrs) unless @hash[:included].include?(attrs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
serializers.each do |serializer|
|
78
|
+
serializer.each_association do |name, association, opts|
|
79
|
+
add_included(name, association, resource_path) if association
|
80
|
+
end if include_nested_assoc? resource_path
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def attributes_for_serializer(serializer, options)
|
85
|
+
if serializer.respond_to?(:each)
|
86
|
+
result = []
|
87
|
+
serializer.each do |object|
|
88
|
+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
|
89
|
+
result << cache_check(object) do
|
90
|
+
options[:required_fields] = [:id, :type]
|
91
|
+
attributes = object.attributes(options)
|
92
|
+
attributes[:id] = attributes[:id].to_s
|
93
|
+
result << attributes
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
|
98
|
+
options[:required_fields] = [:id, :type]
|
99
|
+
result = cache_check(serializer) do
|
100
|
+
result = serializer.attributes(options)
|
101
|
+
result[:id] = result[:id].to_s
|
102
|
+
result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
result
|
106
|
+
end
|
107
|
+
|
108
|
+
def include_assoc?(assoc)
|
109
|
+
return false unless @options[:include]
|
110
|
+
check_assoc("#{assoc}$")
|
111
|
+
end
|
112
|
+
|
113
|
+
def include_nested_assoc?(assoc)
|
114
|
+
return false unless @options[:include]
|
115
|
+
check_assoc("#{assoc}.")
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_assoc(assoc)
|
119
|
+
include_opt = @options[:include]
|
120
|
+
include_opt = include_opt.split(',') if include_opt.is_a?(String)
|
121
|
+
include_opt.any? do |s|
|
122
|
+
s.match(/^#{assoc.gsub('.', '\.')}/)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_resource_links(attrs, serializer, options = {})
|
127
|
+
options[:add_included] = options.fetch(:add_included, true)
|
128
|
+
|
129
|
+
serializer.each_association do |name, association, opts|
|
130
|
+
attrs[:links] ||= {}
|
131
|
+
|
132
|
+
if association.respond_to?(:each)
|
133
|
+
add_links(attrs, name, association)
|
134
|
+
else
|
135
|
+
if opts[:virtual_value]
|
136
|
+
add_link(attrs, name, nil, opts[:virtual_value])
|
137
|
+
else
|
138
|
+
add_link(attrs, name, association)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if options[:add_included]
|
143
|
+
Array(association).each do |association|
|
144
|
+
add_included(name, association)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Adapter
|
4
|
+
class JsonApi < Adapter
|
5
|
+
class FragmentCache
|
6
|
+
|
7
|
+
def fragment_cache(root, cached_hash, non_cached_hash)
|
8
|
+
hash = {}
|
9
|
+
core_cached = cached_hash.first
|
10
|
+
core_non_cached = non_cached_hash.first
|
11
|
+
no_root_cache = cached_hash.delete_if {|key, value| key == core_cached[0] }
|
12
|
+
no_root_non_cache = non_cached_hash.delete_if {|key, value| key == core_non_cached[0] }
|
13
|
+
cached_resource = (core_cached[1]) ? core_cached[1].merge(core_non_cached[1]) : core_non_cached[1]
|
14
|
+
hash = (root) ? { root => cached_resource } : cached_resource
|
15
|
+
hash.merge no_root_non_cache.merge no_root_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class ArraySerializer
|
4
|
+
include Enumerable
|
5
|
+
delegate :each, to: :@objects
|
6
|
+
|
7
|
+
attr_reader :meta, :meta_key
|
8
|
+
|
9
|
+
def initialize(objects, options = {})
|
10
|
+
options.merge!(root: nil)
|
11
|
+
|
12
|
+
@objects = objects.map do |object|
|
13
|
+
serializer_class = options.fetch(
|
14
|
+
:serializer,
|
15
|
+
ActiveModel::Serializer.serializer_for(object)
|
16
|
+
)
|
17
|
+
serializer_class.new(object, options.except(:serializer))
|
18
|
+
end
|
19
|
+
@meta = options[:meta]
|
20
|
+
@meta_key = options[:meta_key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def json_key
|
24
|
+
@objects.first.json_key if @objects.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def root=(root)
|
28
|
+
@objects.first.root = root if @objects.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
module Configuration
|
4
|
+
include ActiveSupport::Configurable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do |base|
|
8
|
+
base.config.array_serializer = ActiveModel::Serializer::ArraySerializer
|
9
|
+
base.config.adapter = :json
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
class Serializer
|
3
|
+
class Fieldset
|
4
|
+
|
5
|
+
def initialize(fields, root = nil)
|
6
|
+
@root = root
|
7
|
+
@raw_fields = fields
|
8
|
+
end
|
9
|
+
|
10
|
+
def fields
|
11
|
+
@fields ||= parsed_fields
|
12
|
+
end
|
13
|
+
|
14
|
+
def fields_for(serializer)
|
15
|
+
key = serializer.json_key || serializer.class.root_name
|
16
|
+
fields[key.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :raw_fields, :root
|
22
|
+
|
23
|
+
def parsed_fields
|
24
|
+
if raw_fields.is_a?(Hash)
|
25
|
+
raw_fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
|
26
|
+
elsif raw_fields.is_a?(Array)
|
27
|
+
if root.nil?
|
28
|
+
raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.'
|
29
|
+
end
|
30
|
+
hash = {}
|
31
|
+
hash[root.to_sym] = raw_fields.map(&:to_sym)
|
32
|
+
hash
|
33
|
+
else
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_model/serializer/version'
|
3
|
+
require 'active_model/serializer'
|
4
|
+
require 'active_model/serializer/fieldset'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'action_controller'
|
8
|
+
require 'action_controller/serialization'
|
9
|
+
|
10
|
+
ActiveSupport.on_load(:action_controller) do
|
11
|
+
include ::ActionController::Serialization
|
12
|
+
ActionDispatch::Reloader.to_prepare do
|
13
|
+
ActiveModel::Serializer.serializers_cache.clear
|
14
|
+
end
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
# rails not installed, continuing
|
18
|
+
end
|