model-api 0.8.5 → 0.8.6
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 +4 -4
- data/README.md +48 -8
- data/lib/model-api.rb +2 -2
- data/lib/model-api/base_controller.rb +170 -492
- data/lib/model-api/utils.rb +425 -23
- data/model-api.gemspec +1 -1
- metadata +2 -2
data/lib/model-api/utils.rb
CHANGED
@@ -128,7 +128,7 @@ module ModelApi
|
|
128
128
|
end
|
129
129
|
value = value.symbolize_keys if value.is_a?(Hash)
|
130
130
|
return value unless transform_method_or_proc.respond_to?(:call)
|
131
|
-
invoke_callback(transform_method_or_proc, value, opts
|
131
|
+
invoke_callback(transform_method_or_proc, value, opts)
|
132
132
|
end
|
133
133
|
|
134
134
|
def http_status_code(status)
|
@@ -216,7 +216,7 @@ module ModelApi
|
|
216
216
|
end
|
217
217
|
|
218
218
|
def format_value(value, attr_metadata, opts)
|
219
|
-
|
219
|
+
transform_value(value, attr_metadata[:render], opts)
|
220
220
|
rescue Exception => e
|
221
221
|
Rails.logger.warn 'Error encountered formatting API output ' \
|
222
222
|
"(\"#{e.message}\") for value: \"#{value}\"" \
|
@@ -227,28 +227,33 @@ module ModelApi
|
|
227
227
|
def not_found_response_body(opts = {})
|
228
228
|
response =
|
229
229
|
{
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
230
|
+
successful: false,
|
231
|
+
status: :not_found,
|
232
|
+
status_code: http_status_code(:not_found),
|
233
|
+
errors: [{
|
234
|
+
error: opts[:error] || 'No resource found',
|
235
|
+
message: opts[:message] || 'No resource found at the path ' \
|
236
236
|
'provided or matching the criteria specified'
|
237
|
-
|
237
|
+
}]
|
238
238
|
}
|
239
239
|
response.to_json(opts)
|
240
240
|
end
|
241
241
|
|
242
242
|
def invoke_callback(callback, *params)
|
243
243
|
return nil unless callback.respond_to?(:call)
|
244
|
+
callback_param_count = callback.parameters.size
|
245
|
+
if params.size >= callback_param_count + 1 && (last_param = params.last).is_a?(Hash)
|
246
|
+
# Automatically pass duplicate of final hash param (to prevent data corruption)
|
247
|
+
params = params[0..-2] + [last_param.dup]
|
248
|
+
end
|
244
249
|
callback.send(*(([:call] + params)[0..callback.parameters.size]))
|
245
250
|
end
|
246
251
|
|
247
252
|
def common_http_headers
|
248
253
|
{
|
249
|
-
|
250
|
-
|
251
|
-
|
254
|
+
'Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate',
|
255
|
+
'Pragma' => 'no-cache',
|
256
|
+
'Expires' => 'Fri, 01 Jan 1990 00:00:00 GMT'
|
252
257
|
}
|
253
258
|
end
|
254
259
|
|
@@ -265,8 +270,158 @@ module ModelApi
|
|
265
270
|
obj = controller.response_body.first if controller.response_body.is_a?(Array)
|
266
271
|
obj = (JSON.parse(obj) rescue nil) if obj.present?
|
267
272
|
opts = opts.merge(generate_body_only: true)
|
268
|
-
controller.response_body = [ModelApi::Renderer.render(controller,
|
269
|
-
|
273
|
+
controller.response_body = [ModelApi::Renderer.render(controller, ext_value(obj), opts)]
|
274
|
+
end
|
275
|
+
|
276
|
+
def resolve_assoc_obj(parent_obj, assoc, assoc_payload, opts = {})
|
277
|
+
klass = parent_obj.class
|
278
|
+
assoc = klass.reflect_on_association(assoc) if assoc.is_a?(Symbol) || assoc.is_a?(String)
|
279
|
+
fail "Unrecognized association '#{assoc}' on class '#{klass.name}'" if assoc.nil?
|
280
|
+
assoc_class = assoc.class_name.constantize
|
281
|
+
model_metadata = model_metadata(assoc_class)
|
282
|
+
do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload, parent_obj, opts)
|
283
|
+
end
|
284
|
+
|
285
|
+
def update_api_attr(obj, attr, value, opts = {})
|
286
|
+
attr = attr.to_sym
|
287
|
+
attr_metadata = get_attr_metadata(obj, attr, opts)
|
288
|
+
begin
|
289
|
+
value = transform_value(value, attr_metadata[:parse], opts)
|
290
|
+
rescue Exception => e
|
291
|
+
Rails.logger.warn "Error encountered parsing API input for attribute \"#{attr}\" " \
|
292
|
+
"(\"#{e.message}\"): \"#{value.to_s.first(1000)}\" ... using raw value instead."
|
293
|
+
end
|
294
|
+
begin
|
295
|
+
if attr_metadata[:type] == :association && attr_metadata[:parse].blank?
|
296
|
+
attr_metadata = opts[:attr_metadata]
|
297
|
+
assoc = attr_metadata[:association]
|
298
|
+
if assoc.macro == :has_many
|
299
|
+
update_has_many_assoc(obj, attr, value, opts)
|
300
|
+
elsif assoc.macro == :belongs_to
|
301
|
+
update_belongs_to_assoc(obj, attr, value, opts)
|
302
|
+
else
|
303
|
+
add_ignored_field(opts[:ignored_fields], attr, value, attr_metadata)
|
304
|
+
end
|
305
|
+
else
|
306
|
+
set_api_attr(obj, attr, value, opts)
|
307
|
+
end
|
308
|
+
rescue Exception => e
|
309
|
+
handle_api_setter_exception(e, obj, attr_metadata, opts)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def find_by_id_attrs(id_attributes, assoc_class, assoc_payload)
|
314
|
+
return nil unless id_attributes.present?
|
315
|
+
id_attributes.each do |id_attr_set|
|
316
|
+
query = nil
|
317
|
+
id_attr_set.each do |id_attr|
|
318
|
+
unless assoc_payload.include?(id_attr.to_s)
|
319
|
+
query = nil
|
320
|
+
break
|
321
|
+
end
|
322
|
+
query = (query || assoc_class).where(id_attr => assoc_payload[id_attr.to_s])
|
323
|
+
end
|
324
|
+
return query unless query.nil?
|
325
|
+
end
|
326
|
+
nil
|
327
|
+
end
|
328
|
+
|
329
|
+
def add_ignored_field(ignored_fields, attr, value, attr_metadata)
|
330
|
+
return unless ignored_fields.is_a?(Array)
|
331
|
+
attr_metadata ||= {}
|
332
|
+
external_attr = ext_attr(attr, attr_metadata)
|
333
|
+
return unless external_attr.present?
|
334
|
+
ignored_fields << { external_attr => value }
|
335
|
+
end
|
336
|
+
|
337
|
+
def apply_updates(obj, req_obj, operation, opts = {})
|
338
|
+
opts = opts.merge(object: opts[:object] || obj)
|
339
|
+
metadata = filtered_ext_attrs(opts[:api_attr_metadata] ||
|
340
|
+
filtered_attrs(obj, operation, opts), operation, opts)
|
341
|
+
set_context_attrs(obj, opts)
|
342
|
+
req_obj.each do |attr, value|
|
343
|
+
attr = attr.to_sym
|
344
|
+
attr_metadata = metadata[attr]
|
345
|
+
unless attr_metadata.present?
|
346
|
+
add_ignored_field(opts[:ignored_fields], attr, value, attr_metadata)
|
347
|
+
next
|
348
|
+
end
|
349
|
+
update_api_attr(obj, attr, value, opts.merge(attr_metadata: attr_metadata))
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def extract_error_msgs(obj, opts = {})
|
354
|
+
object_errors = []
|
355
|
+
attr_prefix = opts[:attr_prefix] || ''
|
356
|
+
api_metadata = opts[:api_attr_metadata] || api_attrs(obj.class)
|
357
|
+
obj.errors.each do |attr, attr_errors|
|
358
|
+
attr_errors = [attr_errors] unless attr_errors.is_a?(Array)
|
359
|
+
attr_errors.each do |error|
|
360
|
+
attr_metadata = api_metadata[attr] || {}
|
361
|
+
qualified_attr = "#{attr_prefix}#{ext_attr(attr, attr_metadata)}"
|
362
|
+
assoc_errors = nil
|
363
|
+
if attr_metadata[:type] == :association
|
364
|
+
assoc_errors = extract_assoc_error_msgs(obj, attr, opts.merge(
|
365
|
+
attr_metadata: attr_metadata))
|
366
|
+
end
|
367
|
+
if assoc_errors.present?
|
368
|
+
object_errors += assoc_errors
|
369
|
+
else
|
370
|
+
error_hash = {}
|
371
|
+
error_hash[:object] = attr_prefix if attr_prefix.present?
|
372
|
+
error_hash[:attribute] = qualified_attr unless attr == :base
|
373
|
+
object_errors << error_hash.merge(error: error,
|
374
|
+
message: (attr == :base ? error : "#{qualified_attr} #{error}"))
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
object_errors
|
379
|
+
end
|
380
|
+
|
381
|
+
def save_obj(obj, opts = {})
|
382
|
+
operation = opts[:operation] || (obj.new_record? ? :create : :update)
|
383
|
+
model_metadata = opts.delete(:model_metadata) || model_metadata(obj.class)
|
384
|
+
before_validate_callbacks(model_metadata, obj, opts)
|
385
|
+
validate_operation(obj, operation, opts.merge(model_metadata: model_metadata))
|
386
|
+
validate_preserving_existing_errors(obj)
|
387
|
+
new_obj = obj.new_record?
|
388
|
+
before_save_callbacks(model_metadata, obj, new_obj, opts)
|
389
|
+
obj.instance_variable_set(:@readonly, false) if obj.instance_variable_get(:@readonly)
|
390
|
+
successful = obj.save unless obj.errors.present?
|
391
|
+
after_save_callbacks(model_metadata, obj, new_obj, opts) if successful
|
392
|
+
successful
|
393
|
+
end
|
394
|
+
|
395
|
+
def validate_operation(obj, operation, opts = {})
|
396
|
+
klass = find_class(obj, opts)
|
397
|
+
model_metadata = opts[:model_metadata] || model_metadata(klass)
|
398
|
+
return nil unless operation.present?
|
399
|
+
if obj.nil?
|
400
|
+
invoke_callback(model_metadata[:"validate_#{operation}"], opts)
|
401
|
+
else
|
402
|
+
invoke_callback(model_metadata[:"validate_#{operation}"], obj, opts)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def process_collection_includes(collection, opts = {})
|
407
|
+
klass = find_class(collection, opts)
|
408
|
+
metadata = filtered_ext_attrs(klass, opts[:operation] || :index, opts)
|
409
|
+
model_metadata = opts[:model_metadata] || model_metadata(klass)
|
410
|
+
includes = []
|
411
|
+
if (metadata_includes = model_metadata[:collection_includes]).is_a?(Array)
|
412
|
+
includes += metadata_includes.map(&:to_sym)
|
413
|
+
end
|
414
|
+
metadata.each do |_attr, attr_metadata|
|
415
|
+
includes << attr_metadata[:key] if attr_metadata[:type] == :association
|
416
|
+
end
|
417
|
+
includes = includes.compact.uniq
|
418
|
+
collection = collection.includes(includes) if includes.present?
|
419
|
+
collection
|
420
|
+
end
|
421
|
+
|
422
|
+
def find_class(obj, opts = {})
|
423
|
+
return nil if obj.nil?
|
424
|
+
opts[:class] || (obj.respond_to?(:klass) ? obj.klass : obj.class)
|
270
425
|
end
|
271
426
|
|
272
427
|
private
|
@@ -279,8 +434,7 @@ module ModelApi
|
|
279
434
|
test_value = test_value[filter_value]
|
280
435
|
end
|
281
436
|
if test_value.respond_to?(:call)
|
282
|
-
return
|
283
|
-
opts.merge(filter_type => filter_value).freeze)
|
437
|
+
return invoke_callback(test_value, klass, opts.merge(filter_type => filter_value))
|
284
438
|
end
|
285
439
|
filter_value == test_value
|
286
440
|
end
|
@@ -303,7 +457,7 @@ module ModelApi
|
|
303
457
|
|
304
458
|
def include_item?(metadata, obj, operation, opts = {})
|
305
459
|
return false unless metadata.is_a?(Hash)
|
306
|
-
return false unless include_item_meets_admin_criteria?(metadata, obj, opts)
|
460
|
+
return false unless include_item_meets_admin_criteria?(metadata, obj, operation, opts)
|
307
461
|
# Stop here re: filter/sort params, as following checks involve payloads/responses only
|
308
462
|
return eval_bool(obj, metadata[:filter], opts) if operation == :filter
|
309
463
|
return eval_bool(obj, metadata[:sort], opts) if operation == :sort
|
@@ -350,13 +504,11 @@ module ModelApi
|
|
350
504
|
end]
|
351
505
|
end
|
352
506
|
|
353
|
-
def include_item_meets_admin_criteria?(metadata, obj, opts = {})
|
507
|
+
def include_item_meets_admin_criteria?(metadata, obj, operation, opts = {})
|
354
508
|
if eval_bool(obj, metadata[:admin_only], opts)
|
355
|
-
if opts
|
356
|
-
|
357
|
-
|
358
|
-
return false unless opts[:user].try(:admin_api_user?)
|
359
|
-
end
|
509
|
+
return true if opts[:admin]
|
510
|
+
return false unless [:create, :update, :patch].include?(operation)
|
511
|
+
return opts[:admin_user] ? true : false
|
360
512
|
end
|
361
513
|
return false if eval_bool(obj, metadata[:admin_content], opts) && !opts[:admin_content]
|
362
514
|
true
|
@@ -387,6 +539,256 @@ module ModelApi
|
|
387
539
|
end
|
388
540
|
true
|
389
541
|
end
|
542
|
+
|
543
|
+
def update_has_many_assoc(obj, attr, value, opts = {})
|
544
|
+
attr_metadata = opts[:attr_metadata]
|
545
|
+
assoc = attr_metadata[:association]
|
546
|
+
assoc_class = assoc.class_name.constantize
|
547
|
+
model_metadata = model_metadata(assoc_class)
|
548
|
+
value_array = value.to_a rescue nil
|
549
|
+
unless value_array.is_a?(Array)
|
550
|
+
obj.errors.add(attr, 'must be supplied as an array of objects')
|
551
|
+
return
|
552
|
+
end
|
553
|
+
opts = opts.merge(model_metadata: model_metadata)
|
554
|
+
opts[:ignored_fields] = [] if opts.include?(:ignored_fields)
|
555
|
+
assoc_objs = []
|
556
|
+
value_array.each_with_index do |assoc_payload, index|
|
557
|
+
opts[:ignored_fields].clear if opts.include?(:ignored_fields)
|
558
|
+
assoc_objs << update_has_many_assoc_obj(obj, assoc, assoc_class, assoc_payload,
|
559
|
+
opts.merge(model_metadata: model_metadata))
|
560
|
+
if opts[:ignored_fields].present?
|
561
|
+
external_attr = ext_attr(attr, attr_metadata)
|
562
|
+
opts[:ignored_fields] << { "#{external_attr}[#{index}]" => opts[:ignored_fields] }
|
563
|
+
end
|
564
|
+
end
|
565
|
+
set_api_attr(obj, attr, assoc_objs, opts)
|
566
|
+
end
|
567
|
+
|
568
|
+
def update_has_many_assoc_obj(parent_obj, assoc, assoc_class, assoc_payload, opts = {})
|
569
|
+
model_metadata = opts[:model_metadata] || model_metadata(assoc_class)
|
570
|
+
assoc_obj, assoc_oper, assoc_opts = resolve_has_many_assoc_obj(model_metadata, assoc,
|
571
|
+
assoc_class, assoc_payload, parent_obj, opts)
|
572
|
+
if (inverse_assoc = assoc.options[:inverse_of]).present? &&
|
573
|
+
assoc_obj.respond_to?("#{inverse_assoc}=")
|
574
|
+
assoc_obj.send("#{inverse_assoc}=", parent_obj)
|
575
|
+
elsif !parent_obj.new_record? && assoc_obj.respond_to?("#{assoc.foreign_key}=")
|
576
|
+
assoc_obj.send("#{assoc.foreign_key}=", obj.id)
|
577
|
+
end
|
578
|
+
apply_updates(assoc_obj, assoc_payload, assoc_oper, assoc_opts)
|
579
|
+
invoke_callback(model_metadata[:after_initialize], assoc_obj,
|
580
|
+
assoc_opts.merge(operation: assoc_oper))
|
581
|
+
assoc_obj
|
582
|
+
end
|
583
|
+
|
584
|
+
def resolve_has_many_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
|
585
|
+
parent_obj, opts = {})
|
586
|
+
assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
|
587
|
+
parent_obj, opts.merge(auto_create: true))
|
588
|
+
if assoc_obj.new_record?
|
589
|
+
assoc_oper = :create
|
590
|
+
opts[:create_opts] ||= opts.merge(api_attr_metadata: filtered_attrs(
|
591
|
+
assoc_class, :create, opts))
|
592
|
+
assoc_opts = opts[:create_opts]
|
593
|
+
else
|
594
|
+
assoc_oper = :update
|
595
|
+
opts[:update_opts] ||= opts.merge(api_attr_metadata: filtered_attrs(
|
596
|
+
assoc_class, :update, opts))
|
597
|
+
|
598
|
+
assoc_opts = opts[:update_opts]
|
599
|
+
end
|
600
|
+
[assoc_obj, assoc_oper, assoc_opts]
|
601
|
+
end
|
602
|
+
|
603
|
+
def update_belongs_to_assoc(parent_obj, attr, assoc_payload, opts = {})
|
604
|
+
unless assoc_payload.is_a?(Hash)
|
605
|
+
parent_obj.errors.add(attr, 'must be supplied as an object')
|
606
|
+
return
|
607
|
+
end
|
608
|
+
attr_metadata = opts[:attr_metadata]
|
609
|
+
assoc = attr_metadata[:association]
|
610
|
+
assoc_class = assoc.class_name.constantize
|
611
|
+
model_metadata = model_metadata(assoc_class)
|
612
|
+
assoc_obj, assoc_oper, assoc_opts = resolve_belongs_to_assoc_obj(model_metadata, assoc,
|
613
|
+
assoc_class, assoc_payload, parent_obj, opts)
|
614
|
+
apply_updates(assoc_obj, assoc_payload, assoc_oper, assoc_opts)
|
615
|
+
invoke_callback(model_metadata[:after_initialize], assoc_obj,
|
616
|
+
opts.merge(operation: assoc_oper))
|
617
|
+
if assoc_opts[:ignored_fields].present?
|
618
|
+
external_attr = ext_attr(attr, attr_metadata)
|
619
|
+
opts[:ignored_fields] << { external_attr.to_s => assoc_opts[:ignored_fields] }
|
620
|
+
end
|
621
|
+
set_api_attr(parent_obj, attr, assoc_obj, opts)
|
622
|
+
end
|
623
|
+
|
624
|
+
def resolve_belongs_to_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
|
625
|
+
parent_obj, opts = {})
|
626
|
+
assoc_opts = opts[:ignored_fields].is_a?(Array) ? opts.merge(ignored_fields: []) : opts
|
627
|
+
assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
|
628
|
+
parent_obj, opts.merge(auto_create: true))
|
629
|
+
assoc_oper = assoc_obj.new_record? ? :create : :update
|
630
|
+
assoc_opts = assoc_opts.merge(
|
631
|
+
api_attr_metadata: filtered_attrs(assoc_class, assoc_oper, opts))
|
632
|
+
return [assoc_obj, assoc_oper, assoc_opts]
|
633
|
+
end
|
634
|
+
|
635
|
+
def do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload, parent_obj,
|
636
|
+
opts = {})
|
637
|
+
if opts[:resolve].try(:respond_to?, :call)
|
638
|
+
assoc_obj = invoke_callback(opts[:resolve], assoc_payload, opts.merge(
|
639
|
+
parent: parent_obj, association: assoc, association_metadata: model_metadata))
|
640
|
+
else
|
641
|
+
assoc_obj = find_by_id_attrs(model_metadata[:id_attributes], assoc_class, assoc_payload)
|
642
|
+
assoc_obj = assoc_obj.first unless assoc_obj.nil? || assoc_obj.count != 1
|
643
|
+
assoc_obj ||= assoc_class.new if opts[:auto_create]
|
644
|
+
end
|
645
|
+
assoc_obj
|
646
|
+
end
|
647
|
+
|
648
|
+
def set_api_attr(obj, attr, value, opts)
|
649
|
+
attr = attr.to_sym
|
650
|
+
attr_metadata = get_attr_metadata(obj, attr, opts)
|
651
|
+
internal_field = attr_metadata[:key] || attr
|
652
|
+
setter = attr_metadata[:setter] || "#{(internal_field)}="
|
653
|
+
unless obj.respond_to?(setter)
|
654
|
+
Rails.logger.warn "Error encountered assigning API input for attribute \"#{attr}\" " \
|
655
|
+
'(setter not found): skipping.'
|
656
|
+
add_ignored_field(opts[:ignored_fields], attr, value, attr_metadata)
|
657
|
+
return
|
658
|
+
end
|
659
|
+
obj.send(setter, value)
|
660
|
+
end
|
661
|
+
|
662
|
+
def handle_api_setter_exception(e, obj, attr_metadata, opts = {})
|
663
|
+
return unless attr_metadata.is_a?(Hash)
|
664
|
+
on_exception = attr_metadata[:on_exception]
|
665
|
+
fail e unless on_exception.present?
|
666
|
+
on_exception = { Exception => on_exception } unless on_exception.is_a?(Hash)
|
667
|
+
on_exception.each do |klass, handler|
|
668
|
+
klass = klass.to_s.constantize rescue nil unless klass.is_a?(Class)
|
669
|
+
next unless klass.is_a?(Class) && e.is_a?(klass)
|
670
|
+
if handler.respond_to?(:call)
|
671
|
+
invoke_callback(handler, obj, e, opts)
|
672
|
+
elsif handler.present?
|
673
|
+
# Presume handler is an error message in this case
|
674
|
+
obj.errors.add(attr_metadata[:key], handler.to_s)
|
675
|
+
else
|
676
|
+
add_ignored_field(opts[:ignored_fields], nil, opts[:value],
|
677
|
+
attr_metadata)
|
678
|
+
end
|
679
|
+
break
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
def get_attr_metadata(obj, attr, opts)
|
684
|
+
attr_metadata = opts[:attr_metadata]
|
685
|
+
return attr_metadata unless attr_metadata.nil?
|
686
|
+
operation = opts[:operation] || :update
|
687
|
+
metadata = filtered_ext_attrs(opts[:api_attr_metadata] ||
|
688
|
+
filtered_attrs(obj, operation, opts), operation, opts)
|
689
|
+
metadata[attr] || {}
|
690
|
+
end
|
691
|
+
|
692
|
+
def set_context_attrs(obj, opts = {})
|
693
|
+
klass = (obj.class < ActiveRecord::Base ? obj.class : nil)
|
694
|
+
(opts[:context] || {}).each do |key, value|
|
695
|
+
begin
|
696
|
+
setter = "#{key}="
|
697
|
+
next unless obj.respond_to?(setter)
|
698
|
+
if (column = klass.try(:columns_hash).try(:[], key.to_s)).present?
|
699
|
+
case column.type
|
700
|
+
when :integer, :primary_key then
|
701
|
+
obj.send("#{key}=", value.to_i)
|
702
|
+
when :decimal, :float then
|
703
|
+
obj.send("#{key}=", value.to_f)
|
704
|
+
else
|
705
|
+
obj.send(setter, value.to_s)
|
706
|
+
end
|
707
|
+
else
|
708
|
+
obj.send(setter, value.to_s)
|
709
|
+
end
|
710
|
+
rescue Exception => e
|
711
|
+
Rails.logger.warn "Error encountered assigning context parameter #{key} to " \
|
712
|
+
"'#{value}' (skipping): \"#{e.message}\")."
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
# rubocop:disable Metrics/MethodLength
|
718
|
+
def extract_assoc_error_msgs(obj, attr, opts)
|
719
|
+
object_errors = []
|
720
|
+
attr_metadata = opts[:attr_metadata] || {}
|
721
|
+
processed_assoc_objects = {}
|
722
|
+
assoc = attr_metadata[:association]
|
723
|
+
assoc_class = assoc.class_name.constantize
|
724
|
+
external_attr = ext_attr(attr, attr_metadata)
|
725
|
+
attr_metadata_create = attr_metadata_update = nil
|
726
|
+
if assoc.macro == :has_many
|
727
|
+
obj.send(attr).each_with_index do |assoc_obj, index|
|
728
|
+
next if processed_assoc_objects[assoc_obj]
|
729
|
+
processed_assoc_objects[assoc_obj] = true
|
730
|
+
attr_prefix = "#{external_attr}[#{index}]."
|
731
|
+
if assoc_obj.new_record?
|
732
|
+
attr_metadata_create ||= filtered_attrs(assoc_class, :create, opts)
|
733
|
+
object_errors += extract_error_msgs(assoc_obj, opts.merge(
|
734
|
+
attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_create))
|
735
|
+
else
|
736
|
+
attr_metadata_update ||= filtered_attrs(assoc_class, :update, opts)
|
737
|
+
object_errors += extract_error_msgs(assoc_obj, opts.merge(
|
738
|
+
attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_update))
|
739
|
+
end
|
740
|
+
end
|
741
|
+
else
|
742
|
+
assoc_obj = obj.send(attr)
|
743
|
+
return object_errors unless assoc_obj.present? && !processed_assoc_objects[assoc_obj]
|
744
|
+
processed_assoc_objects[assoc_obj] = true
|
745
|
+
attr_prefix = "#{external_attr}->"
|
746
|
+
if assoc_obj.new_record?
|
747
|
+
attr_metadata_create ||= filtered_attrs(assoc_class, :create, opts)
|
748
|
+
object_errors += extract_error_msgs(assoc_obj, opts.merge(
|
749
|
+
attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_create))
|
750
|
+
else
|
751
|
+
attr_metadata_update ||= filtered_attrs(assoc_class, :update, opts)
|
752
|
+
object_errors += extract_error_msgs(assoc_obj, opts.merge(
|
753
|
+
attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_update))
|
754
|
+
end
|
755
|
+
end
|
756
|
+
object_errors
|
757
|
+
end
|
758
|
+
|
759
|
+
# rubocop:enable Metrics/MethodLength
|
760
|
+
|
761
|
+
def before_validate_callbacks(model_metadata, obj, opts)
|
762
|
+
|
763
|
+
invoke_callback(model_metadata[:before_validate], obj, opts)
|
764
|
+
invoke_callback(opts[:before_validate], obj, opts)
|
765
|
+
end
|
766
|
+
|
767
|
+
def before_save_callbacks(model_metadata, obj, new_obj, opts)
|
768
|
+
invoke_callback(model_metadata[:before_create], obj, opts) if new_obj
|
769
|
+
invoke_callback(opts[:before_create], obj, opts) if new_obj
|
770
|
+
invoke_callback(model_metadata[:before_save], obj, opts)
|
771
|
+
invoke_callback(opts[:before_save], obj, opts)
|
772
|
+
end
|
773
|
+
|
774
|
+
def after_save_callbacks(model_metadata, obj, new_obj, opts)
|
775
|
+
invoke_callback(model_metadata[:after_create], obj, opts) if new_obj
|
776
|
+
invoke_callback(opts[:after_create], obj, opts) if new_obj
|
777
|
+
invoke_callback(model_metadata[:after_save], obj, opts)
|
778
|
+
invoke_callback(opts[:after_save], obj, opts)
|
779
|
+
end
|
780
|
+
|
781
|
+
def validate_preserving_existing_errors(obj)
|
782
|
+
if obj.errors.present?
|
783
|
+
errors = obj.errors.messages.dup
|
784
|
+
obj.valid?
|
785
|
+
errors = obj.errors.messages.merge(errors)
|
786
|
+
obj.errors.clear
|
787
|
+
errors.each { |field, error| obj.errors.add(field, error) }
|
788
|
+
else
|
789
|
+
obj.valid?
|
790
|
+
end
|
791
|
+
end
|
390
792
|
end
|
391
793
|
end
|
392
794
|
end
|