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