active_facet 1.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE.txt +21 -0
- data/README.md +206 -0
- data/Rakefile +29 -0
- data/lib/active_facet.rb +98 -0
- data/lib/active_facet/acts_as_active_facet.rb +102 -0
- data/lib/active_facet/config.rb +267 -0
- data/lib/active_facet/document_cache.rb +52 -0
- data/lib/active_facet/errors/attribute_error.rb +6 -0
- data/lib/active_facet/errors/configuration_error.rb +16 -0
- data/lib/active_facet/errors/lookup_error.rb +6 -0
- data/lib/active_facet/filter.rb +62 -0
- data/lib/active_facet/helper.rb +116 -0
- data/lib/active_facet/serializer/base.rb +297 -0
- data/lib/active_facet/serializer/facade.rb +223 -0
- data/lib/active_facet/version.rb +3 -0
- data/lib/rails/generators/active_facet/install/install_generator.rb +21 -0
- data/lib/rails/generators/active_facet/install/templates/active_facet.yml +18 -0
- data/lib/rails/generators/active_facet/install/templates/initializer.rb +85 -0
- metadata +317 -0
@@ -0,0 +1,297 @@
|
|
1
|
+
#TODO --jdc rebuild this class to either use an explicit singleton pattern or use a factory pattern
|
2
|
+
# Mixin providing DSL for ActiveFacet Serializers and a handful of public methods which reflect on the DSL
|
3
|
+
module ActiveFacet
|
4
|
+
module Serializer
|
5
|
+
module Base
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# ###
|
11
|
+
# DSL
|
12
|
+
# ###
|
13
|
+
|
14
|
+
# See Readme.md
|
15
|
+
|
16
|
+
# DSL Defines a transform to rename or reformat an attribute
|
17
|
+
# @param api_attribute [Symbol] name of the attribute
|
18
|
+
# @param options [Hash]
|
19
|
+
# @option options [Symbol] :as internal method name to call on the resource for serialization and hydration
|
20
|
+
# @option options [Symbol] :from internal method name to call on the resource for serialization
|
21
|
+
# @option options [Symbol] :to internal method name to call on the resource for hydration
|
22
|
+
# @option options [Symbol] :with name of a CustomAttributeSerializer Class for serialization and hydration
|
23
|
+
# @option options [Symbol] :within name of nested json attribute to serialize/hyrdrate within
|
24
|
+
def transform(api_attribute, options = {})
|
25
|
+
if options[:as].present?
|
26
|
+
config.transforms_from[api_attribute] = options[:as]
|
27
|
+
config.transforms_to[api_attribute] = options[:as]
|
28
|
+
end
|
29
|
+
if options[:from].present?
|
30
|
+
config.transforms_from[api_attribute] = options[:from]
|
31
|
+
config.transforms_to[api_attribute] ||= options[:from]
|
32
|
+
end
|
33
|
+
if options[:to].present?
|
34
|
+
config.transforms_from[api_attribute] ||= options[:to]
|
35
|
+
config.transforms_to[api_attribute] = options[:to]
|
36
|
+
end
|
37
|
+
config.serializers[api_attribute] = options[:with] if options[:with].present?
|
38
|
+
config.namespaces[api_attribute] = options[:within] if options[:within].present?
|
39
|
+
expose api_attribute
|
40
|
+
end
|
41
|
+
|
42
|
+
# DSL Defines an attribute extension available for decoration and serialization
|
43
|
+
# @param api_attribute [Symbol] name of the attribute
|
44
|
+
def extension(api_attribute)
|
45
|
+
config.extensions[api_attribute] = true
|
46
|
+
config.serializers[api_attribute] = api_attribute.to_sym
|
47
|
+
expose api_attribute
|
48
|
+
end
|
49
|
+
|
50
|
+
# DSL Defines an alias that can be used instead of a Field Set
|
51
|
+
# @param field_set_name [Symbol] the alias name
|
52
|
+
# @param options [Hash]
|
53
|
+
# @option as [MIXED] a nested field_set collection
|
54
|
+
def expose(field_set_name, options = {})
|
55
|
+
field_set_name = field_set_name.to_sym
|
56
|
+
raise ActiveFacet::Errors::ConfigurationError.new(ActiveFacet::Errors::ConfigurationError::ALL_ATTRIBUTES_ERROR_MSG) if field_set_name == :all_attributes
|
57
|
+
raise ActiveFacet::Errors::ConfigurationError.new(ActiveFacet::Errors::ConfigurationError::ALL_FIELDS_ERROR_MSG) if field_set_name == :all
|
58
|
+
config.alias_field_set(field_set_name, options.key?(:as) ? options[:as] : field_set_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
#DSL Defines an alias for common ActiveRecord attributes
|
62
|
+
def expose_timestamps
|
63
|
+
transform :created_at, with: :time
|
64
|
+
transform :updated_at, with: :time
|
65
|
+
expose :timestamps, as: [:id, :created_at, :updated_at]
|
66
|
+
end
|
67
|
+
|
68
|
+
#DSL Registers the class type to be serialized
|
69
|
+
def resource_class(klass)
|
70
|
+
config.resource_class = klass
|
71
|
+
end
|
72
|
+
|
73
|
+
# ###
|
74
|
+
# Public Interface
|
75
|
+
# ###
|
76
|
+
|
77
|
+
# Singleton
|
78
|
+
# @return [Serializer::Base]
|
79
|
+
def new
|
80
|
+
@instance ||= super
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO --jdc change new/instance contract
|
84
|
+
# def instance
|
85
|
+
# @instance ||= new
|
86
|
+
# end
|
87
|
+
|
88
|
+
# Memoized class getter
|
89
|
+
# @return [Config]
|
90
|
+
def config
|
91
|
+
@config ||= ActiveFacet::Config.new
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# INSTANCE METHODS
|
97
|
+
|
98
|
+
# This method returns a hash suitable to pass into ActiveRecord.includes to avoid N+1
|
99
|
+
# @param field_set [Field Set] collections of fields to be serialized from this resource later
|
100
|
+
# given:
|
101
|
+
# :basic is a defined collection
|
102
|
+
# :extended is a defined collection
|
103
|
+
# :orders is a defined association
|
104
|
+
# :line_items is a defined association on OrderSerializer
|
105
|
+
# examples:
|
106
|
+
# :basic
|
107
|
+
# [:basic]
|
108
|
+
# {basic: nil}
|
109
|
+
# [:basic, :extended]
|
110
|
+
# [:basic, :extended, :orders]
|
111
|
+
# [:basic, :extended, {orders: :basic}]
|
112
|
+
# [:basic, :extended, {orders: [:basic, :extended]}]
|
113
|
+
# [:basic, :extended, {orders: [:basic, :line_items]}]
|
114
|
+
# [:basic, :extended, {orders: [:basic, {line_items: :extended}]}]
|
115
|
+
# @return [Hash]
|
116
|
+
def scoped_includes(field_set = nil, options = {})
|
117
|
+
result = {}
|
118
|
+
config.field_set_itterator(field_set) do |field, nested_field_set|
|
119
|
+
case value = scoped_include(field, nested_field_set, options)
|
120
|
+
when nil
|
121
|
+
when Hash
|
122
|
+
result.deep_merge! value
|
123
|
+
else
|
124
|
+
result[value] ||= nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
result
|
128
|
+
end
|
129
|
+
|
130
|
+
# TODO -- comment and move private
|
131
|
+
|
132
|
+
def scoped_include(field, nested_field_set, options)
|
133
|
+
if is_association? field
|
134
|
+
attribute = resource_attribute_name(field)
|
135
|
+
if nested_field_set
|
136
|
+
serializer_class = get_association_serializer_class(field, options)
|
137
|
+
attribute = { attribute => serializer_class.present? ? serializer_class.scoped_includes(nested_field_set, options) : nested_field_set }
|
138
|
+
end
|
139
|
+
attribute
|
140
|
+
else
|
141
|
+
custom_includes(field, options)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
#TODO -- move private
|
146
|
+
|
147
|
+
# Returns field_set serialized for dependant resources in custom attribute serializers & extensions
|
148
|
+
# @param field [Field]
|
149
|
+
# @return [Field Set]
|
150
|
+
def custom_includes(field, options)
|
151
|
+
attribute = resource_attribute_name(field)
|
152
|
+
custom_serializer_name = config.serializers[attribute]
|
153
|
+
|
154
|
+
if custom_serializer_name
|
155
|
+
custom_serializer = get_custom_serializer_class(custom_serializer_name, options)
|
156
|
+
if custom_serializer.respond_to? :custom_scope
|
157
|
+
custom_serializer.custom_scope
|
158
|
+
else
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
else
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Gets flattened fields from a Field Set Alias
|
167
|
+
# @param field_set_alias [Symbol] to retrieve aliased field_sets for
|
168
|
+
# @param include_relations [Boolean]
|
169
|
+
# @param include_nested_field_sets [Boolean]
|
170
|
+
# @return [Array] of symbols
|
171
|
+
def exposed_aliases(field_set_alias = :all, include_relations = false, include_nested_field_sets = false)
|
172
|
+
return include_nested_field_sets ? field_set_alias : [field_set_alias] unless normalized_field_sets = config.normalized_field_sets[field_set_alias]
|
173
|
+
result = normalized_field_sets[include_relations ? :fields : :attributes]
|
174
|
+
return result if include_nested_field_sets
|
175
|
+
result.keys.map(&:to_sym).sort
|
176
|
+
end
|
177
|
+
|
178
|
+
# This method returns a ActiveRecord model updated to match a JSON of hash values
|
179
|
+
# @param resource [ActiveRecord] to hydrate
|
180
|
+
# @param attribute [Hash] subset of the values returned by {resource.as_json}
|
181
|
+
# @return [ActiveRecord] resource
|
182
|
+
def from_hash(resource, attributes, options = {})
|
183
|
+
ActiveFacet::Serializer::Facade.new(self, resource, options).from_hash(attributes)
|
184
|
+
end
|
185
|
+
|
186
|
+
# This method returns a JSON of hash values representing the resource(s)
|
187
|
+
# @param resource [ActiveRecord || Array] CollectionProxy object ::or:: a collection of resources
|
188
|
+
# @param options [Hash] collection of values required that are not available in lexical field_set
|
189
|
+
# @return [JSON] representing the resource
|
190
|
+
def as_json(resources, options = {})
|
191
|
+
resource_itterator(resources) do |resource|
|
192
|
+
facade = ActiveFacet::Serializer::Facade.new(self, resource, options)
|
193
|
+
facade.as_json
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Memoized instance getter
|
198
|
+
# @return [Config]
|
199
|
+
def config
|
200
|
+
@config ||= self.class.config
|
201
|
+
end
|
202
|
+
|
203
|
+
# ^^ START REFLECTOR
|
204
|
+
### TODO --jdc move all this to a reflector class, instance holding resource_class, and store in config
|
205
|
+
|
206
|
+
# Constantizes the appropriate resource serializer class
|
207
|
+
# @return [Class]
|
208
|
+
def resource_class
|
209
|
+
@resource_class ||= config.resource_class
|
210
|
+
end
|
211
|
+
|
212
|
+
# Constantizes an appropriate resource serializer class for relations
|
213
|
+
# @param field [Symbol] to find relation reflection for
|
214
|
+
# @return [Reflection | nil]
|
215
|
+
def get_association_reflection(field)
|
216
|
+
@association_reflections ||= {}
|
217
|
+
@association_reflections[field] ||= resource_class.reflect_on_association(resource_attribute_name(field).to_sym)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Constantizes an appropriate resource serializer class
|
221
|
+
# @param field [Symbol] to test as relation and find serializer class for
|
222
|
+
# @return [Class | nil]
|
223
|
+
def get_association_serializer_class(field, options)
|
224
|
+
@association_serializers ||= {}
|
225
|
+
unless @association_serializers.key? field
|
226
|
+
@association_serializers[field] = nil
|
227
|
+
#return nil if field isn't an association
|
228
|
+
if reflection = get_association_reflection(field)
|
229
|
+
#return nil if association doesn't have a custom class
|
230
|
+
@association_serializers[field] = ActiveFacet::Helper.serializer_for(reflection.klass, options)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
@association_serializers[field]
|
234
|
+
end
|
235
|
+
|
236
|
+
# Constantizes an appropriate attribute serializer class
|
237
|
+
# @param attribute [Symbol] base_name of attribute serializer class to find
|
238
|
+
# @param options [Hash]
|
239
|
+
# @return [Class | nil]
|
240
|
+
def get_custom_serializer_class(attribute, options)
|
241
|
+
@custom_serializers ||= {}
|
242
|
+
@custom_serializers[attribute] ||= ActiveFacet::Helper.attribute_serializer_class_for(resource_class, attribute, options)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Determines if public attribute maps to a private relation
|
246
|
+
# @param field [Symbol] public attribute name
|
247
|
+
# @return [Boolean]
|
248
|
+
def is_association?(field)
|
249
|
+
!!get_association_reflection(field)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Renames attribute between resource.attribute_name and json.attribute_name
|
253
|
+
# @param field [Symbol] attribute name
|
254
|
+
# @param direction [Symbol] to apply translation
|
255
|
+
# @return [Symbol]
|
256
|
+
def resource_attribute_name(field, direction = :from)
|
257
|
+
(config.transforms(direction)[field] || field).to_sym
|
258
|
+
end
|
259
|
+
|
260
|
+
### TODO ^^ END REFLECTOR
|
261
|
+
|
262
|
+
protected
|
263
|
+
|
264
|
+
# @return [Serializer::Base]
|
265
|
+
def initialize
|
266
|
+
config.compile! self
|
267
|
+
rescue SystemStackError => e
|
268
|
+
raise ActiveFacet::Errors::ConfigurationError.new(ActiveFacet::Errors::ConfigurationError::STACK_ERROR_MSG)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Itterates a resource collection invoking block
|
272
|
+
# @param resource [ActiveRecord || Array] to traverse
|
273
|
+
# @param block [Block] to call for each resource
|
274
|
+
def resource_itterator(resource)
|
275
|
+
if resource.is_a?(Array)
|
276
|
+
resource.map do |resource|
|
277
|
+
yield resource
|
278
|
+
end.compact
|
279
|
+
else
|
280
|
+
yield resource
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Removes self.class_name from the end of self.module_name
|
285
|
+
# @return [String]
|
286
|
+
def module_base_name
|
287
|
+
@module_base_name ||= module_name.deconstantize
|
288
|
+
end
|
289
|
+
|
290
|
+
# Removes self.class_name from the end of self.class.name
|
291
|
+
# @return [String]
|
292
|
+
def module_name
|
293
|
+
@module_name ||= self.class.name.deconstantize
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# Serializes Facets of a given resource using an ActiveFacet::Serializer::Base serializer
|
2
|
+
module ActiveFacet
|
3
|
+
module Serializer
|
4
|
+
class Facade
|
5
|
+
attr_accessor :serializer, # Serializer:Base
|
6
|
+
:resource, # Object to delegate to
|
7
|
+
:options, # Options Hash passed to as_json
|
8
|
+
:opts, # RCB specific options inside Options Hash
|
9
|
+
:fields, # Field Sets to apply
|
10
|
+
:field_overrides, # Field Overrides to apply
|
11
|
+
:overrides, # Field Overrides specific to resource
|
12
|
+
:version, # Serializer version to apply
|
13
|
+
:filters, # Filters to apply
|
14
|
+
:filters_enabled # Apply Filters, override global setting
|
15
|
+
|
16
|
+
# @return [Serializer::Facade]
|
17
|
+
def initialize(serializer, resource, options = {})
|
18
|
+
self.serializer = serializer
|
19
|
+
self.resource = resource
|
20
|
+
self.options = options
|
21
|
+
self.opts = options[ActiveFacet.opts_key] || {}
|
22
|
+
|
23
|
+
self.fields = opts[ActiveFacet.fields_key]
|
24
|
+
self.field_overrides = opts[ActiveFacet.field_overrides_key] || {}
|
25
|
+
self.overrides = ActiveFacet::Helper.resource_map(resource_class).inject({}) { |overrides, map_entry|
|
26
|
+
overrides.merge(field_overrides[map_entry] || {})
|
27
|
+
}
|
28
|
+
|
29
|
+
self.version = opts[ActiveFacet.version_key]
|
30
|
+
self.filters = opts[ActiveFacet.filters_key]
|
31
|
+
self.filters_enabled = opts.key?(ActiveFacet.filters_force_key) ? opts[ActiveFacet.filters_force_key] : ActiveFacet.filters_enabled
|
32
|
+
end
|
33
|
+
|
34
|
+
# This method returns a JSON of hash values representing the resource
|
35
|
+
# @param resource [ActiveRecord || Array] CollectionProxy object ::or:: a collection of resources
|
36
|
+
# @param opts [Hash] collection of values required that are not available in lexical scope
|
37
|
+
# @return [JSON] representing the values returned by {resource.serialize} method
|
38
|
+
def as_json
|
39
|
+
ActiveFacet.document_cache.fetch(self) {
|
40
|
+
serialize!
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] a cache key that can be used to identify this resource
|
45
|
+
def cache_key
|
46
|
+
version.to_s +
|
47
|
+
resource.cache_key +
|
48
|
+
fields.to_s +
|
49
|
+
field_overrides.to_s +
|
50
|
+
filters.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
# This method returns a ActiveRecord model updated to match a JSON of hash values
|
54
|
+
# @param resource [ActiveRecord] to hydrate
|
55
|
+
# @param attribute [Hash] subset of the values returned by {resource.as_json}
|
56
|
+
# @return [ActiveRecord] resource
|
57
|
+
def from_hash(attributes)
|
58
|
+
hydrate! ActiveFacet.deep_copy(attributes)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# @return [Config]
|
64
|
+
def config
|
65
|
+
@config ||= serializer.config
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean]
|
69
|
+
def allowed_field?(field)
|
70
|
+
overrides.blank? || overrides[field.to_sym]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Checks field to see if it is a relation that is valid have Field Sets applied to it
|
74
|
+
# @param expression [Symbol]
|
75
|
+
# @return [Boolean]
|
76
|
+
def is_expression_scopeable?(expression)
|
77
|
+
resource.persisted? && is_active_relation?(expression) && is_relation_scopeable?(expression)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Checks field to see if expression is a relation
|
81
|
+
# @return [Boolean]
|
82
|
+
def is_active_relation?(expression)
|
83
|
+
#TODO -jdc let me know if anyone finds a better way to identify Proxy objects
|
84
|
+
#NOTE:: Proxy Collections use method missing for most actions; .scoped is the only reliable test
|
85
|
+
expression.is_a?(ActiveRecord::Relation) || (expression.is_a?(Array) && expression.respond_to?(:scoped))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Checks expression to determine if filters are enabled
|
89
|
+
# @return [Boolean]
|
90
|
+
def is_relation_scopeable?(expression)
|
91
|
+
filters_enabled
|
92
|
+
end
|
93
|
+
|
94
|
+
#TODO --jdc delete this method and call resource.class above, see what happens
|
95
|
+
#TODO --jdc this is a hack for assets. fix by making this class the primary entry point
|
96
|
+
# rather than serializers and pass in resource class, or better yet, enforce pseudo resource classes
|
97
|
+
# @return [Class]
|
98
|
+
def resource_class
|
99
|
+
resource.is_a?(ActiveRecord::Base) ? resource.class : serializer.resource_class
|
100
|
+
end
|
101
|
+
|
102
|
+
# This method returns a JSON of hash values representing the resource
|
103
|
+
# @return [JSON]
|
104
|
+
def serialize!
|
105
|
+
json = {}.with_indifferent_access
|
106
|
+
config.field_set_itterator(fields) do |scope, nested_scopes|
|
107
|
+
begin
|
108
|
+
json[scope] = get_resource_attribute scope, nested_scopes if allowed_field?(scope)
|
109
|
+
rescue ActiveFacet::Errors::AttributeError => e
|
110
|
+
# Deliberately do nothing. Ignore scopes that do not map to resource methods (or aliases)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
apply_custom_serializers! json
|
114
|
+
end
|
115
|
+
|
116
|
+
# Gets serialized field from the resource
|
117
|
+
# @param field [Symbol]
|
118
|
+
# @param nested_scope [Mixed] Field Set to pass for relations
|
119
|
+
# @return [Mixed]
|
120
|
+
def get_resource_attribute(field, nested_field_set)
|
121
|
+
if config.namespaces.key? field
|
122
|
+
if ns = get_resource_attribute!(config.namespaces[field])
|
123
|
+
ns[serializer.resource_attribute_name(field).to_s]
|
124
|
+
else
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
elsif config.extensions.key?(field)
|
128
|
+
field
|
129
|
+
elsif serializer.is_association?(field)
|
130
|
+
get_association_attribute(field, nested_field_set)
|
131
|
+
else
|
132
|
+
get_resource_attribute!(serializer.resource_attribute_name(field))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Invokes a method on the resource to retrieve the attribute value
|
137
|
+
# @param attribute [Symbol] identifies
|
138
|
+
# @return [Object]
|
139
|
+
def get_resource_attribute!(attribute)
|
140
|
+
raise ActiveFacet::Errors::AttributeError.new("#{resource.class.name}.#{attribute} missing") unless resource.respond_to?(attribute,true)
|
141
|
+
resource.send(attribute)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Retrieves scoped association from cache or record
|
145
|
+
# @param field [Symbol] attribute to get
|
146
|
+
# @return [Array | ActiveRelation] of ActiveRecord
|
147
|
+
def get_association_attribute(field, nested_field_set)
|
148
|
+
association = serializer.resource_attribute_name(field)
|
149
|
+
|
150
|
+
ActiveFacet.document_cache.fetch_association(self, association, opts) do
|
151
|
+
attribute = resource.send(association)
|
152
|
+
attribute = attribute.scope_filters(filters) if is_expression_scopeable?(attribute)
|
153
|
+
ActiveFacet.restore_opts_after(options, ActiveFacet.fields_key, nested_field_set) do
|
154
|
+
attribute.as_json(options)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Modifies json by reference by applying custom serializers to all attributes registered with custom serializers
|
160
|
+
# @param json [JSON] structure
|
161
|
+
# @return [JSON]
|
162
|
+
def apply_custom_serializers!(json)
|
163
|
+
config.serializers.each do |scope, type|
|
164
|
+
scope_s = scope
|
165
|
+
json[scope_s] = ActiveFacet.restore_opts_after(options, ActiveFacet.fields_key, fields) do
|
166
|
+
serializer.get_custom_serializer_class(type, options).serialize(json[scope_s], resource, options)
|
167
|
+
end if json.key? scope_s
|
168
|
+
end
|
169
|
+
|
170
|
+
json
|
171
|
+
end
|
172
|
+
|
173
|
+
# This method returns a ActiveRecord model updated to match a JSON of hash values
|
174
|
+
# @param json [JSON] attributes identical to the values returned by {serialize}
|
175
|
+
# @return [ActiveRecord] resource
|
176
|
+
def hydrate!(json)
|
177
|
+
filter_allowed_keys! json, serializer.exposed_aliases
|
178
|
+
hydrate_scopes! json
|
179
|
+
json.each do |scope, value|
|
180
|
+
set_resource_attribute scope, value
|
181
|
+
end
|
182
|
+
|
183
|
+
resource
|
184
|
+
end
|
185
|
+
|
186
|
+
# Modifies json by reference to remove all attributes from json which aren't exposed
|
187
|
+
# @param json [JSON] structure
|
188
|
+
# @param keys [Array] of attributes
|
189
|
+
# @return [JSON]
|
190
|
+
def filter_allowed_keys!(json, keys)
|
191
|
+
values = json.with_indifferent_access
|
192
|
+
json.replace ( keys.inject({}.with_indifferent_access) { |results, key|
|
193
|
+
results[key] = values[key] if values.key?(key)
|
194
|
+
results
|
195
|
+
} )
|
196
|
+
end
|
197
|
+
|
198
|
+
# Modifies json by reference by applying custom hydration to all fields registered with custom serializers
|
199
|
+
# @param json [JSON] structure
|
200
|
+
# @return [JSON]
|
201
|
+
def hydrate_scopes!(json)
|
202
|
+
config.serializers.each do |scope, type|
|
203
|
+
scope_s = scope
|
204
|
+
json[scope_s] = serializer.get_custom_serializer_class(type, options).hydrate(json[scope], resource, options) if json.key? scope_s
|
205
|
+
end
|
206
|
+
json
|
207
|
+
end
|
208
|
+
|
209
|
+
# Sets the specified attribute on the resource
|
210
|
+
# @param field [Symbol] to set
|
211
|
+
# @param value [Mixed] to set
|
212
|
+
# @return [Mixed] for chaining
|
213
|
+
def set_resource_attribute(field, value)
|
214
|
+
if config.namespaces.key? field
|
215
|
+
resource.send(config.namespaces[field].to_s+"=", {}) unless resource.send(config.namespaces[field]).present?
|
216
|
+
resource.send(config.namespaces[field])[serializer.resource_attribute_name(field,:to).to_s] = value
|
217
|
+
else
|
218
|
+
resource.send("#{serializer.resource_attribute_name(field,:to)}=", value)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|