Class: JSONAPI::Resource

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

Constant Summary collapse

DEFAULT_ATTRIBUTE_OPTIONS =
{ format: :default }.freeze
MODULE_PATH_REGEXP =
/::[^:]+\Z/.freeze

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.



26
27
28
29
30
31
32
# File 'lib/jsonapi/resource.rb', line 26

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

Class Attribute Details

._allowed_filtersObject



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

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

._attributesObject

Returns the value of attribute _attributes.



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

def _attributes
  @_attributes
end

._model_hintsObject

Returns the value of attribute _model_hints.



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

def _model_hints
  @_model_hints
end

._paginatorObject



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

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

._relationshipsObject

Returns the value of attribute _relationships.



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

def _relationships
  @_relationships
end

._routedObject

Returns the value of attribute _routed.



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

def _routed
  @_routed
end

._typeObject

Returns the value of attribute _type.



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

def _type
  @_type
end

._warned_missing_routeObject

Returns the value of attribute _warned_missing_route.



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

def _warned_missing_route
  @_warned_missing_route
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



11
12
13
# File 'lib/jsonapi/resource.rb', line 11

def context
  @context
end

Class Method Details

._abstractObject



1058
1059
1060
# File 'lib/jsonapi/resource.rb', line 1058

def _abstract
  @abstract
end

._add_relationship(klass, *attrs) ⇒ Object



1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
# File 'lib/jsonapi/resource.rb', line 1158

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)


1131
1132
1133
# File 'lib/jsonapi/resource.rb', line 1131

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

._as_parent_keyObject



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

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

._attribute_options(attr) ⇒ Object

quasi private class methods



997
998
999
# File 'lib/jsonapi/resource.rb', line 997

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

._build_joins(associations) ⇒ Object



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

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



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

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

._cachingObject



1103
1104
1105
# File 'lib/jsonapi/resource.rb', line 1103

def _caching
  @caching
end


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

def _exclude_links
  @_exclude_links ||= _resolve_exclude_links(JSONAPI.configuration.default_exclude_links)
end

._has_attribute?(attr) ⇒ Boolean

Returns:

  • (Boolean)


1001
1002
1003
# File 'lib/jsonapi/resource.rb', line 1001

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

._immutableObject



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

def _immutable
  @immutable
end

._lookup_association_chain(model_names) ⇒ Object



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

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



1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
# File 'lib/jsonapi/resource.rb', line 1115

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



1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
# File 'lib/jsonapi/resource.rb', line 1014

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



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

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

._relationship(type) ⇒ Object



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

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


1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
# File 'lib/jsonapi/resource.rb', line 1086

def _resolve_exclude_links(exclude)
  case exclude
    when :default, "default"
      @_exclude_links = [:self]
    when :none, "none"
      @_exclude_links = []
    when Array
      @_exclude_links = exclude.collect {|link| link.to_sym}
    else
      fail "Invalid exclude_links"
  end
end

._resource_name_from_type(type) ⇒ Object



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

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

._singleton_optionsObject



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

def _singleton_options
  @_singleton_options ||= {}
end

._table_nameObject



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

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

._updatable_relationshipsObject



1005
1006
1007
# File 'lib/jsonapi/resource.rb', line 1005

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

.abstract(val = true) ⇒ Object



1054
1055
1056
# File 'lib/jsonapi/resource.rb', line 1054

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

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



744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'lib/jsonapi/resource.rb', line 744

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



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

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



783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'lib/jsonapi/resource.rb', line 783

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



685
686
687
688
689
690
691
692
693
# File 'lib/jsonapi/resource.rb', line 685

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) if model_includes.present?
  end

  records
end

.apply_pagination(records, paginator, order_options) ⇒ Object



695
696
697
698
# File 'lib/jsonapi/resource.rb', line 695

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

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



700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
# File 'lib/jsonapi/resource.rb', line 700

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



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'lib/jsonapi/resource.rb', line 538

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



1111
1112
1113
# File 'lib/jsonapi/resource.rb', line 1111

def attribute_caching_context(context)
  nil
end

.attributes(*attrs) ⇒ Object

Methods used in defining a resource class



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

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

.belongs_to(*attrs) ⇒ Object



583
584
585
586
587
588
589
590
# File 'lib/jsonapi/resource.rb', line 583

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



642
643
644
# File 'lib/jsonapi/resource.rb', line 642

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

.caching(val = true) ⇒ Object



1099
1100
1101
# File 'lib/jsonapi/resource.rb', line 1099

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

.caching?Boolean

Returns:

  • (Boolean)


1107
1108
1109
# File 'lib/jsonapi/resource.rb', line 1107

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

.construct_order_options(sort_params) ⇒ Object



1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
# File 'lib/jsonapi/resource.rb', line 1147

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



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

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

.creatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the creatable keys



652
653
654
# File 'lib/jsonapi/resource.rb', line 652

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

.create(context) ⇒ Object



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

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

.create_modelObject



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

def create_model
  _model_class.new
end

.default_attribute_optionsObject



560
561
562
# File 'lib/jsonapi/resource.rb', line 560

def default_attribute_options
  DEFAULT_ATTRIBUTE_OPTIONS
end

.default_sortObject



1143
1144
1145
# File 'lib/jsonapi/resource.rb', line 1143

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

.exclude_link?(link) ⇒ Boolean

Returns:

  • (Boolean)


1082
1083
1084
# File 'lib/jsonapi/resource.rb', line 1082

def exclude_link?(link)
  _exclude_links.include?(link.to_sym)
end


1074
1075
1076
# File 'lib/jsonapi/resource.rb', line 1074

def exclude_links(exclude)
  _resolve_exclude_links(exclude)
end

.fieldsObject



661
662
663
# File 'lib/jsonapi/resource.rb', line 661

def fields
  _relationships.keys | _attributes.keys
end

.filter(attr, *args) ⇒ Object



634
635
636
# File 'lib/jsonapi/resource.rb', line 634

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

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



803
804
805
806
807
# File 'lib/jsonapi/resource.rb', line 803

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



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

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

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



822
823
824
# File 'lib/jsonapi/resource.rb', line 822

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

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



854
855
856
857
858
859
860
# File 'lib/jsonapi/resource.rb', line 854

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



862
863
864
865
866
867
868
869
870
871
872
# File 'lib/jsonapi/resource.rb', line 862

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



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

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



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

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

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



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

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



592
593
594
# File 'lib/jsonapi/resource.rb', line 592

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

.has_one(*attrs) ⇒ Object



579
580
581
# File 'lib/jsonapi/resource.rb', line 579

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

.immutable(val = true) ⇒ Object



1062
1063
1064
# File 'lib/jsonapi/resource.rb', line 1062

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

.inherited(subclass) ⇒ Object



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/jsonapi/resource.rb', line 437

def inherited(subclass)
  subclass.abstract(false)
  subclass.immutable(false)
  subclass.caching(false)
  subclass.singleton(singleton?, (_singleton_options.dup || {}))
  subclass.exclude_links(_exclude_links)
  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)

  subclass._routed = false
  subclass._warned_missing_route = false
end

.inject_method_definition(name, body) ⇒ Object

Allows JSONAPI::RelationshipBuilder to access metaprogramming hooks



1173
1174
1175
# File 'lib/jsonapi/resource.rb', line 1173

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

.is_filter_relationship?(filter) ⇒ Boolean

Returns:

  • (Boolean)


892
893
894
# File 'lib/jsonapi/resource.rb', line 892

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

.key_type(key_type) ⇒ Object



924
925
926
# File 'lib/jsonapi/resource.rb', line 924

def key_type(key_type)
  @_resource_key_type = key_type
end

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



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

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.



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

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



1135
1136
1137
1138
1139
1140
1141
# File 'lib/jsonapi/resource.rb', line 1135

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

.mutable?Boolean

Returns:

  • (Boolean)


1070
1071
1072
# File 'lib/jsonapi/resource.rb', line 1070

def mutable?
  !@immutable
end

.paginator(paginator) ⇒ Object



1050
1051
1052
# File 'lib/jsonapi/resource.rb', line 1050

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object



638
639
640
# File 'lib/jsonapi/resource.rb', line 638

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

.rebuild_relationships(relationships) ⇒ Object



468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/jsonapi/resource.rb', line 468

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)



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

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

.register_relationship(name, relationship_object) ⇒ Object



1177
1178
1179
# File 'lib/jsonapi/resource.rb', line 1177

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

.relationship(*attrs) ⇒ Object



564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/jsonapi/resource.rb', line 564

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



665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'lib/jsonapi/resource.rb', line 665

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



482
483
484
485
486
487
488
489
490
491
492
# File 'lib/jsonapi/resource.rb', line 482

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



494
495
496
# File 'lib/jsonapi/resource.rb', line 494

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

.resource_key_typeObject



928
929
930
# File 'lib/jsonapi/resource.rb', line 928

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

.resource_type_for(model) ⇒ Object



502
503
504
505
506
507
508
509
# File 'lib/jsonapi/resource.rb', line 502

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



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

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



522
523
524
# File 'lib/jsonapi/resource.rb', line 522

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject



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

def routing_resource_options
  @_routing_resource_options ||= {}
end

.singleton(*attrs) ⇒ Object



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

def singleton(*attrs)
  @_singleton = (!!attrs[0] == attrs[0]) ? attrs[0] : true
  @_singleton_options = attrs.extract_options!
end

.singleton?Boolean

Returns:

  • (Boolean)


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

def singleton?
  @_singleton ||= false
end

.singleton_key(context) ⇒ Object

override to all resolution of masked ids to actual ids. Because singleton routes do not specify the id this will be needed to allow lookup of singleton resources. Alternately singleton resources can override ‘verify_key`



935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/jsonapi/resource.rb', line 935

def singleton_key(context)
  if @_singleton_options && @_singleton_options[:singleton_key]
    strategy = @_singleton_options[:singleton_key]
    case strategy
      when Proc
        key = strategy.call(context)
      when Symbol, String
        key = send(strategy, context)
      else
        raise "singleton_key must be a proc or function name"
    end
  end
  key
end

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



809
810
811
# File 'lib/jsonapi/resource.rb', line 809

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



657
658
659
# File 'lib/jsonapi/resource.rb', line 657

def sortable_fields(_context = nil)
  _attributes.keys
end

.updatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the updatable keys



647
648
649
# File 'lib/jsonapi/resource.rb', line 647

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



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

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

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



896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
# File 'lib/jsonapi/resource.rb', line 896

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



880
881
882
883
884
885
886
887
888
889
890
# File 'lib/jsonapi/resource.rb', line 880

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



950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
# File 'lib/jsonapi/resource.rb', line 950

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



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

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



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

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

Instance Method Details

#_modelObject



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

def _model
  @model
end

#cache_idObject



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

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

#change(callback) ⇒ Object



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

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


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

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



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

def custom_links(_options)
  {}
end

#fetchable_fieldsObject

Override this on a resource instance to override the fetchable keys



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

def fetchable_fields
  self.class.fields
end

#idObject



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

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

#is_new?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/jsonapi/resource.rb', line 46

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



162
163
164
# File 'lib/jsonapi/resource.rb', line 162

def meta(_options)
  {}
end

#model_error_messagesObject



130
131
132
# File 'lib/jsonapi/resource.rb', line 130

def model_error_messages
  _model.errors.messages
end

#preloaded_fragmentsObject



175
176
177
178
# File 'lib/jsonapi/resource.rb', line 175

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.



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

def records_for(relation_name)
  _model.public_send relation_name
end

#removeObject



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

def remove
  run_callbacks :remove do
    _remove
  end
end


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

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


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

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



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

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


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

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


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

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


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

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.



153
154
155
# File 'lib/jsonapi/resource.rb', line 153

def 
  {}
end