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



1003
1004
1005
# File 'lib/jsonapi/resource.rb', line 1003

def _allowed_filters
  defined?(@_allowed_filters) ? @_allowed_filters : { id: {} }
end

._attributesObject

Returns the value of attribute _attributes.



503
504
505
# File 'lib/jsonapi/resource.rb', line 503

def _attributes
  @_attributes
end

._model_hintsObject

Returns the value of attribute _model_hints.



503
504
505
# File 'lib/jsonapi/resource.rb', line 503

def _model_hints
  @_model_hints
end

._paginatorObject



1007
1008
1009
# File 'lib/jsonapi/resource.rb', line 1007

def _paginator
  @_paginator ||= JSONAPI.configuration.default_paginator
end

._relationshipsObject

Returns the value of attribute _relationships.



503
504
505
# File 'lib/jsonapi/resource.rb', line 503

def _relationships
  @_relationships
end

._typeObject

Returns the value of attribute _type.



503
504
505
# File 'lib/jsonapi/resource.rb', line 503

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



1019
1020
1021
# File 'lib/jsonapi/resource.rb', line 1019

def _abstract
  @abstract
end

._add_relationship(klass, *attrs) ⇒ Object



1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
# File 'lib/jsonapi/resource.rb', line 1094

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

  attrs.each do |name|
    relationship_name = name.to_sym
    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)


1067
1068
1069
# File 'lib/jsonapi/resource.rb', line 1067

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

._as_parent_keyObject



999
1000
1001
# File 'lib/jsonapi/resource.rb', line 999

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

._attribute_options(attr) ⇒ Object

quasi private class methods



958
959
960
# File 'lib/jsonapi/resource.rb', line 958

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

._build_joins(associations) ⇒ Object



713
714
715
716
717
718
719
720
721
# File 'lib/jsonapi/resource.rb', line 713

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

._cache_fieldObject



991
992
993
# File 'lib/jsonapi/resource.rb', line 991

def _cache_field
  @_cache_field ||= JSONAPI.configuration.default_resource_cache_field
end

._cachingObject



1039
1040
1041
# File 'lib/jsonapi/resource.rb', line 1039

def _caching
  @caching
end

._has_attribute?(attr) ⇒ Boolean

Returns:

  • (Boolean)


962
963
964
# File 'lib/jsonapi/resource.rb', line 962

def _has_attribute?(attr)
  @_attributes.keys.include?(attr.to_sym)
end

._immutableObject



1027
1028
1029
# File 'lib/jsonapi/resource.rb', line 1027

def _immutable
  @immutable
end

._lookup_association_chain(model_names) ⇒ Object



700
701
702
703
704
705
706
707
708
709
710
711
# File 'lib/jsonapi/resource.rb', line 700

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



1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
# File 'lib/jsonapi/resource.rb', line 1051

def _model_class
  return nil if _abstract

  return @model_class if @model_class

  model_name = _model_name
  return nil if model_name.to_s.blank?

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

  @model_class
end

._model_nameObject



975
976
977
978
979
980
981
982
983
984
985
# File 'lib/jsonapi/resource.rb', line 975

def _model_name
  if _abstract
    return ''
  else
    return @_model_name.to_s if defined?(@_model_name)
    class_name = self.name
    return '' if class_name.nil?
    @_model_name = class_name.demodulize.sub(/Resource$/, '')
    return @_model_name.to_s
  end
end

._primary_keyObject



987
988
989
# File 'lib/jsonapi/resource.rb', line 987

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

._relationship(type) ⇒ Object



970
971
972
973
# File 'lib/jsonapi/resource.rb', line 970

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

._resource_name_from_type(type) ⇒ Object



490
491
492
# File 'lib/jsonapi/resource.rb', line 490

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

._table_nameObject



995
996
997
# File 'lib/jsonapi/resource.rb', line 995

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

._updatable_relationshipsObject



966
967
968
# File 'lib/jsonapi/resource.rb', line 966

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

.abstract(val = true) ⇒ Object



1015
1016
1017
# File 'lib/jsonapi/resource.rb', line 1015

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

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



723
724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/jsonapi/resource.rb', line 723

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



737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
# File 'lib/jsonapi/resource.rb', line 737

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_included_resources_filters(records, options = {}) ⇒ Object



762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'lib/jsonapi/resource.rb', line 762

def apply_included_resources_filters(records, options = {})
  include_directives = options[:include_directives]
  return records unless include_directives
  related_directives = include_directives.include_directives.fetch(:include_related)
  related_directives.reduce(records) do |memo, (relationship_name, config)|
    relationship = _relationship(relationship_name)
    next memo unless relationship && relationship.is_a?(JSONAPI::Relationship::ToMany)
    filtering_resource = relationship.resource_klass

    # Don't try to merge where clauses when relation isn't already being joined to query.
    next memo unless config[:include_in_join]

    filters = config[:include_filters]
    next memo unless filters

    rel_records = filtering_resource.apply_filters(filtering_resource.records(options), filters, options).references(relationship_name)
    memo.merge(rel_records)
  end
end

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



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

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



674
675
676
677
# File 'lib/jsonapi/resource.rb', line 674

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

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



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/jsonapi/resource.rb', line 679

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. overridden 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(attribute_name, options = {}) ⇒ Object



530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/jsonapi/resource.rb', line 530

def attribute(attribute_name, options = {})
  attr = attribute_name.to_sym

  check_reserved_attribute_name(attr)

  if (attr == :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

.attribute_caching_context(context) ⇒ Object



1047
1048
1049
# File 'lib/jsonapi/resource.rb', line 1047

def attribute_caching_context(context)
  nil
end

.attributes(*attrs) ⇒ Object

Methods used in defining a resource class



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

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

.belongs_to(*attrs) ⇒ Object



575
576
577
578
579
580
581
582
# File 'lib/jsonapi/resource.rb', line 575

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

.cache_field(field) ⇒ Object



621
622
623
# File 'lib/jsonapi/resource.rb', line 621

def cache_field(field)
  @_cache_field = field.to_sym
end

.caching(val = true) ⇒ Object



1035
1036
1037
# File 'lib/jsonapi/resource.rb', line 1035

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

.caching?Boolean

Returns:

  • (Boolean)


1043
1044
1045
# File 'lib/jsonapi/resource.rb', line 1043

def caching?
  @caching && !JSONAPI.configuration.resource_cache.nil?
end

.construct_order_options(sort_params) ⇒ Object



1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
# File 'lib/jsonapi/resource.rb', line 1083

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



793
794
795
# File 'lib/jsonapi/resource.rb', line 793

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

.creatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the creatable keys



631
632
633
# File 'lib/jsonapi/resource.rb', line 631

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

.create(context) ⇒ Object



506
507
508
# File 'lib/jsonapi/resource.rb', line 506

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

.create_modelObject



510
511
512
# File 'lib/jsonapi/resource.rb', line 510

def create_model
  _model_class.new
end

.default_attribute_optionsObject



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

def default_attribute_options
  { format: :default }
end

.default_sortObject



1079
1080
1081
# File 'lib/jsonapi/resource.rb', line 1079

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

.fieldsObject



640
641
642
# File 'lib/jsonapi/resource.rb', line 640

def fields
  _relationships.keys | _attributes.keys
end

.filter(attr, *args) ⇒ Object



613
614
615
# File 'lib/jsonapi/resource.rb', line 613

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

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



782
783
784
785
786
# File 'lib/jsonapi/resource.rb', line 782

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

.filters(*attrs) ⇒ Object



609
610
611
# File 'lib/jsonapi/resource.rb', line 609

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

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



801
802
803
# File 'lib/jsonapi/resource.rb', line 801

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

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



833
834
835
836
837
838
839
# File 'lib/jsonapi/resource.rb', line 833

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

.find_by_key_serialized_with_caching(key, serializer, options = {}) ⇒ Object



841
842
843
844
845
846
847
848
849
850
851
# File 'lib/jsonapi/resource.rb', line 841

def find_by_key_serialized_with_caching(key, serializer, options = {})
  if _model_class.respond_to?(:all) && _model_class.respond_to?(:arel_table)
    results = find_serialized_with_caching({_primary_key => key}, serializer, options)
    result = results.first
    fail JSONAPI::Exceptions::RecordNotFound.new(key) if result.nil?
    return result
  else
    resource = find_by_key(key, options)
    return cached_resources_for([resource], serializer, options).first
  end
end

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



812
813
814
815
816
817
818
819
820
# File 'lib/jsonapi/resource.rb', line 812

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



797
798
799
# File 'lib/jsonapi/resource.rb', line 797

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

.find_serialized_with_caching(filters_or_source, serializer, options = {}) ⇒ Object



822
823
824
825
826
827
828
829
830
831
# File 'lib/jsonapi/resource.rb', line 822

def find_serialized_with_caching(filters_or_source, serializer, options = {})
  if filters_or_source.is_a?(ActiveRecord::Relation)
    records = filters_or_source
  elsif _model_class.respond_to?(:all) && _model_class.respond_to?(:arel_table)
    records = find_records(filters_or_source, options.except(:include_directives))
  else
    records = find(filters_or_source, options)
  end
  cached_resources_for(records, serializer, options)
end

.has_many(*attrs) ⇒ Object



584
585
586
# File 'lib/jsonapi/resource.rb', line 584

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

.has_one(*attrs) ⇒ Object



571
572
573
# File 'lib/jsonapi/resource.rb', line 571

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

.immutable(val = true) ⇒ Object



1023
1024
1025
# File 'lib/jsonapi/resource.rb', line 1023

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

.inherited(subclass) ⇒ Object



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/jsonapi/resource.rb', line 434

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

  subclass._model_hints = (_model_hints || {}).dup

  unless _model_name.empty?
    subclass.model_name(_model_name, add_model_hint: (_model_hints && !_model_hints[_model_name].nil?) == true)
  end

  subclass.rebuild_relationships(_relationships || {})

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

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

  unless subclass._attributes[:id]
    subclass.attribute :id, format: :id
  end

  check_reserved_resource_name(subclass._type, subclass.name)
end

.inject_method_definition(name, body) ⇒ Object

Allows JSONAPI::RelationshipBuilder to access metaprogramming hooks



1109
1110
1111
# File 'lib/jsonapi/resource.rb', line 1109

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

.is_filter_relationship?(filter) ⇒ Boolean

Returns:

  • (Boolean)


871
872
873
# File 'lib/jsonapi/resource.rb', line 871

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

.key_type(key_type) ⇒ Object



903
904
905
# File 'lib/jsonapi/resource.rb', line 903

def key_type(key_type)
  @_resource_key_type = key_type
end

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



603
604
605
606
607
# File 'lib/jsonapi/resource.rb', line 603

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

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

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

“‘ CarResource._model_class #=> Vehicle # it should be Car “` so in order to invoke the right class from subclasses, we should call this method to override it.



594
595
596
597
598
599
600
601
# File 'lib/jsonapi/resource.rb', line 594

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

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

  rebuild_relationships(_relationships)
end

.module_pathObject



1071
1072
1073
1074
1075
1076
1077
# File 'lib/jsonapi/resource.rb', line 1071

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

.mutable?Boolean

Returns:

  • (Boolean)


1031
1032
1033
# File 'lib/jsonapi/resource.rb', line 1031

def mutable?
  !@immutable
end

.paginator(paginator) ⇒ Object



1011
1012
1013
# File 'lib/jsonapi/resource.rb', line 1011

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object



617
618
619
# File 'lib/jsonapi/resource.rb', line 617

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

.rebuild_relationships(relationships) ⇒ Object



460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/jsonapi/resource.rb', line 460

def rebuild_relationships(relationships)
  original_relationships = relationships.deep_dup

  @_relationships = {}

  if original_relationships.is_a?(Hash)
    original_relationships.each_value do |relationship|
      options = relationship.options.dup
      options[:parent_resource] = self
      _add_relationship(relationship.class, relationship.name, options)
    end
  end
end

.records(_options = {}) ⇒ Object

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



855
856
857
# File 'lib/jsonapi/resource.rb', line 855

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

.register_relationship(name, relationship_object) ⇒ Object



1113
1114
1115
# File 'lib/jsonapi/resource.rb', line 1113

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

.relationship(*attrs) ⇒ Object



556
557
558
559
560
561
562
563
564
565
566
567
568
569
# File 'lib/jsonapi/resource.rb', line 556

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



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/jsonapi/resource.rb', line 644

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



474
475
476
477
478
479
480
481
482
483
484
# File 'lib/jsonapi/resource.rb', line 474

def resource_for(type)
  type = type.underscore
  type_with_module = type.start_with?(module_path) ? 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



486
487
488
# File 'lib/jsonapi/resource.rb', line 486

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

.resource_key_typeObject



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

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

.resource_type_for(model) ⇒ Object



494
495
496
497
498
499
500
501
# File 'lib/jsonapi/resource.rb', line 494

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



805
806
807
808
809
810
# File 'lib/jsonapi/resource.rb', line 805

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



514
515
516
# File 'lib/jsonapi/resource.rb', line 514

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject



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

def routing_resource_options
  @_routing_resource_options ||= {}
end

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



788
789
790
# File 'lib/jsonapi/resource.rb', line 788

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



636
637
638
# File 'lib/jsonapi/resource.rb', line 636

def sortable_fields(_context = nil)
  _attributes.keys
end

.updatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the updatable keys



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

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



947
948
949
# File 'lib/jsonapi/resource.rb', line 947

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

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



875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
# File 'lib/jsonapi/resource.rb', line 875

def verify_filter(filter, raw, context = nil)
  filter_values = []
  if raw.present?
    begin
      filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
    rescue CSV::MalformedCSVError
      filter_values << raw
    end
  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



859
860
861
862
863
864
865
866
867
868
869
# File 'lib/jsonapi/resource.rb', line 859

def verify_filters(filters, context = nil)
  verified_filters = {}

  return verified_filters if filters.nil?

  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



911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
# File 'lib/jsonapi/resource.rb', line 911

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



940
941
942
943
944
# File 'lib/jsonapi/resource.rb', line 940

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



953
954
955
# File 'lib/jsonapi/resource.rb', line 953

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

#cache_idObject



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

def cache_id
  [id, _model.public_send(self.class._cache_field)]
end

#change(callback) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/jsonapi/resource.rb', line 47

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


74
75
76
77
78
# File 'lib/jsonapi/resource.rb', line 74

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



168
169
170
# File 'lib/jsonapi/resource.rb', line 168

def custom_links(_options)
  {}
end

#fetchable_fieldsObject

Override this on a resource instance to override the fetchable keys



117
118
119
# File 'lib/jsonapi/resource.rb', line 117

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)


43
44
45
# File 'lib/jsonapi/resource.rb', line 43

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



159
160
161
# File 'lib/jsonapi/resource.rb', line 159

def meta(_options)
  {}
end

#model_error_messagesObject



127
128
129
# File 'lib/jsonapi/resource.rb', line 127

def model_error_messages
  _model.errors.messages
end

#preloaded_fragmentsObject



172
173
174
175
# File 'lib/jsonapi/resource.rb', line 172

def preloaded_fragments
  # A hash of hashes
  @preloaded_fragments ||= Hash.new
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.



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

def records_for(relation_name)
  _model.public_send relation_name
end

#removeObject



68
69
70
71
72
# File 'lib/jsonapi/resource.rb', line 68

def remove
  run_callbacks :remove do
    _remove
  end
end


98
99
100
101
102
# File 'lib/jsonapi/resource.rb', line 98

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


104
105
106
107
108
# File 'lib/jsonapi/resource.rb', line 104

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



110
111
112
113
114
# File 'lib/jsonapi/resource.rb', line 110

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


92
93
94
95
96
# File 'lib/jsonapi/resource.rb', line 92

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


80
81
82
83
84
# File 'lib/jsonapi/resource.rb', line 80

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


86
87
88
89
90
# File 'lib/jsonapi/resource.rb', line 86

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.



150
151
152
# File 'lib/jsonapi/resource.rb', line 150

def 
  {}
end