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


22
23
24
25
# File 'lib/jsonapi/resource.rb', line 22

def initialize(model, context)
  @model = model
  @context = context
end

Class Attribute Details

._allowed_filtersObject

Returns the value of attribute _allowed_filters


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _allowed_filters
  @_allowed_filters
end

._attributesObject

Returns the value of attribute _attributes


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _attributes
  @_attributes
end

._model_hintsObject

Returns the value of attribute _model_hints


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _model_hints
  @_model_hints
end

._paginatorObject

Returns the value of attribute _paginator


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _paginator
  @_paginator
end

._relationshipsObject

Returns the value of attribute _relationships


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _relationships
  @_relationships
end

._typeObject

Returns the value of attribute _type


374
375
376
# File 'lib/jsonapi/resource.rb', line 374

def _type
  @_type
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context


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

def context
  @context
end

Class Method Details

._abstractObject


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

def _abstract
  @abstract
end

._add_relationship(klass, *attrs) ⇒ Object


864
865
866
867
868
869
870
871
872
873
874
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
902
903
904
905
906
907
908
909
910
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
938
939
940
941
942
943
944
945
946
947
948
949
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
977
978
979
# File 'lib/jsonapi/resource.rb', line 864

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

  attrs.each do |attr|
    relationship_name = attr.to_sym

    check_reserved_relationship_name(relationship_name)

    # Initialize from an ActiveRecord model's properties
    if _model_class && _model_class.ancestors.collect{|ancestor| ancestor.name}.include?('ActiveRecord::Base')
      model_association = _model_class.reflect_on_association(relationship_name)
      if model_association
        options[:class_name] ||= model_association.class_name
      end
    end

    @_relationships[relationship_name] = relationship = klass.new(relationship_name, options)

    associated_records_method_name = case relationship
                                     when JSONAPI::Relationship::ToOne then "record_for_#{relationship_name}"
                                     when JSONAPI::Relationship::ToMany then "records_for_#{relationship_name}"
                                     end

    foreign_key = relationship.foreign_key

    define_method "#{foreign_key}=" do |value|
      @model.method("#{foreign_key}=").call(value)
    end unless method_defined?("#{foreign_key}=")

    define_method associated_records_method_name do
      relationship = self.class._relationships[relationship_name]
      relation_name = relationship.relation_name(context: @context)
      records_for(relation_name)
    end unless method_defined?(associated_records_method_name)

    if relationship.is_a?(JSONAPI::Relationship::ToOne)
      if relationship.belongs_to?
        define_method foreign_key do
          @model.method(foreign_key).call
        end unless method_defined?(foreign_key)

        define_method relationship_name do |options = {}|
          relationship = self.class._relationships[relationship_name]

          if relationship.polymorphic?
            associated_model = public_send(associated_records_method_name)
            resource_klass = self.class.resource_for_model(associated_model) if associated_model
            return resource_klass.new(associated_model, @context) if resource_klass
          else
            resource_klass = relationship.resource_klass
            if resource_klass
              associated_model = public_send(associated_records_method_name)
              return associated_model ? resource_klass.new(associated_model, @context) : nil
            end
          end
        end unless method_defined?(relationship_name)
      else
        define_method foreign_key do
          relationship = self.class._relationships[relationship_name]

          record = public_send(associated_records_method_name)
          return nil if record.nil?
          record.public_send(relationship.resource_klass._primary_key)
        end unless method_defined?(foreign_key)

        define_method relationship_name do |options = {}|
          relationship = self.class._relationships[relationship_name]

          resource_klass = relationship.resource_klass
          if resource_klass
            associated_model = public_send(associated_records_method_name)
            return associated_model ? resource_klass.new(associated_model, @context) : nil
          end
        end unless method_defined?(relationship_name)
      end
    elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
      define_method foreign_key do
        records = public_send(associated_records_method_name)
        return records.collect do |record|
          record.public_send(relationship.resource_klass._primary_key)
        end
      end unless method_defined?(foreign_key)

      define_method relationship_name do |options = {}|
        relationship = self.class._relationships[relationship_name]

        resource_klass = relationship.resource_klass
        records = public_send(associated_records_method_name)

        filters = options.fetch(:filters, {})
        unless filters.nil? || filters.empty?
          records = resource_klass.apply_filters(records, filters, options)
        end

        sort_criteria =  options.fetch(:sort_criteria, {})
        unless sort_criteria.nil? || sort_criteria.empty?
          order_options = relationship.resource_klass.construct_order_options(sort_criteria)
          records = resource_klass.apply_sort(records, order_options, @context)
        end

        paginator = options[:paginator]
        if paginator
          records = resource_klass.apply_pagination(records, paginator, order_options)
        end

        return records.collect do |record|
          if relationship.polymorphic?
            resource_klass = self.class.resource_for_model(record)
          end
          resource_klass.new(record, @context)
        end
      end unless method_defined?(relationship_name)
    end
  end
end

._allowed_filter?(filter) ⇒ Boolean

Returns:

  • (Boolean)

843
844
845
# File 'lib/jsonapi/resource.rb', line 843

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

._as_parent_keyObject


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

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

._attribute_options(attr) ⇒ Object

quasi private class methods


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

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

._build_joins(associations) ⇒ Object


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

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


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

def _immutable
  @immutable
end

._lookup_association_chain(model_names) ⇒ Object


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

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


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

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


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

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

._primary_keyObject


790
791
792
# File 'lib/jsonapi/resource.rb', line 790

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

._relationship(type) ⇒ Object


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

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

._resource_name_from_type(type) ⇒ Object


361
362
363
# File 'lib/jsonapi/resource.rb', line 361

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

._table_nameObject


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

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

._updatable_relationshipsObject


777
778
779
# File 'lib/jsonapi/resource.rb', line 777

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

.abstract(val = true) ⇒ Object


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

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

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


583
584
585
586
587
588
589
590
591
592
593
594
595
# File 'lib/jsonapi/resource.rb', line 583

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


597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/jsonapi/resource.rb', line 597

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(required_includes)))
  end

  records
end

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


524
525
526
527
528
529
530
531
532
# File 'lib/jsonapi/resource.rb', line 524

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


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

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

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


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 539

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


400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/jsonapi/resource.rb', line 400

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

  @_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


393
394
395
396
397
398
# File 'lib/jsonapi/resource.rb', line 393

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

.construct_order_options(sort_params) ⇒ Object


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

def construct_order_options(sort_params)
  return {} unless sort_params

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

.count_records(records) ⇒ Object

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


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

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

.creatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the creatable keys


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

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

.create(context) ⇒ Object


376
377
378
# File 'lib/jsonapi/resource.rb', line 376

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

.create_modelObject


380
381
382
# File 'lib/jsonapi/resource.rb', line 380

def create_model
  _model_class.new
end

.default_attribute_optionsObject


418
419
420
# File 'lib/jsonapi/resource.rb', line 418

def default_attribute_options
  { format: :default }
end

.fieldsObject


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

def fields
  _relationships.keys | _attributes.keys
end

.filter(attr, *args) ⇒ Object


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

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

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


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

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

.filters(*attrs) ⇒ Object


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

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


641
642
643
644
645
646
647
648
649
650
651
652
653
# File 'lib/jsonapi/resource.rb', line 641

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


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

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


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

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

.has_many(*attrs) ⇒ Object


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

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

.has_one(*attrs) ⇒ Object


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

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

.immutable(val = true) ⇒ Object


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

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

.inherited(subclass) ⇒ Object


320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/jsonapi/resource.rb', line 320

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

.is_filter_relationship?(filter) ⇒ Boolean

Returns:

  • (Boolean)

690
691
692
# File 'lib/jsonapi/resource.rb', line 690

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

.key_type(key_type) ⇒ Object


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

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:


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

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


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

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


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

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


847
848
849
850
851
852
853
# File 'lib/jsonapi/resource.rb', line 847

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

.mutable?Boolean

Returns:

  • (Boolean)

830
831
832
# File 'lib/jsonapi/resource.rb', line 830

def mutable?
  !@immutable
end

.paginator(paginator) ⇒ Object


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

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object


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

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)


677
678
679
# File 'lib/jsonapi/resource.rb', line 677

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

.relationship(*attrs) ⇒ Object


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

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


504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/jsonapi/resource.rb', line 504

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


346
347
348
349
350
351
352
353
354
355
# File 'lib/jsonapi/resource.rb', line 346

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


357
358
359
# File 'lib/jsonapi/resource.rb', line 357

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

.resource_key_typeObject


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

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

.resource_type_for(model) ⇒ Object


365
366
367
368
369
370
371
372
# File 'lib/jsonapi/resource.rb', line 365

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


655
656
657
658
659
660
661
662
663
664
# File 'lib/jsonapi/resource.rb', line 655

def resources_for(records, context)
  resources = []
  resource_classes = {}
  records.each do |model|
    resource_class = resource_classes[model.class] ||= self.resource_for_model(model)
    resources.push resource_class.new(model, context)
  end

  resources
end

.routing_options(options) ⇒ Object


384
385
386
# File 'lib/jsonapi/resource.rb', line 384

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject


388
389
390
# File 'lib/jsonapi/resource.rb', line 388

def routing_resource_options
  @_routing_resource_options ||= {}
end

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


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

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


496
497
498
# File 'lib/jsonapi/resource.rb', line 496

def sortable_fields(_context = nil)
  _attributes.keys
end

.updatable_fields(_context = nil) ⇒ Object

Override in your resource to filter the updatable keys


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

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


762
763
764
# File 'lib/jsonapi/resource.rb', line 762

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

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


694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# File 'lib/jsonapi/resource.rb', line 694

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


681
682
683
684
685
686
687
688
# File 'lib/jsonapi/resource.rb', line 681

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


726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/jsonapi/resource.rb', line 726

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


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

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


768
769
770
# File 'lib/jsonapi/resource.rb', line 768

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

Instance Method Details

#_modelObject


27
28
29
# File 'lib/jsonapi/resource.rb', line 27

def _model
  @model
end

#change(callback) ⇒ Object


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/jsonapi/resource.rb', line 39

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

66
67
68
69
70
# File 'lib/jsonapi/resource.rb', line 66

def create_to_many_links(relationship_type, relationship_key_values)
  change :create_to_many_link do
    _create_to_many_links(relationship_type, relationship_key_values)
  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


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

def custom_links(_options)
  {}
end

#fetchable_fieldsObject

Override this on a resource instance to override the fetchable keys


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

def fetchable_fields
  self.class.fields
end

#idObject


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

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

#is_new?Boolean

Returns:

  • (Boolean)

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

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


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

def meta(_options)
  {}
end

#model_error_messagesObject


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

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.


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

def records_for(relation_name)
  _model.public_send relation_name
end

#removeObject


60
61
62
63
64
# File 'lib/jsonapi/resource.rb', line 60

def remove
  run_callbacks :remove do
    _remove
  end
end

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

def remove_to_many_link(relationship_type, key)
  change :remove_to_many_link do
    _remove_to_many_link(relationship_type, key)
  end
end

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

def remove_to_one_link(relationship_type)
  change :remove_to_one_link do
    _remove_to_one_link(relationship_type)
  end
end

#replace_fields(field_data) ⇒ Object


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

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

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

def replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
  change :replace_polymorphic_to_one_link do
    _replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
  end
end

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

def replace_to_many_links(relationship_type, relationship_key_values)
  change :replace_to_many_links do
    _replace_to_many_links(relationship_type, relationship_key_values)
  end
end

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

def replace_to_one_link(relationship_type, relationship_key_value)
  change :replace_to_one_link do
    _replace_to_one_link(relationship_type, relationship_key_value)
  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.


142
143
144
# File 'lib/jsonapi/resource.rb', line 142

def 
  {}
end