model-api 0.8.9 → 0.8.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a051ac2f217b23b0c589bbbc5caea263b07fe1c4
4
- data.tar.gz: 6aba019e731873a4a83b175fef7dca407a2572b7
3
+ metadata.gz: f4236690ba6188e94302365bcb54af72c44d9895
4
+ data.tar.gz: 8feab210c4e863f0d031ec4a4ec65f7fad683fbd
5
5
  SHA512:
6
- metadata.gz: ce211939394ed3727f1ac25113dea914e12b843bff9e80049a7b7c79f3bf7a401b524b950a42dc369addd77fd33fdd2eccb0a14be7e6ba23ba5d442d2dfbfcd7
7
- data.tar.gz: f85df7230f7f16ef074ee85700a9b56a6f719ca3f91d6315fa3fee156db6ce227ba999cbc5aef3fd64d963500c2a4a51ac2d06d6b21d3d1b0b47a58e56bb6a72
6
+ metadata.gz: 6ad9de6daf542958341c2f86f50362a3b554bba9b74e10883a20c6905424f6b3e04b3c63660b48a178a626ce36baac70932f35b58c1533ceb91885346c2eaba6
7
+ data.tar.gz: 07725bfd70fd203020847b0c07cbd68fcdeb50db2917d999484c6a57d649388df07041e1432ad0cfb99ef86c844cc9853ea58573a353e09573fecfedd86faba0
@@ -126,7 +126,7 @@ module ModelApi
126
126
  result_filters = {}
127
127
  metadata.values.each do |attr_metadata|
128
128
  collection = apply_filter_param(attr_metadata, collection,
129
- opts.merge(attr_values: attr_values, result_filters: result_filters, class: klass))
129
+ opts.merge(attr_values: attr_values, result_filters: result_filters, class_name: klass))
130
130
  end
131
131
  assoc_values.each do |assoc, assoc_filter_params|
132
132
  ar_assoc = klass.reflect_on_association(assoc)
@@ -277,9 +277,8 @@ module ModelApi
277
277
  klass = parent_obj.class
278
278
  assoc = klass.reflect_on_association(assoc) if assoc.is_a?(Symbol) || assoc.is_a?(String)
279
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)
280
+ model_metadata = model_metadata(assoc.class_name.constantize)
281
+ do_resolve_assoc_obj(model_metadata, assoc, assoc_payload, parent_obj, nil, opts)
283
282
  end
284
283
 
285
284
  def update_api_attr(obj, attr, value, opts = {})
@@ -296,9 +295,9 @@ module ModelApi
296
295
  attr_metadata = opts[:attr_metadata]
297
296
  assoc = attr_metadata[:association]
298
297
  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)
298
+ update_one_to_many_assoc(obj, attr, value, opts)
299
+ elsif [:belongs_to, :has_one].include?(assoc.macro)
300
+ update_one_to_one_assoc(obj, attr, value, opts)
302
301
  else
303
302
  add_ignored_field(opts[:ignored_fields], attr, value, attr_metadata)
304
303
  end
@@ -310,16 +309,20 @@ module ModelApi
310
309
  end
311
310
  end
312
311
 
313
- def find_by_id_attrs(id_attributes, assoc_class, assoc_payload)
314
- return nil unless id_attributes.present?
312
+ def query_by_id_attrs(id_attributes, assoc, assoc_payload, opts = {})
313
+ return nil unless id_attributes.present? && opts[:api_context].present?
314
+ assoc_class = assoc.class_name.constantize
315
+ attr_metadata = filtered_attrs(assoc_class, opts[:operation] || :update, opts)
315
316
  id_attributes.each do |id_attr_set|
316
317
  query = nil
317
318
  id_attr_set.each do |id_attr|
318
- unless assoc_payload.include?(id_attr.to_s)
319
+ ext_attr = attr_metadata[id_attr].try(:[], :alias) || id_attr
320
+ unless ext_attr.present? && assoc_payload.include?(ext_attr.to_s)
319
321
  query = nil
320
322
  break
321
323
  end
322
- query = (query || assoc_class).where(id_attr => assoc_payload[id_attr.to_s])
324
+ query = (query || opts[:api_context].api_query(assoc_class, opts))
325
+ .where(id_attr => assoc_payload[ext_attr.to_s])
323
326
  end
324
327
  return query unless query.nil?
325
328
  end
@@ -541,11 +544,10 @@ module ModelApi
541
544
  true
542
545
  end
543
546
 
544
- def update_has_many_assoc(obj, attr, value, opts = {})
547
+ def update_one_to_many_assoc(obj, attr, value, opts = {})
545
548
  attr_metadata = opts[:attr_metadata]
546
549
  assoc = attr_metadata[:association]
547
- assoc_class = assoc.class_name.constantize
548
- model_metadata = model_metadata(assoc_class)
550
+ model_metadata = model_metadata(assoc.class_name.constantize)
549
551
  value_array = value.to_a rescue nil
550
552
  unless value_array.is_a?(Array)
551
553
  obj.errors.add(attr, 'must be supplied as an array of objects')
@@ -556,25 +558,37 @@ module ModelApi
556
558
  assoc_objs = []
557
559
  value_array.each_with_index do |assoc_payload, index|
558
560
  opts[:ignored_fields].clear if opts.include?(:ignored_fields)
559
- assoc_objs << update_has_many_assoc_obj(obj, assoc, assoc_class, assoc_payload,
561
+ assoc_obj = update_one_to_many_assoc_obj(obj, assoc, assoc_payload,
560
562
  opts.merge(model_metadata: model_metadata))
563
+ next if assoc_obj.nil?
564
+ assoc_objs << assoc_obj
561
565
  if opts[:ignored_fields].present?
562
566
  external_attr = ext_attr(attr, attr_metadata)
563
567
  opts[:ignored_fields] << { "#{external_attr}[#{index}]" => opts[:ignored_fields] }
564
568
  end
565
569
  end
566
- set_api_attr(obj, attr, assoc_objs, opts)
570
+ assoc_objs = assoc_objs.each do |assoc_obj|
571
+ if assoc_obj.new_record? || assoc_obj != existing_assoc_obj
572
+ set_api_attr(obj, attr, assoc_obj.attributes, opts.merge(setter: ->(v, _opts) {
573
+ obj.send(assoc.name).build(v)
574
+ }))
575
+ else
576
+ assoc_obj.save
577
+ assoc_obj
578
+ end
579
+ end
567
580
  end
568
581
 
569
- def update_has_many_assoc_obj(parent_obj, assoc, assoc_class, assoc_payload, opts = {})
570
- model_metadata = opts[:model_metadata] || model_metadata(assoc_class)
571
- assoc_obj, assoc_oper, assoc_opts = resolve_has_many_assoc_obj(model_metadata, assoc,
572
- assoc_class, assoc_payload, parent_obj, opts)
582
+ def update_one_to_many_assoc_obj(parent_obj, assoc, assoc_payload, opts = {})
583
+ model_metadata = opts[:model_metadata] || model_metadata(assoc.class_name.constantize)
584
+ assoc_obj, assoc_oper, assoc_opts = resolve_one_to_many_assoc_obj(model_metadata, assoc,
585
+ assoc_payload, parent_obj, opts)
586
+ return nil if assoc_obj.nil?
573
587
  if (inverse_assoc = assoc.options[:inverse_of]).present? &&
574
588
  assoc_obj.respond_to?("#{inverse_assoc}=")
575
589
  assoc_obj.send("#{inverse_assoc}=", parent_obj)
576
590
  elsif !parent_obj.new_record? && assoc_obj.respond_to?("#{assoc.foreign_key}=")
577
- assoc_obj.send("#{assoc.foreign_key}=", obj.id)
591
+ assoc_obj.send("#{assoc.foreign_key}=", parent_obj.id)
578
592
  end
579
593
  apply_updates(assoc_obj, assoc_payload, assoc_oper, assoc_opts)
580
594
  invoke_callback(model_metadata[:after_initialize], assoc_obj,
@@ -582,36 +596,38 @@ module ModelApi
582
596
  assoc_obj
583
597
  end
584
598
 
585
- def resolve_has_many_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
599
+ def resolve_one_to_many_assoc_obj(model_metadata, assoc, assoc_payload,
586
600
  parent_obj, opts = {})
587
- assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
588
- parent_obj, opts.merge(auto_create: true))
601
+ assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_payload, parent_obj, nil,
602
+ opts.merge(auto_create: true))
603
+ return [nil, nil, nil] if assoc_obj.nil?
589
604
  if assoc_obj.new_record?
590
605
  assoc_oper = :create
591
606
  opts[:create_opts] ||= opts.merge(api_attr_metadata: filtered_attrs(
592
- assoc_class, :create, opts))
607
+ assoc.class_name.constantize, :create, opts))
593
608
  assoc_opts = opts[:create_opts]
594
609
  else
595
610
  assoc_oper = :update
596
611
  opts[:update_opts] ||= opts.merge(api_attr_metadata: filtered_attrs(
597
- assoc_class, :update, opts))
612
+ assoc.class_name.constantize, :update, opts))
598
613
 
599
614
  assoc_opts = opts[:update_opts]
600
615
  end
601
616
  [assoc_obj, assoc_oper, assoc_opts]
602
617
  end
603
618
 
604
- def update_belongs_to_assoc(parent_obj, attr, assoc_payload, opts = {})
619
+ def update_one_to_one_assoc(parent_obj, attr, assoc_payload, opts = {})
605
620
  unless assoc_payload.is_a?(Hash)
606
621
  parent_obj.errors.add(attr, 'must be supplied as an object')
607
622
  return
608
623
  end
609
624
  attr_metadata = opts[:attr_metadata]
610
625
  assoc = attr_metadata[:association]
611
- assoc_class = assoc.class_name.constantize
612
- model_metadata = model_metadata(assoc_class)
613
- assoc_obj, assoc_oper, assoc_opts = resolve_belongs_to_assoc_obj(model_metadata, assoc,
614
- assoc_class, assoc_payload, parent_obj, opts)
626
+ model_metadata = model_metadata(assoc.class_name.constantize)
627
+ existing_assoc_obj = parent_obj.send(assoc.name) rescue nil
628
+ assoc_obj, assoc_oper, assoc_opts = resolve_one_to_one_assoc_obj(model_metadata, assoc,
629
+ assoc_payload, parent_obj, existing_assoc_obj, opts)
630
+ return if assoc_obj.nil?
615
631
  apply_updates(assoc_obj, assoc_payload, assoc_oper, assoc_opts)
616
632
  invoke_callback(model_metadata[:after_initialize], assoc_obj,
617
633
  opts.merge(operation: assoc_oper))
@@ -619,29 +635,36 @@ module ModelApi
619
635
  external_attr = ext_attr(attr, attr_metadata)
620
636
  opts[:ignored_fields] << { external_attr.to_s => assoc_opts[:ignored_fields] }
621
637
  end
622
- set_api_attr(parent_obj, attr, assoc_obj, opts)
638
+ if assoc_obj.new_record? || assoc_obj != existing_assoc_obj
639
+ set_api_attr(parent_obj, attr, assoc_obj.attributes,
640
+ opts.merge(setter: "build_#{attr_metadata[:key] || attr}"))
641
+ else
642
+ assoc_obj.save
643
+ end
623
644
  end
624
645
 
625
- def resolve_belongs_to_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
626
- parent_obj, opts = {})
646
+ def resolve_one_to_one_assoc_obj(model_metadata, assoc, assoc_payload, parent_obj,
647
+ existing_assoc_obj, opts = {})
627
648
  assoc_opts = opts[:ignored_fields].is_a?(Array) ? opts.merge(ignored_fields: []) : opts
628
- assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload,
629
- parent_obj, opts.merge(auto_create: true))
649
+ assoc_obj = do_resolve_assoc_obj(model_metadata, assoc, assoc_payload, parent_obj,
650
+ existing_assoc_obj, opts.merge(auto_create: true))
651
+ return [nil, nil, nil] if assoc_obj.nil?
630
652
  assoc_oper = assoc_obj.new_record? ? :create : :update
631
653
  assoc_opts = assoc_opts.merge(
632
- api_attr_metadata: filtered_attrs(assoc_class, assoc_oper, opts))
654
+ api_attr_metadata: filtered_attrs(assoc.class_name.constantize, assoc_oper, opts))
633
655
  return [assoc_obj, assoc_oper, assoc_opts]
634
656
  end
635
657
 
636
- def do_resolve_assoc_obj(model_metadata, assoc, assoc_class, assoc_payload, parent_obj,
658
+ def do_resolve_assoc_obj(model_metadata, assoc, assoc_payload, parent_obj, existing_assoc_obj,
637
659
  opts = {})
660
+ return nil unless assoc_payload.present?
638
661
  if opts[:resolve].try(:respond_to?, :call)
639
662
  assoc_obj = invoke_callback(opts[:resolve], assoc_payload, opts.merge(
640
663
  parent: parent_obj, association: assoc, association_metadata: model_metadata))
641
- else
642
- assoc_obj = find_by_id_attrs(model_metadata[:id_attributes], assoc_class, assoc_payload)
643
- assoc_obj = assoc_obj.first unless assoc_obj.nil? || assoc_obj.count != 1
644
- assoc_obj ||= assoc_class.new if opts[:auto_create]
664
+ elsif (assoc_obj = existing_assoc_obj).blank?
665
+ assoc_obj = query_by_id_attrs(model_metadata[:id_attributes], assoc, assoc_payload, opts)
666
+ assoc_obj = (assoc_obj.count != 1 ? nil : assoc_obj.first) unless assoc_obj.nil?
667
+ assoc_obj ||= assoc.class_name.constantize.new if opts[:auto_create]
645
668
  end
646
669
  assoc_obj
647
670
  end
@@ -650,14 +673,17 @@ module ModelApi
650
673
  attr = attr.to_sym
651
674
  attr_metadata = get_attr_metadata(obj, attr, opts)
652
675
  internal_field = attr_metadata[:key] || attr
653
- setter = attr_metadata[:setter] || "#{(internal_field)}="
654
- unless obj.respond_to?(setter)
676
+ setter = opts[:setter] || attr_metadata[:setter] || "#{(internal_field)}="
677
+ if setter.respond_to?(:call)
678
+ ModelApi::Utils.invoke_callback(setter, value, opts.merge(parent: obj, attribute: attr))
679
+ elsif !obj.respond_to?(setter)
655
680
  Rails.logger.warn "Error encountered assigning API input for attribute \"#{attr}\" " \
656
681
  '(setter not found): skipping.'
657
682
  add_ignored_field(opts[:ignored_fields], attr, value, attr_metadata)
658
683
  return
684
+ else
685
+ obj.send(setter, value)
659
686
  end
660
- obj.send(setter, value)
661
687
  end
662
688
 
663
689
  def handle_api_setter_exception(e, obj, attr_metadata, opts = {})
@@ -721,7 +747,6 @@ module ModelApi
721
747
  attr_metadata = opts[:attr_metadata] || {}
722
748
  processed_assoc_objects = {}
723
749
  assoc = attr_metadata[:association]
724
- assoc_class = assoc.class_name.constantize
725
750
  external_attr = ext_attr(attr, attr_metadata)
726
751
  attr_metadata_create = attr_metadata_update = nil
727
752
  if assoc.macro == :has_many
@@ -730,11 +755,11 @@ module ModelApi
730
755
  processed_assoc_objects[assoc_obj] = true
731
756
  attr_prefix = "#{external_attr}[#{index}]."
732
757
  if assoc_obj.new_record?
733
- attr_metadata_create ||= filtered_attrs(assoc_class, :create, opts)
758
+ attr_metadata_create ||= filtered_attrs(assoc.class_name.constantize, :create, opts)
734
759
  object_errors += extract_error_msgs(assoc_obj, opts.merge(
735
760
  attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_create))
736
761
  else
737
- attr_metadata_update ||= filtered_attrs(assoc_class, :update, opts)
762
+ attr_metadata_update ||= filtered_attrs(assoc.class_name.constantize, :update, opts)
738
763
  object_errors += extract_error_msgs(assoc_obj, opts.merge(
739
764
  attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_update))
740
765
  end
@@ -745,11 +770,11 @@ module ModelApi
745
770
  processed_assoc_objects[assoc_obj] = true
746
771
  attr_prefix = "#{external_attr}->"
747
772
  if assoc_obj.new_record?
748
- attr_metadata_create ||= filtered_attrs(assoc_class, :create, opts)
773
+ attr_metadata_create ||= filtered_attrs(assoc.class_name.constantize, :create, opts)
749
774
  object_errors += extract_error_msgs(assoc_obj, opts.merge(
750
775
  attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_create))
751
776
  else
752
- attr_metadata_update ||= filtered_attrs(assoc_class, :update, opts)
777
+ attr_metadata_update ||= filtered_attrs(assoc.class_name.constantize, :update, opts)
753
778
  object_errors += extract_error_msgs(assoc_obj, opts.merge(
754
779
  attr_prefix: attr_prefix, api_attr_metadata: attr_metadata_update))
755
780
  end
@@ -3,7 +3,7 @@ $:.unshift lib unless $:.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'model-api'
6
- s.version = '0.8.9'
6
+ s.version = '0.8.10'
7
7
  s.summary = 'Create easy REST API\'s using metadata inside your ActiveRecord models'
8
8
  s.description = 'Ruby gem allowing Ruby on Rails developers to create REST API’s using ' \
9
9
  'metadata defined inside their ActiveRecord models.'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.9
4
+ version: 0.8.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Mead
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-26 00:00:00.000000000 Z
11
+ date: 2016-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails