Class: JSONAPI::Resource

Inherits:
Object
  • Object
show all
Includes:
Callbacks
Defined in:
lib/jsonapi/resource.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

included

Constructor Details

#initialize(model, context) ⇒ Resource

Returns a new instance of Resource.



23
24
25
26
27
28
29
# File 'lib/jsonapi/resource.rb', line 23

def initialize(model, context)
  @model = model
  @context = context
  @reload_needed = false
  @changing = false
  @save_needed = false
end

Class Attribute Details

._allowed_filtersObject

Returns the value of attribute _allowed_filters.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _allowed_filters
  @_allowed_filters
end

._attributesObject

Returns the value of attribute _attributes.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _attributes
  @_attributes
end

._model_hintsObject

Returns the value of attribute _model_hints.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _model_hints
  @_model_hints
end

._paginatorObject

Returns the value of attribute _paginator.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _paginator
  @_paginator
end

._relationshipsObject

Returns the value of attribute _relationships.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _relationships
  @_relationships
end

._typeObject

Returns the value of attribute _type.



453
454
455
# File 'lib/jsonapi/resource.rb', line 453

def _type
  @_type
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



8
9
10
# File 'lib/jsonapi/resource.rb', line 8

def context
  @context
end

Class Method Details

._abstractObject



914
915
916
# File 'lib/jsonapi/resource.rb', line 914

def _abstract
  @abstract
end

._add_relationship(klass, *attrs) ⇒ Object



966
967
968
969
970
971
972
973
974
975
976
977
# File 'lib/jsonapi/resource.rb', line 966

def _add_relationship(klass, *attrs)
  options = attrs.extract_options!
  options[:parent_resource] = self

  attrs.each do |relationship_name|
    check_reserved_relationship_name(relationship_name)
    check_duplicate_relationship_name(relationship_name)

    JSONAPI::RelationshipBuilder.new(klass, _model_class, options)
      .define_relationship_methods(relationship_name.to_sym)
  end
end

._allowed_filter?(filter) ⇒ Boolean

Returns:

  • (Boolean)


939
940
941
# File 'lib/jsonapi/resource.rb', line 939

def _allowed_filter?(filter)
  !_allowed_filters[filter].nil?
end

._as_parent_keyObject



894
895
896
# File 'lib/jsonapi/resource.rb', line 894

def _as_parent_key
  @_as_parent_key ||= "#{_type.to_s.singularize}_id"
end

._attribute_options(attr) ⇒ Object

quasi private class methods



869
870
871
# File 'lib/jsonapi/resource.rb', line 869

def _attribute_options(attr)
  default_attribute_options.merge(@_attributes[attr])
end

._build_joins(associations) ⇒ Object



663
664
665
666
667
668
669
670
671
# File 'lib/jsonapi/resource.rb', line 663

def _build_joins(associations)
  joins = []

  associations.inject do |prev, current|
    joins << "LEFT JOIN #{current.table_name} AS #{current.name}_sorting ON #{current.name}_sorting.id = #{prev.table_name}.#{current.foreign_key}"
    current
  end
  joins.join("\n")
end

._immutableObject



922
923
924
# File 'lib/jsonapi/resource.rb', line 922

def _immutable
  @immutable
end

._lookup_association_chain(model_names) ⇒ Object



650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/jsonapi/resource.rb', line 650

def _lookup_association_chain(model_names)
  associations = []
  model_names.inject do |prev, current|
    association = prev.classify.constantize.reflect_on_all_associations.detect do |assoc|
      assoc.name.to_s.downcase == current.downcase
    end
    associations << association
    association.class_name
  end

  associations
end

._model_classObject



930
931
932
933
934
935
936
937
# File 'lib/jsonapi/resource.rb', line 930

def _model_class
  return nil if _abstract

  return @model if @model
  @model = _model_name.to_s.safe_constantize
  warn "[MODEL NOT FOUND] Model could not be found for #{self.name}. If this a base Resource declare it as abstract." if @model.nil?
  @model
end

._model_nameObject



882
883
884
# File 'lib/jsonapi/resource.rb', line 882

def _model_name
  _abstract ? '' : @_model_name ||= name.demodulize.sub(/Resource$/, '')
end

._primary_keyObject



886
887
888
# File 'lib/jsonapi/resource.rb', line 886

def _primary_key
  @_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
end

._relationship(type) ⇒ Object



877
878
879
880
# File 'lib/jsonapi/resource.rb', line 877

def _relationship(type)
  type = type.to_sym
  @_relationships[type]
end

._resource_name_from_type(type) ⇒ Object



440
441
442
# File 'lib/jsonapi/resource.rb', line 440

def _resource_name_from_type(type)
  "#{type.to_s.underscore.singularize}_resource".camelize
end

._table_nameObject



890
891
892
# File 'lib/jsonapi/resource.rb', line 890

def _table_name
  @_table_name ||= _model_class.respond_to?(:table_name) ? _model_class.table_name : _model_name.tableize
end

._updatable_relationshipsObject



873
874
875
# File 'lib/jsonapi/resource.rb', line 873

def _updatable_relationships
  @_relationships.map { |key, _relationship| key }
end

.abstract(val = true) ⇒ Object



910
911
912
# File 'lib/jsonapi/resource.rb', line 910

def abstract(val = true)
  @abstract = val
end

.apply_filter(records, filter, value, options = {}) ⇒ Object



673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'lib/jsonapi/resource.rb', line 673

def apply_filter(records, filter, value, options = {})
  strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]

  if strategy
    if strategy.is_a?(Symbol) || strategy.is_a?(String)
      send(strategy, records, value, options)
    else
      strategy.call(records, value, options)
    end
  else
    records.where(filter => value)
  end
end

.apply_filters(records, filters, options = {}) ⇒ Object



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/jsonapi/resource.rb', line 687

def apply_filters(records, filters, options = {})
  required_includes = []

  if filters
    filters.each do |filter, value|
      if _relationships.include?(filter)
        if _relationships[filter].belongs_to?
          records = apply_filter(records, _relationships[filter].foreign_key, value, options)
        else
          required_includes.push(filter.to_s)
          records = apply_filter(records, "#{_relationships[filter].table_name}.#{_relationships[filter].primary_key}", value, options)
        end
      else
        records = apply_filter(records, filter, value, options)
      end
    end
  end

  if required_includes.any?
    records = apply_includes(records, options.merge(include_directives: IncludeDirectives.new(self, required_includes, force_eager_load: true)))
  end

  records
end

.apply_includes(records, options = {}) ⇒ Object



614
615
616
617
618
619
620
621
622
# File 'lib/jsonapi/resource.rb', line 614

def apply_includes(records, options = {})
  include_directives = options[:include_directives]
  if include_directives
    model_includes = resolve_relationship_names_to_relations(self, include_directives.model_includes, options)
    records = records.includes(model_includes)
  end

  records
end

.apply_pagination(records, paginator, order_options) ⇒ Object



624
625
626
627
# File 'lib/jsonapi/resource.rb', line 624

def apply_pagination(records, paginator, order_options)
  records = paginator.apply(records, order_options) if paginator
  records
end

.apply_sort(records, order_options, _context = {}) ⇒ Object



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
# File 'lib/jsonapi/resource.rb', line 629

def apply_sort(records, order_options, _context = {})
  if order_options.any?
     order_options.each_pair do |field, direction|
      if field.to_s.include?(".")
        *model_names, column_name = field.split(".")

        associations = _lookup_association_chain([records.model.to_s, *model_names])
        joins_query = _build_joins([records.model, *associations])

        # _sorting is appended to avoid name clashes with manual joins eg. overriden filters
        order_by_query = "#{associations.last.name}_sorting.#{column_name} #{direction}"
        records = records.joins(joins_query).order(order_by_query)
      else
        records = records.order(field => direction)
      end
    end
  end

  records
end

.attribute(attr, options = {}) ⇒ Object



479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/jsonapi/resource.rb', line 479

def attribute(attr, options = {})
  check_reserved_attribute_name(attr)

  if (attr.to_sym == :id) && (options[:format].nil?)
    ActiveSupport::Deprecation.warn('Id without format is no longer supported. Please remove ids from attributes, or specify a format.')
  end

  check_duplicate_attribute_name(attr) if options[:format].nil?

  @_attributes ||= {}
  @_attributes[attr] = options
  define_method attr do
    @model.public_send(options[:delegate] ? options[:delegate].to_sym : attr)
  end unless method_defined?(attr)

  define_method "#{attr}=" do |value|
    @model.public_send("#{options[:delegate] ? options[:delegate].to_sym : attr}=", value)
  end unless method_defined?("#{attr}=")
end

.attributes(*attrs) ⇒ Object

Methods used in defining a resource class



472
473
474
475
476
477
# File 'lib/jsonapi/resource.rb', line 472

def attributes(*attrs)
  options = attrs.extract_options!.dup
  attrs.each do |attr|
    attribute(attr, options)
  end
end

.belongs_to(*attrs) ⇒ Object



522
523
524
525
526
527
528
529
# File 'lib/jsonapi/resource.rb', line 522

def belongs_to(*attrs)
  ActiveSupport::Deprecation.warn "In #{name} you exposed a `has_one` relationship "\
                                  " using the `belongs_to` class method. We think `has_one`" \
                                  " is more appropriate. If you know what you're doing," \
                                  " and don't want to see this warning again, override the" \
                                  " `belongs_to` class method on your resource."
  _add_relationship(Relationship::ToOne, *attrs)
end

.construct_order_options(sort_params) ⇒ Object



955
956
957
958
959
960
961
962
963
964
# File 'lib/jsonapi/resource.rb', line 955

def construct_order_options(sort_params)
  sort_params ||= default_sort

  return {} unless sort_params

  sort_params.each_with_object({}) do |sort, order_hash|
    field = sort[:field].to_s == 'id' ? _primary_key : sort[:field].to_s
    order_hash[field] = sort[:direction]
  end
end

.count_records(records) ⇒ Object

Assumes ActiveRecord’s counting. Override if you need a different counting method



722
723
724
# File 'lib/jsonapi/resource.rb', line 722

def count_records(records)
  records.count(:all)
end

.creatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the creatable keys



581
582
583
# File 'lib/jsonapi/resource.rb', line 581

def creatable_fields(_context = nil)
  _updatable_relationships | _attributes.keys
end

.create(context) ⇒ Object



455
456
457
# File 'lib/jsonapi/resource.rb', line 455

def create(context)
  new(create_model, context)
end

.create_modelObject



459
460
461
# File 'lib/jsonapi/resource.rb', line 459

def create_model
  _model_class.new
end

.default_attribute_optionsObject



499
500
501
# File 'lib/jsonapi/resource.rb', line 499

def default_attribute_options
  { format: :default }
end

.default_sortObject



951
952
953
# File 'lib/jsonapi/resource.rb', line 951

def default_sort
  [{field: 'id', direction: :asc}]
end

.fieldsObject



590
591
592
# File 'lib/jsonapi/resource.rb', line 590

def fields
  _relationships.keys | _attributes.keys
end

.filter(attr, *args) ⇒ Object



552
553
554
# File 'lib/jsonapi/resource.rb', line 552

def filter(attr, *args)
  @_allowed_filters[attr.to_sym] = args.extract_options!
end

.filter_records(filters, options, records = records(options)) ⇒ Object



712
713
714
715
# File 'lib/jsonapi/resource.rb', line 712

def filter_records(filters, options, records = records(options))
  records = apply_filters(records, filters, options)
  apply_includes(records, options)
end

.filters(*attrs) ⇒ Object



548
549
550
# File 'lib/jsonapi/resource.rb', line 548

def filters(*attrs)
  @_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h })
end

.find(filters, options = {}) ⇒ Object

Override this method if you have more complex requirements than this basic find method provides



731
732
733
734
735
736
737
738
739
740
741
742
743
# File 'lib/jsonapi/resource.rb', line 731

def find(filters, options = {})
  context = options[:context]

  records = filter_records(filters, options)

  sort_criteria = options.fetch(:sort_criteria) { [] }
  order_options = construct_order_options(sort_criteria)
  records = sort_records(records, order_options, context)

  records = apply_pagination(records, options[:paginator], order_options)

  resources_for(records, context)
end

.find_by_key(key, options = {}) ⇒ Object



762
763
764
765
766
767
768
769
# File 'lib/jsonapi/resource.rb', line 762

def find_by_key(key, options = {})
  context = options[:context]
  records = records(options)
  records = apply_includes(records, options)
  model = records.where({_primary_key => key}).first
  fail JSONAPI::Exceptions::RecordNotFound.new(key) if model.nil?
  self.resource_for_model(model).new(model, context)
end

.find_by_keys(keys, options = {}) ⇒ Object



752
753
754
755
756
757
758
759
760
# File 'lib/jsonapi/resource.rb', line 752

def find_by_keys(keys, options = {})
  context = options[:context]
  records = records(options)
  records = apply_includes(records, options)
  models = records.where({_primary_key => keys})
  models.collect do |model|
    self.resource_for_model(model).new(model, context)
  end
end

.find_count(filters, options = {}) ⇒ Object



726
727
728
# File 'lib/jsonapi/resource.rb', line 726

def find_count(filters, options = {})
  count_records(filter_records(filters, options))
end

.has_many(*attrs) ⇒ Object



531
532
533
# File 'lib/jsonapi/resource.rb', line 531

def has_many(*attrs)
  _add_relationship(Relationship::ToMany, *attrs)
end

.has_one(*attrs) ⇒ Object



518
519
520
# File 'lib/jsonapi/resource.rb', line 518

def has_one(*attrs)
  _add_relationship(Relationship::ToOne, *attrs)
end

.immutable(val = true) ⇒ Object



918
919
920
# File 'lib/jsonapi/resource.rb', line 918

def immutable(val = true)
  @immutable = val
end

.inherited(subclass) ⇒ Object



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/jsonapi/resource.rb', line 399

def inherited(subclass)
  subclass.abstract(false)
  subclass.immutable(false)
  subclass._attributes = (_attributes || {}).dup
  subclass._model_hints = (_model_hints || {}).dup

  subclass._relationships = {}
  # Add the relationships from the base class to the subclass using the original options
  if _relationships.is_a?(Hash)
    _relationships.each_value do |relationship|
      options = relationship.options.dup
      options[:parent_resource] = subclass
      subclass._add_relationship(relationship.class, relationship.name, options)
    end
  end

  subclass._allowed_filters = (_allowed_filters || Set.new).dup

  type = subclass.name.demodulize.sub(/Resource$/, '').underscore
  subclass._type = type.pluralize.to_sym

  subclass.attribute :id, format: :id

  check_reserved_resource_name(subclass._type, subclass.name)
end

.inject_method_definition(name, body) ⇒ Object

Allows JSONAPI::RelationshipBuilder to access metaprogramming hooks



980
981
982
# File 'lib/jsonapi/resource.rb', line 980

def inject_method_definition(name, body)
  define_method(name, body)
end

.is_filter_relationship?(filter) ⇒ Boolean

Returns:

  • (Boolean)


786
787
788
# File 'lib/jsonapi/resource.rb', line 786

def is_filter_relationship?(filter)
  filter == _type || _relationships.include?(filter)
end

.key_type(key_type) ⇒ Object



814
815
816
# File 'lib/jsonapi/resource.rb', line 814

def key_type(key_type)
  @_resource_key_type = key_type
end

.method_missing(method, *args) ⇒ Object

TODO: remove this after the createable_fields and updateable_fields are phased out :nocov:



562
563
564
565
566
567
568
569
570
571
572
# File 'lib/jsonapi/resource.rb', line 562

def method_missing(method, *args)
  if method.to_s.match /createable_fields/
    ActiveSupport::Deprecation.warn('`createable_fields` is deprecated, please use `creatable_fields` instead')
    creatable_fields(*args)
  elsif method.to_s.match /updateable_fields/
    ActiveSupport::Deprecation.warn('`updateable_fields` is deprecated, please use `updatable_fields` instead')
    updatable_fields(*args)
  else
    super
  end
end

.model_hint(model: _model_name, resource: _type) ⇒ Object



541
542
543
544
545
546
# File 'lib/jsonapi/resource.rb', line 541

def model_hint(model: _model_name, resource: _type)
  model_name = ((model.is_a?(Class)) && (model < ActiveRecord::Base)) ? model.name : model
  resource_type = ((resource.is_a?(Class)) && (resource < JSONAPI::Resource)) ? resource._type : resource.to_s

  _model_hints[model_name.to_s.gsub('::', '/').underscore] = resource_type.to_s
end

.model_name(model, options = {}) ⇒ Object



535
536
537
538
539
# File 'lib/jsonapi/resource.rb', line 535

def model_name(model, options = {})
  @_model_name = model.to_sym

  model_hint(model: @_model_name, resource: self) unless options[:add_model_hint] == false
end

.module_pathObject



943
944
945
946
947
948
949
# File 'lib/jsonapi/resource.rb', line 943

def module_path
  if name == 'JSONAPI::Resource'
    ''
  else
    name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
  end
end

.mutable?Boolean

Returns:

  • (Boolean)


926
927
928
# File 'lib/jsonapi/resource.rb', line 926

def mutable?
  !@immutable
end

.paginator(paginator) ⇒ Object



906
907
908
# File 'lib/jsonapi/resource.rb', line 906

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object



556
557
558
# File 'lib/jsonapi/resource.rb', line 556

def primary_key(key)
  @_primary_key = key.to_sym
end

.records(_options = {}) ⇒ Object

Override this method if you want to customize the relation for finder methods (find, find_by_key)



773
774
775
# File 'lib/jsonapi/resource.rb', line 773

def records(_options = {})
  _model_class.all
end

.register_relationship(name, relationship_object) ⇒ Object



984
985
986
# File 'lib/jsonapi/resource.rb', line 984

def register_relationship(name, relationship_object)
  @_relationships[name] = relationship_object
end

.relationship(*attrs) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'lib/jsonapi/resource.rb', line 503

def relationship(*attrs)
  options = attrs.extract_options!
  klass = case options[:to]
            when :one
              Relationship::ToOne
            when :many
              Relationship::ToMany
            else
              #:nocov:#
              fail ArgumentError.new('to: must be either :one or :many')
              #:nocov:#
          end
  _add_relationship(klass, *attrs, options.except(:to))
end

.resolve_relationship_names_to_relations(resource_klass, model_includes, options = {}) ⇒ Object



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/jsonapi/resource.rb', line 594

def resolve_relationship_names_to_relations(resource_klass, model_includes, options = {})
  case model_includes
    when Array
      return model_includes.map do |value|
        resolve_relationship_names_to_relations(resource_klass, value, options)
      end
    when Hash
      model_includes.keys.each do |key|
        relationship = resource_klass._relationships[key]
        value = model_includes[key]
        model_includes.delete(key)
        model_includes[relationship.relation_name(options)] = resolve_relationship_names_to_relations(relationship.resource_klass, value, options)
      end
      return model_includes
    when Symbol
      relationship = resource_klass._relationships[model_includes]
      return relationship.relation_name(options)
  end
end

.resource_for(type) ⇒ Object



425
426
427
428
429
430
431
432
433
434
# File 'lib/jsonapi/resource.rb', line 425

def resource_for(type)
  type_with_module = type.include?('/') ? type : module_path + type

  resource_name = _resource_name_from_type(type_with_module)
  resource = resource_name.safe_constantize if resource_name
  if resource.nil?
    fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
  end
  resource
end

.resource_for_model(model) ⇒ Object



436
437
438
# File 'lib/jsonapi/resource.rb', line 436

def resource_for_model(model)
  resource_for(resource_type_for(model))
end

.resource_key_typeObject



818
819
820
# File 'lib/jsonapi/resource.rb', line 818

def resource_key_type
  @_resource_key_type ||= JSONAPI.configuration.resource_key_type
end

.resource_type_for(model) ⇒ Object



444
445
446
447
448
449
450
451
# File 'lib/jsonapi/resource.rb', line 444

def resource_type_for(model)
  model_name = model.class.to_s.underscore
  if _model_hints[model_name]
    _model_hints[model_name]
  else
    model_name.rpartition('/').last
  end
end

.resources_for(records, context) ⇒ Object



745
746
747
748
749
750
# File 'lib/jsonapi/resource.rb', line 745

def resources_for(records, context)
  records.collect do |model|
    resource_class = self.resource_for_model(model)
    resource_class.new(model, context)
  end
end

.routing_options(options) ⇒ Object



463
464
465
# File 'lib/jsonapi/resource.rb', line 463

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject



467
468
469
# File 'lib/jsonapi/resource.rb', line 467

def routing_resource_options
  @_routing_resource_options ||= {}
end

.sort_records(records, order_options, context = {}) ⇒ Object



717
718
719
# File 'lib/jsonapi/resource.rb', line 717

def sort_records(records, order_options, context = {})
  apply_sort(records, order_options, context)
end

.sortable_fields(_context = nil) ⇒ Object

Override in your resource to filter the sortable keys



586
587
588
# File 'lib/jsonapi/resource.rb', line 586

def sortable_fields(_context = nil)
  _attributes.keys
end

.updatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the updatable keys



576
577
578
# File 'lib/jsonapi/resource.rb', line 576

def updatable_fields(_context = nil)
  _updatable_relationships | _attributes.keys - [:id]
end

.verify_custom_filter(filter, value, _context = nil) ⇒ Object

Either add a custom :verify labmda or override verify_custom_filter to allow for custom filters



858
859
860
# File 'lib/jsonapi/resource.rb', line 858

def verify_custom_filter(filter, value, _context = nil)
  [filter, value]
end

.verify_filter(filter, raw, context = nil) ⇒ Object



790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'lib/jsonapi/resource.rb', line 790

def verify_filter(filter, raw, context = nil)
  filter_values = []
  if raw.present?
    filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
  end

  strategy = _allowed_filters.fetch(filter, Hash.new)[:verify]

  if strategy
    if strategy.is_a?(Symbol) || strategy.is_a?(String)
      values = send(strategy, filter_values, context)
    else
      values = strategy.call(filter_values, context)
    end
    [filter, values]
  else
    if is_filter_relationship?(filter)
      verify_relationship_filter(filter, filter_values, context)
    else
      verify_custom_filter(filter, filter_values, context)
    end
  end
end

.verify_filters(filters, context = nil) ⇒ Object



777
778
779
780
781
782
783
784
# File 'lib/jsonapi/resource.rb', line 777

def verify_filters(filters, context = nil)
  verified_filters = {}
  filters.each do |filter, raw_value|
    verified_filter = verify_filter(filter, raw_value, context)
    verified_filters[verified_filter[0]] = verified_filter[1]
  end
  verified_filters
end

.verify_key(key, context = nil) ⇒ Object



822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
# File 'lib/jsonapi/resource.rb', line 822

def verify_key(key, context = nil)
  key_type = resource_key_type

  case key_type
  when :integer
    return if key.nil?
    Integer(key)
  when :string
    return if key.nil?
    if key.to_s.include?(',')
      raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
    else
      key
    end
  when :uuid
    return if key.nil?
    if key.to_s.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)
      key
    else
      raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
    end
  else
    key_type.call(key, context)
  end
rescue
  raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
end

.verify_keys(keys, context = nil) ⇒ Object

override to allow for key processing and checking



851
852
853
854
855
# File 'lib/jsonapi/resource.rb', line 851

def verify_keys(keys, context = nil)
  return keys.collect do |key|
    verify_key(key, context)
  end
end

.verify_relationship_filter(filter, raw, _context = nil) ⇒ Object

Either add a custom :verify labmda or override verify_relationship_filter to allow for custom relationship logic, such as uuids, multiple keys or permission checks on keys



864
865
866
# File 'lib/jsonapi/resource.rb', line 864

def verify_relationship_filter(filter, raw, _context = nil)
  [filter, raw]
end

Instance Method Details

#_modelObject



31
32
33
# File 'lib/jsonapi/resource.rb', line 31

def _model
  @model
end

#change(callback) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/jsonapi/resource.rb', line 43

def change(callback)
  completed = false

  if @changing
    run_callbacks callback do
      completed = (yield == :completed)
    end
  else
    run_callbacks is_new? ? :create : :update do
      @changing = true
      run_callbacks callback do
        completed = (yield == :completed)
      end

      completed = (save == :completed) if @save_needed || is_new?
    end
  end

  return completed ? :completed : :accepted
end


70
71
72
73
74
# File 'lib/jsonapi/resource.rb', line 70

def create_to_many_links(relationship_type, relationship_key_values, options = {})
  change :create_to_many_link do
    _create_to_many_links(relationship_type, relationship_key_values, options)
  end
end

Override this to return custom links must return a hash, which will be merged with the default { self: ‘self-url’ } links hash links keys will be not be formatted with the key formatter for the serializer by default. They can however use the serializer’s format_key and format_value methods if desired the _options hash will contain the serializer and the serialization_options



164
165
166
# File 'lib/jsonapi/resource.rb', line 164

def custom_links(_options)
  {}
end

#fetchable_fieldsObject

Override this on a resource instance to override the fetchable keys



113
114
115
# File 'lib/jsonapi/resource.rb', line 113

def fetchable_fields
  self.class.fields
end

#idObject



35
36
37
# File 'lib/jsonapi/resource.rb', line 35

def id
  _model.public_send(self.class._primary_key)
end

#is_new?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/jsonapi/resource.rb', line 39

def is_new?
  id.nil?
end

#meta(_options) ⇒ Object

Override this to return resource level meta data must return a hash, and if the hash is empty the meta section will not be serialized with the resource meta keys will be not be formatted with the key formatter for the serializer by default. They can however use the serializer’s format_key and format_value methods if desired the _options hash will contain the serializer and the serialization_options



155
156
157
# File 'lib/jsonapi/resource.rb', line 155

def meta(_options)
  {}
end

#model_error_messagesObject



123
124
125
# File 'lib/jsonapi/resource.rb', line 123

def model_error_messages
  _model.errors.messages
end

#records_for(relation_name) ⇒ Object

Override this on a resource to customize how the associated records are fetched for a model. Particularly helpful for authorization.



119
120
121
# File 'lib/jsonapi/resource.rb', line 119

def records_for(relation_name)
  _model.public_send relation_name
end

#removeObject



64
65
66
67
68
# File 'lib/jsonapi/resource.rb', line 64

def remove
  run_callbacks :remove do
    _remove
  end
end


94
95
96
97
98
# File 'lib/jsonapi/resource.rb', line 94

def remove_to_many_link(relationship_type, key, options = {})
  change :remove_to_many_link do
    _remove_to_many_link(relationship_type, key, options)
  end
end


100
101
102
103
104
# File 'lib/jsonapi/resource.rb', line 100

def remove_to_one_link(relationship_type, options = {})
  change :remove_to_one_link do
    _remove_to_one_link(relationship_type, options)
  end
end

#replace_fields(field_data) ⇒ Object



106
107
108
109
110
# File 'lib/jsonapi/resource.rb', line 106

def replace_fields(field_data)
  change :replace_fields do
    _replace_fields(field_data)
  end
end


88
89
90
91
92
# File 'lib/jsonapi/resource.rb', line 88

def replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type, options = {})
  change :replace_polymorphic_to_one_link do
    _replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type, options)
  end
end


76
77
78
79
80
# File 'lib/jsonapi/resource.rb', line 76

def replace_to_many_links(relationship_type, relationship_key_values, options = {})
  change :replace_to_many_links do
    _replace_to_many_links(relationship_type, relationship_key_values, options)
  end
end


82
83
84
85
86
# File 'lib/jsonapi/resource.rb', line 82

def replace_to_one_link(relationship_type, relationship_key_value, options = {})
  change :replace_to_one_link do
    _replace_to_one_link(relationship_type, relationship_key_value, options)
  end
end

#validation_error_metadataObject

Add metadata to validation error objects.

Suppose ‘model_error_messages` returned the following error messages hash:

{password: ["too_short", "format"]}

Then to add data to the validation error ‘validation_error_metadata` could return:

{
  password: {
    "too_short": {"minimum_length" => 6},
    "format": {"requirement" => "must contain letters and numbers"}
  }
}

The specified metadata is then be merged into the validation error object.



146
147
148
# File 'lib/jsonapi/resource.rb', line 146

def 
  {}
end