ruby_jsonapi 1.0.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/LICENSE.txt +201 -0
- data/README.md +129 -0
- data/lib/extensions/has_one.rb +18 -0
- data/lib/fast_jsonapi/attribute.rb +5 -0
- data/lib/fast_jsonapi/helpers.rb +21 -0
- data/lib/fast_jsonapi/instrumentation/skylight.rb +3 -0
- data/lib/fast_jsonapi/instrumentation.rb +7 -0
- data/lib/fast_jsonapi/link.rb +5 -0
- data/lib/fast_jsonapi/object_serializer.rb +351 -0
- data/lib/fast_jsonapi/railtie.rb +11 -0
- data/lib/fast_jsonapi/relationship.rb +236 -0
- data/lib/fast_jsonapi/scalar.rb +29 -0
- data/lib/fast_jsonapi/serialization_core.rb +202 -0
- data/lib/fast_jsonapi/version.rb +3 -0
- data/lib/fast_jsonapi.rb +12 -0
- data/lib/generators/serializer/USAGE +8 -0
- data/lib/generators/serializer/serializer_generator.rb +19 -0
- data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
- data/lib/jsonapi/serializer/errors.rb +21 -0
- data/lib/jsonapi/serializer/instrumentation.rb +28 -0
- data/lib/jsonapi/serializer/version.rb +5 -0
- data/lib/jsonapi/serializer.rb +12 -0
- metadata +257 -0
@@ -0,0 +1,351 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/time'
|
5
|
+
require 'active_support/concern'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'active_support/core_ext/numeric/time'
|
8
|
+
require 'fast_jsonapi/helpers'
|
9
|
+
require 'fast_jsonapi/attribute'
|
10
|
+
require 'fast_jsonapi/relationship'
|
11
|
+
require 'fast_jsonapi/link'
|
12
|
+
require 'fast_jsonapi/serialization_core'
|
13
|
+
|
14
|
+
module FastJsonapi
|
15
|
+
module ObjectSerializer
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
include SerializationCore
|
18
|
+
|
19
|
+
TRANSFORMS_MAPPING = {
|
20
|
+
camel: :camelize,
|
21
|
+
camel_lower: [:camelize, :lower],
|
22
|
+
dash: :dasherize,
|
23
|
+
underscore: :underscore
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
included do
|
27
|
+
# Set record_type based on the name of the serializer class
|
28
|
+
set_type(reflected_record_type) if reflected_record_type
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(resource, options = {})
|
32
|
+
process_options(options)
|
33
|
+
|
34
|
+
@resource = resource
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializable_hash
|
38
|
+
if self.class.is_collection?(@resource, @is_collection)
|
39
|
+
return hash_for_collection
|
40
|
+
end
|
41
|
+
|
42
|
+
hash_for_one_record
|
43
|
+
end
|
44
|
+
alias to_hash serializable_hash
|
45
|
+
|
46
|
+
def hash_for_one_record
|
47
|
+
serializable_hash = { data: nil }
|
48
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
49
|
+
serializable_hash[:links] = @links if @links.present?
|
50
|
+
|
51
|
+
return serializable_hash unless @resource
|
52
|
+
|
53
|
+
serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @includes, @params)
|
54
|
+
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
|
55
|
+
serializable_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def hash_for_collection
|
59
|
+
serializable_hash = {}
|
60
|
+
|
61
|
+
data = []
|
62
|
+
included = []
|
63
|
+
fieldset = @fieldsets[self.class.record_type.to_sym]
|
64
|
+
@resource.each do |record|
|
65
|
+
data << self.class.record_hash(record, fieldset, @includes, @params)
|
66
|
+
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
|
67
|
+
end
|
68
|
+
|
69
|
+
serializable_hash[:data] = data
|
70
|
+
serializable_hash[:included] = included if @includes.present?
|
71
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
72
|
+
serializable_hash[:links] = @links if @links.present?
|
73
|
+
serializable_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def process_options(options)
|
79
|
+
@fieldsets = deep_symbolize(options[:fields].presence || {})
|
80
|
+
@params = {}
|
81
|
+
|
82
|
+
return if options.blank?
|
83
|
+
|
84
|
+
@known_included_objects = Set.new
|
85
|
+
@meta = options[:meta]
|
86
|
+
@links = options[:links]
|
87
|
+
@is_collection = options[:is_collection]
|
88
|
+
@params = options[:params] || {}
|
89
|
+
raise ArgumentError, '`params` option passed to serializer must be a hash' unless @params.is_a?(Hash)
|
90
|
+
|
91
|
+
if options[:include].present?
|
92
|
+
@includes = options[:include].reject(&:blank?).map(&:to_sym)
|
93
|
+
self.class.validate_includes!(@includes)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def deep_symbolize(collection)
|
98
|
+
if collection.is_a? Hash
|
99
|
+
collection.each_with_object({}) do |(k, v), hsh|
|
100
|
+
hsh[k.to_sym] = deep_symbolize(v)
|
101
|
+
end
|
102
|
+
elsif collection.is_a? Array
|
103
|
+
collection.map { |i| deep_symbolize(i) }
|
104
|
+
else
|
105
|
+
collection.to_sym
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class_methods do
|
110
|
+
# Detects a collection/enumerable
|
111
|
+
#
|
112
|
+
# @return [TrueClass] on a successful detection
|
113
|
+
def is_collection?(resource, force_is_collection = nil)
|
114
|
+
return force_is_collection unless force_is_collection.nil?
|
115
|
+
|
116
|
+
resource.is_a?(Enumerable) && !resource.respond_to?(:each_pair)
|
117
|
+
end
|
118
|
+
|
119
|
+
def inherited(subclass)
|
120
|
+
super(subclass)
|
121
|
+
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
|
122
|
+
subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
|
123
|
+
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
|
124
|
+
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
|
125
|
+
subclass.transform_method = transform_method
|
126
|
+
subclass.data_links = data_links.dup if data_links.present?
|
127
|
+
subclass.cache_store_instance = cache_store_instance
|
128
|
+
subclass.cache_store_options = cache_store_options
|
129
|
+
subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type
|
130
|
+
subclass.meta_to_serialize = meta_to_serialize
|
131
|
+
subclass.record_id = record_id
|
132
|
+
end
|
133
|
+
|
134
|
+
def reflected_record_type
|
135
|
+
return @reflected_record_type if defined?(@reflected_record_type)
|
136
|
+
|
137
|
+
@reflected_record_type ||= (name.split('::').last.chomp('Serializer').underscore.to_sym if name&.end_with?('Serializer'))
|
138
|
+
end
|
139
|
+
|
140
|
+
def set_key_transform(transform_name)
|
141
|
+
self.transform_method = TRANSFORMS_MAPPING[transform_name.to_sym]
|
142
|
+
|
143
|
+
# ensure that the record type is correctly transformed
|
144
|
+
if record_type
|
145
|
+
set_type(record_type)
|
146
|
+
# TODO: Remove dead code
|
147
|
+
elsif reflected_record_type
|
148
|
+
set_type(reflected_record_type)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def run_key_transform(input)
|
153
|
+
if transform_method.present?
|
154
|
+
input.to_s.send(*@transform_method).to_sym
|
155
|
+
else
|
156
|
+
input.to_sym
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def use_hyphen
|
161
|
+
warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead')
|
162
|
+
set_key_transform :dash
|
163
|
+
end
|
164
|
+
|
165
|
+
def set_type(type_name)
|
166
|
+
self.record_type = run_key_transform(type_name)
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_id(id_name = nil, &block)
|
170
|
+
self.record_id = block || id_name
|
171
|
+
end
|
172
|
+
|
173
|
+
def cache_options(cache_options)
|
174
|
+
# FIXME: remove this if block once deprecated cache_options are not supported anymore
|
175
|
+
unless cache_options.key?(:store)
|
176
|
+
# fall back to old, deprecated behaviour because no store was passed.
|
177
|
+
# we assume the user explicitly wants new behaviour if he passed a
|
178
|
+
# store because this is the new syntax.
|
179
|
+
deprecated_cache_options(cache_options)
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
self.cache_store_instance = cache_options[:store]
|
184
|
+
self.cache_store_options = cache_options.except(:store)
|
185
|
+
end
|
186
|
+
|
187
|
+
# FIXME: remove this method once deprecated cache_options are not supported anymore
|
188
|
+
def deprecated_cache_options(cache_options)
|
189
|
+
warn('DEPRECATION WARNING: `store:` is a required cache option, we will default to `Rails.cache` for now. See https://github.com/fast-jsonapi/fast_jsonapi#caching for more information.')
|
190
|
+
|
191
|
+
%i[enabled cache_length].select { |key| cache_options.key?(key) }.each do |key|
|
192
|
+
warn("DEPRECATION WARNING: `#{key}` is a deprecated cache option and will have no effect soon. See https://github.com/fast-jsonapi/fast_jsonapi#caching for more information.")
|
193
|
+
end
|
194
|
+
|
195
|
+
self.cache_store_instance = cache_options[:enabled] ? Rails.cache : nil
|
196
|
+
self.cache_store_options = {
|
197
|
+
expires_in: cache_options[:cache_length] || 5.minutes,
|
198
|
+
race_condition_ttl: cache_options[:race_condition_ttl] || 5.seconds
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
def attributes(*attributes_list, &block)
|
203
|
+
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
204
|
+
options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
|
205
|
+
self.attributes_to_serialize = {} if attributes_to_serialize.nil?
|
206
|
+
|
207
|
+
# to support calling `attribute` with a lambda, e.g `attribute :key, ->(object) { ... }`
|
208
|
+
block = attributes_list.pop if attributes_list.last.is_a?(Proc)
|
209
|
+
|
210
|
+
attributes_list.each do |attr_name|
|
211
|
+
method_name = attr_name
|
212
|
+
key = run_key_transform(method_name)
|
213
|
+
attributes_to_serialize[key] = Attribute.new(
|
214
|
+
key: key,
|
215
|
+
method: block || method_name,
|
216
|
+
options: options
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
alias_method :attribute, :attributes
|
222
|
+
|
223
|
+
def add_relationship(relationship)
|
224
|
+
self.relationships_to_serialize = {} if relationships_to_serialize.nil?
|
225
|
+
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
226
|
+
self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
|
227
|
+
|
228
|
+
# TODO: Remove this undocumented option.
|
229
|
+
# Delegate the caching to the serializer exclusively.
|
230
|
+
if relationship.cached
|
231
|
+
cachable_relationships_to_serialize[relationship.name] = relationship
|
232
|
+
else
|
233
|
+
uncachable_relationships_to_serialize[relationship.name] = relationship
|
234
|
+
end
|
235
|
+
relationships_to_serialize[relationship.name] = relationship
|
236
|
+
end
|
237
|
+
|
238
|
+
def has_many(relationship_name, options = {}, &block)
|
239
|
+
relationship = create_relationship(relationship_name, :has_many, options, block)
|
240
|
+
add_relationship(relationship)
|
241
|
+
end
|
242
|
+
|
243
|
+
def has_one(relationship_name, options = {}, &block)
|
244
|
+
relationship = create_relationship(relationship_name, :has_one, options, block)
|
245
|
+
add_relationship(relationship)
|
246
|
+
end
|
247
|
+
|
248
|
+
def belongs_to(relationship_name, options = {}, &block)
|
249
|
+
relationship = create_relationship(relationship_name, :belongs_to, options, block)
|
250
|
+
add_relationship(relationship)
|
251
|
+
end
|
252
|
+
|
253
|
+
def meta(meta_name = nil, &block)
|
254
|
+
self.meta_to_serialize = block || meta_name
|
255
|
+
end
|
256
|
+
|
257
|
+
def create_relationship(base_key, relationship_type, options, block)
|
258
|
+
name = base_key.to_sym
|
259
|
+
if relationship_type == :has_many
|
260
|
+
base_serialization_key = base_key.to_s.singularize
|
261
|
+
id_postfix = '_ids'
|
262
|
+
else
|
263
|
+
base_serialization_key = base_key
|
264
|
+
id_postfix = '_id'
|
265
|
+
end
|
266
|
+
polymorphic = fetch_polymorphic_option(options)
|
267
|
+
|
268
|
+
Relationship.new(
|
269
|
+
owner: self,
|
270
|
+
key: options[:key] || run_key_transform(base_key),
|
271
|
+
name: name,
|
272
|
+
id_method_name: compute_id_method_name(
|
273
|
+
options[:id_method_name],
|
274
|
+
"#{base_serialization_key}#{id_postfix}".to_sym,
|
275
|
+
polymorphic,
|
276
|
+
options[:serializer],
|
277
|
+
block
|
278
|
+
),
|
279
|
+
record_type: options[:record_type],
|
280
|
+
object_method_name: options[:object_method_name] || name,
|
281
|
+
object_block: block,
|
282
|
+
serializer: options[:serializer],
|
283
|
+
relationship_type: relationship_type,
|
284
|
+
cached: options[:cached],
|
285
|
+
polymorphic: polymorphic,
|
286
|
+
conditional_proc: options[:if],
|
287
|
+
transform_method: @transform_method,
|
288
|
+
meta: options[:meta],
|
289
|
+
links: options[:links],
|
290
|
+
lazy_load_data: options[:lazy_load_data]
|
291
|
+
)
|
292
|
+
end
|
293
|
+
|
294
|
+
def compute_id_method_name(custom_id_method_name, id_method_name_from_relationship, polymorphic, serializer, block)
|
295
|
+
if block.present? || serializer.is_a?(Proc) || polymorphic
|
296
|
+
custom_id_method_name || :id
|
297
|
+
else
|
298
|
+
custom_id_method_name || id_method_name_from_relationship
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def serializer_for(name)
|
303
|
+
namespace = self.name.gsub(/()?\w+Serializer$/, '')
|
304
|
+
serializer_name = "#{name.to_s.demodulize.classify}Serializer"
|
305
|
+
serializer_class_name = namespace + serializer_name
|
306
|
+
begin
|
307
|
+
serializer_class_name.constantize
|
308
|
+
rescue NameError
|
309
|
+
raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
|
310
|
+
"Attempted to find '#{serializer_class_name}'. " \
|
311
|
+
'Consider specifying the serializer directly through options[:serializer].'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def fetch_polymorphic_option(options)
|
316
|
+
option = options[:polymorphic]
|
317
|
+
return false unless option.present?
|
318
|
+
return option if option.respond_to? :keys
|
319
|
+
|
320
|
+
{}
|
321
|
+
end
|
322
|
+
|
323
|
+
# def link(link_name, link_method_name = nil, &block)
|
324
|
+
def link(*params, &block)
|
325
|
+
self.data_links = {} if data_links.nil?
|
326
|
+
|
327
|
+
options = params.last.is_a?(Hash) ? params.pop : {}
|
328
|
+
link_name = params.first
|
329
|
+
link_method_name = params[-1]
|
330
|
+
key = run_key_transform(link_name)
|
331
|
+
|
332
|
+
data_links[key] = Link.new(
|
333
|
+
key: key,
|
334
|
+
method: block || link_method_name,
|
335
|
+
options: options
|
336
|
+
)
|
337
|
+
end
|
338
|
+
|
339
|
+
def validate_includes!(includes)
|
340
|
+
return if includes.blank?
|
341
|
+
|
342
|
+
parse_includes_list(includes).each_key do |include_item|
|
343
|
+
relationship_to_include = relationships_to_serialize[include_item]
|
344
|
+
raise(JSONAPI::Serializer::UnsupportedIncludeError.new(include_item, name)) unless relationship_to_include
|
345
|
+
|
346
|
+
relationship_to_include.static_serializer # called for a side-effect to check for a known serializer class.
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module FastJsonapi
|
2
|
+
class Relationship
|
3
|
+
attr_reader :owner, :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :meta, :lazy_load_data
|
4
|
+
|
5
|
+
def initialize(
|
6
|
+
owner:,
|
7
|
+
key:,
|
8
|
+
name:,
|
9
|
+
id_method_name:,
|
10
|
+
record_type:,
|
11
|
+
object_method_name:,
|
12
|
+
object_block:,
|
13
|
+
serializer:,
|
14
|
+
relationship_type:,
|
15
|
+
polymorphic:,
|
16
|
+
conditional_proc:,
|
17
|
+
transform_method:,
|
18
|
+
links:,
|
19
|
+
meta:,
|
20
|
+
cached: false,
|
21
|
+
lazy_load_data: false
|
22
|
+
)
|
23
|
+
@owner = owner
|
24
|
+
@key = key
|
25
|
+
@name = name
|
26
|
+
@id_method_name = id_method_name
|
27
|
+
@record_type = record_type
|
28
|
+
@object_method_name = object_method_name
|
29
|
+
@object_block = object_block
|
30
|
+
@serializer = serializer
|
31
|
+
@relationship_type = relationship_type
|
32
|
+
@cached = cached
|
33
|
+
@polymorphic = polymorphic
|
34
|
+
@conditional_proc = conditional_proc
|
35
|
+
@transform_method = transform_method
|
36
|
+
@links = links || {}
|
37
|
+
@meta = meta || {}
|
38
|
+
@lazy_load_data = lazy_load_data
|
39
|
+
@record_types_for = {}
|
40
|
+
@serializers_for_name = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize(record, included, serialization_params, output_hash)
|
44
|
+
if include_relationship?(record, serialization_params)
|
45
|
+
empty_case = relationship_type == :has_many ? [] : nil
|
46
|
+
|
47
|
+
output_hash[key] = {}
|
48
|
+
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case unless lazy_load_data && !included
|
49
|
+
|
50
|
+
add_meta_hash(record, serialization_params, output_hash) if meta.present?
|
51
|
+
add_links_hash(record, serialization_params, output_hash) if links.present?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_associated_object(record, params)
|
56
|
+
return FastJsonapi.call_proc(object_block, record, params) unless object_block.nil?
|
57
|
+
|
58
|
+
record.send(object_method_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def include_relationship?(record, serialization_params)
|
62
|
+
if conditional_proc.present?
|
63
|
+
FastJsonapi.call_proc(conditional_proc, record, serialization_params)
|
64
|
+
else
|
65
|
+
true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def serializer_for(record, serialization_params)
|
70
|
+
# TODO: Remove this, dead code...
|
71
|
+
if @static_serializer
|
72
|
+
@static_serializer
|
73
|
+
|
74
|
+
elsif polymorphic
|
75
|
+
name = polymorphic[record.class] if polymorphic.is_a?(Hash)
|
76
|
+
name ||= record.class.name
|
77
|
+
serializer_for_name(name)
|
78
|
+
|
79
|
+
elsif serializer.is_a?(Proc)
|
80
|
+
FastJsonapi.call_proc(serializer, record, serialization_params)
|
81
|
+
|
82
|
+
elsif object_block
|
83
|
+
serializer_for_name(record.class.name)
|
84
|
+
|
85
|
+
else
|
86
|
+
# TODO: Remove this, dead code...
|
87
|
+
raise "Unknown serializer for object #{record.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def static_serializer
|
92
|
+
initialize_static_serializer unless @initialized_static_serializer
|
93
|
+
@static_serializer
|
94
|
+
end
|
95
|
+
|
96
|
+
def static_record_type
|
97
|
+
initialize_static_serializer unless @initialized_static_serializer
|
98
|
+
@static_record_type
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def ids_hash_from_record_and_relationship(record, params = {})
|
104
|
+
initialize_static_serializer unless @initialized_static_serializer
|
105
|
+
|
106
|
+
return ids_hash(fetch_id(record, params), @static_record_type) if @static_record_type
|
107
|
+
|
108
|
+
return unless associated_object = fetch_associated_object(record, params)
|
109
|
+
|
110
|
+
if associated_object.respond_to? :map
|
111
|
+
return associated_object.map do |object|
|
112
|
+
id_hash_from_record object, params
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
id_hash_from_record associated_object, params
|
117
|
+
end
|
118
|
+
|
119
|
+
def id_hash_from_record(record, params)
|
120
|
+
associated_record_type = record_type_for(record, params)
|
121
|
+
id_hash(record.public_send(id_method_name), associated_record_type)
|
122
|
+
end
|
123
|
+
|
124
|
+
def ids_hash(ids, record_type)
|
125
|
+
return ids.map { |id| id_hash(id, record_type) } if ids.respond_to? :map
|
126
|
+
|
127
|
+
id_hash(ids, record_type) # ids variable is just a single id here
|
128
|
+
end
|
129
|
+
|
130
|
+
def id_hash(id, record_type, default_return = false)
|
131
|
+
if id.present?
|
132
|
+
{ id: id.to_s, type: record_type }
|
133
|
+
else
|
134
|
+
default_return ? { id: nil, type: record_type } : nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def fetch_id(record, params)
|
139
|
+
if object_block.present?
|
140
|
+
object = FastJsonapi.call_proc(object_block, record, params)
|
141
|
+
return object.map { |item| item.public_send(id_method_name) } if object.respond_to? :map
|
142
|
+
|
143
|
+
return object.try(id_method_name)
|
144
|
+
end
|
145
|
+
record.public_send(id_method_name)
|
146
|
+
end
|
147
|
+
|
148
|
+
def add_links_hash(record, params, output_hash)
|
149
|
+
output_hash[key][:links] = if links.is_a?(Symbol)
|
150
|
+
record.public_send(links)
|
151
|
+
else
|
152
|
+
links.each_with_object({}) do |(key, method), hash|
|
153
|
+
Link.new(key: key, method: method).serialize(record, params, hash)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_meta_hash(record, params, output_hash)
|
159
|
+
output_hash[key][:meta] = if meta.is_a?(Proc)
|
160
|
+
FastJsonapi.call_proc(meta, record, params)
|
161
|
+
else
|
162
|
+
meta
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def run_key_transform(input)
|
167
|
+
if transform_method.present?
|
168
|
+
input.to_s.send(*transform_method).to_sym
|
169
|
+
else
|
170
|
+
input.to_sym
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def initialize_static_serializer
|
175
|
+
return if @initialized_static_serializer
|
176
|
+
|
177
|
+
@static_serializer = compute_static_serializer
|
178
|
+
@static_record_type = compute_static_record_type
|
179
|
+
@initialized_static_serializer = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def compute_static_serializer
|
183
|
+
if polymorphic
|
184
|
+
# polymorphic without a specific serializer --
|
185
|
+
# the serializer is determined on a record-by-record basis
|
186
|
+
nil
|
187
|
+
|
188
|
+
elsif serializer.is_a?(Symbol) || serializer.is_a?(String)
|
189
|
+
# a serializer was explicitly specified by name -- determine the serializer class
|
190
|
+
serializer_for_name(serializer)
|
191
|
+
|
192
|
+
elsif serializer.is_a?(Proc)
|
193
|
+
# the serializer is a Proc to be executed per object -- not static
|
194
|
+
nil
|
195
|
+
|
196
|
+
elsif serializer
|
197
|
+
# something else was specified, e.g. a specific serializer class -- return it
|
198
|
+
serializer
|
199
|
+
|
200
|
+
elsif object_block
|
201
|
+
# an object block is specified without a specific serializer --
|
202
|
+
# assume the objects might be different and infer the serializer by their class
|
203
|
+
nil
|
204
|
+
|
205
|
+
else
|
206
|
+
# no serializer information was provided -- infer it from the relationship name
|
207
|
+
serializer_name = name.to_s
|
208
|
+
serializer_name = serializer_name.singularize if relationship_type.to_sym == :has_many
|
209
|
+
serializer_for_name(serializer_name)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def serializer_for_name(name)
|
214
|
+
@serializers_for_name[name] ||= owner.serializer_for(name)
|
215
|
+
end
|
216
|
+
|
217
|
+
def record_type_for(record, serialization_params)
|
218
|
+
# if the record type is static, return it
|
219
|
+
return @static_record_type if @static_record_type
|
220
|
+
|
221
|
+
# if not, use the record type of the serializer, and memoize the transformed version
|
222
|
+
serializer = serializer_for(record, serialization_params)
|
223
|
+
@record_types_for[serializer] ||= run_key_transform(serializer.record_type)
|
224
|
+
end
|
225
|
+
|
226
|
+
def compute_static_record_type
|
227
|
+
if polymorphic
|
228
|
+
nil
|
229
|
+
elsif record_type
|
230
|
+
run_key_transform(record_type)
|
231
|
+
elsif @static_serializer
|
232
|
+
run_key_transform(@static_serializer.record_type)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FastJsonapi
|
2
|
+
class Scalar
|
3
|
+
attr_reader :key, :method, :conditional_proc
|
4
|
+
|
5
|
+
def initialize(key:, method:, options: {})
|
6
|
+
@key = key
|
7
|
+
@method = method
|
8
|
+
@conditional_proc = options[:if]
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(record, serialization_params, output_hash)
|
12
|
+
if conditionally_allowed?(record, serialization_params)
|
13
|
+
if method.is_a?(Proc)
|
14
|
+
output_hash[key] = FastJsonapi.call_proc(method, record, serialization_params)
|
15
|
+
else
|
16
|
+
output_hash[key] = record.public_send(method)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def conditionally_allowed?(record, serialization_params)
|
22
|
+
if conditional_proc.present?
|
23
|
+
FastJsonapi.call_proc(conditional_proc, record, serialization_params)
|
24
|
+
else
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|