# File lib/jsonapi/basic_resource.rb, line 674 def primary_key(key) @_primary_key = key.to_sym end
class JSONAPI::BasicResource
Attributes
Public Class Methods
# File lib/jsonapi/basic_resource.rb, line 27 def initialize(model, context) @model = model @context = context @reload_needed = false @changing = false @save_needed = false end
Private Class Methods
# File lib/jsonapi/basic_resource.rb, line 958 def _abstract @abstract end
# File lib/jsonapi/basic_resource.rb, line 1071 def _add_relationship(klass, *attrs) _clear_fields_cache 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) define_relationship_methods(relationship_name.to_sym, klass, options) end end
# File lib/jsonapi/basic_resource.rb, line 1040 def _allowed_filter?(filter) !_allowed_filters[filter].nil? end
# File lib/jsonapi/basic_resource.rb, line 900 def _allowed_filters defined?(@_allowed_filters) ? @_allowed_filters : { id: {} } end
# File lib/jsonapi/basic_resource.rb, line 904 def _allowed_sort @_allowed_sort ||= {} end
# File lib/jsonapi/basic_resource.rb, line 896 def _as_parent_key @_as_parent_key ||= "#{_type.to_s.singularize}_id" end
# File lib/jsonapi/basic_resource.rb, line 838 def _attribute_delegated_name(attr) @_attributes.fetch(attr.to_sym, {}).fetch(:delegate, attr) end
quasi private class methods
# File lib/jsonapi/basic_resource.rb, line 834 def _attribute_options(attr) @_cached_attribute_options[attr] ||= default_attribute_options.merge(@_attributes[attr]) end
# File lib/jsonapi/basic_resource.rb, line 888 def _cache_field @_cache_field || JSONAPI.configuration.default_resource_cache_field end
# File lib/jsonapi/basic_resource.rb, line 1003 def _caching @caching end
# File lib/jsonapi/basic_resource.rb, line 1118 def _clear_cached_attribute_options @_cached_attribute_options = {} end
# File lib/jsonapi/basic_resource.rb, line 1122 def _clear_fields_cache @_fields_cache = nil end
# File lib/jsonapi/basic_resource.rb, line 884 def _default_primary_key @_default_primary_key ||=_model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id end
# File lib/jsonapi/basic_resource.rb, line 991 def _exclude_links @_exclude_links ||= parse_exclude_links(JSONAPI.configuration.default_exclude_links) end
# File lib/jsonapi/basic_resource.rb, line 842 def _has_attribute?(attr) @_attributes.keys.include?(attr.to_sym) end
# File lib/jsonapi/basic_resource.rb, line 1044 def _has_sort?(sorting) !_allowed_sort[sorting.to_sym].nil? end
# File lib/jsonapi/basic_resource.rb, line 966 def _immutable @immutable end
# File lib/jsonapi/basic_resource.rb, line 1024 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
# File lib/jsonapi/basic_resource.rb, line 860 def _model_name if _abstract '' 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$/, '') @_model_name.to_s end end
# File lib/jsonapi/basic_resource.rb, line 908 def _paginator @_paginator || JSONAPI.configuration.default_paginator end
# File lib/jsonapi/basic_resource.rb, line 916 def _polymorphic @_polymorphic end
# File lib/jsonapi/basic_resource.rb, line 872 def _polymorphic_name if !_polymorphic '' else @_polymorphic_name ||= _model_name.to_s.underscore end end
# File lib/jsonapi/basic_resource.rb, line 938 def _polymorphic_resource_klasses @_polymorphic_resource_klasses ||= _polymorphic_types.collect do |type| resource_klass_for(type) end end
# File lib/jsonapi/basic_resource.rb, line 924 def _polymorphic_types @poly_hash ||= {}.tap do |hash| ObjectSpace.each_object do |klass| next unless Module === klass if klass < ActiveRecord::Base klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection| (hash[reflection.options[:as]] ||= []) << klass.name.underscore end end end end @poly_hash[_polymorphic_name.to_sym] end
# File lib/jsonapi/basic_resource.rb, line 880 def _primary_key @_primary_key ||= _default_primary_key end
# File lib/jsonapi/basic_resource.rb, line 854 def _relationship(type) return nil unless type type = type.to_sym @_relationships[type] end
# File lib/jsonapi/basic_resource.rb, line 492 def _resource_name_from_type(type) "#{type.to_s.underscore.singularize}_resource".camelize end
# File lib/jsonapi/basic_resource.rb, line 649 def _singleton_options @_singleton_options ||= {} end
# File lib/jsonapi/basic_resource.rb, line 892 def _table_name @_table_name ||= _model_class.respond_to?(:table_name) ? _model_class.table_name : _model_name.tableize end
# File lib/jsonapi/basic_resource.rb, line 846 def _updatable_attributes _attributes.map { |key, options| key unless options[:readonly] }.compact end
# File lib/jsonapi/basic_resource.rb, line 850 def _updatable_relationships @_relationships.map { |key, relationship| key unless relationship.readonly? }.compact end
# File lib/jsonapi/basic_resource.rb, line 954 def abstract(val = true) @abstract = val end
# File lib/jsonapi/basic_resource.rb, line 532 def attribute(attribute_name, options = {}) _clear_cached_attribute_options _clear_fields_cache 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}=") if options.fetch(:sortable, true) && !_has_sort?(attr) sort attr end end
# File lib/jsonapi/basic_resource.rb, line 1015 def attribute_caching_context(_context) nil end
# File lib/jsonapi/basic_resource.rb, line 561 def attribute_to_model_field(attribute) field_name = if attribute == :_cache_field _cache_field else # Note: this will allow the returning of model attributes without a corresponding # resource attribute, for example a belongs_to id such as `author_id` or bypassing # the delegate. attr = @_attributes[attribute] attr && attr[:delegate] ? attr[:delegate].to_sym : attribute end if Rails::VERSION::MAJOR >= 5 attribute_type = _model_class.attribute_types[field_name.to_s] else attribute_type = _model_class.column_types[field_name.to_s] end { name: field_name, type: attribute_type} end
Methods used in defining a resource class
# File lib/jsonapi/basic_resource.rb, line 525 def attributes(*attrs) options = attrs.extract_options!.dup attrs.each do |attr| attribute(attr, options) end end
# File lib/jsonapi/basic_resource.rb, line 610 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
# File lib/jsonapi/basic_resource.rb, line 678 def cache_field(field) @_cache_field = field.to_sym end
# File lib/jsonapi/basic_resource.rb, line 999 def caching(val = true) @caching = val end
# File lib/jsonapi/basic_resource.rb, line 1007 def caching? if @caching.nil? !JSONAPI.configuration.resource_cache.nil? && JSONAPI.configuration.default_caching else @caching && !JSONAPI.configuration.resource_cache.nil? end end
# File lib/jsonapi/basic_resource.rb, line 753 def call_method_or_proc(strategy, *args) if strategy.is_a?(Symbol) || strategy.is_a?(String) send(strategy, *args) else strategy.call(*args) end end
# File lib/jsonapi/basic_resource.rb, line 579 def cast_to_attribute_type(value, type) if Rails::VERSION::MAJOR >= 5 return type.cast(value) else return type.type_cast_from_database(value) end end
# File lib/jsonapi/basic_resource.rb, line 1155 def check_duplicate_attribute_name(name) if _attributes.include?(name.to_sym) warn "[DUPLICATE ATTRIBUTE] `#{name}` has already been defined in #{_resource_name_from_type(_type)}." end end
# File lib/jsonapi/basic_resource.rb, line 1149 def check_duplicate_relationship_name(name) if _relationships.include?(name.to_sym) warn "[DUPLICATE RELATIONSHIP] `#{name}` has already been defined in #{_resource_name_from_type(_type)}." end end
# File lib/jsonapi/basic_resource.rb, line 1135 def check_reserved_attribute_name(name) # Allow :id since it can be used to specify the format. Since it is a method on the base Resource # an attribute method won't be created for it. if [:type, :_cache_field, :cache_field].include?(name.to_sym) warn "[NAME COLLISION] `#{name}` is a reserved key in #{_resource_name_from_type(_type)}." end end
# File lib/jsonapi/basic_resource.rb, line 1143 def check_reserved_relationship_name(name) if [:id, :ids, :type, :types].include?(name.to_sym) warn "[NAME COLLISION] `#{name}` is a reserved relationship name in #{_resource_name_from_type(_type)}." end end
# File lib/jsonapi/basic_resource.rb, line 1128 def check_reserved_resource_name(type, name) if [:ids, :types, :hrefs, :links].include?(type) warn "[NAME COLLISION] `#{name}` is a reserved resource name." return end end
# File lib/jsonapi/basic_resource.rb, line 1060 def construct_order_options(sort_params) sort_params = default_sort if sort_params.blank? 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
Override in your resource to filter the creatable keys
# File lib/jsonapi/basic_resource.rb, line 688 def creatable_fields(_context = nil) _updatable_relationships | _updatable_attributes end
# File lib/jsonapi/basic_resource.rb, line 508 def create(context) new(create_model, context) end
# File lib/jsonapi/basic_resource.rb, line 512 def create_model _model_class.new end
# File lib/jsonapi/basic_resource.rb, line 587 def default_attribute_options { format: :default } end
# File lib/jsonapi/basic_resource.rb, line 1056 def default_sort [{field: 'id', direction: :asc}] end
# File lib/jsonapi/basic_resource.rb, line 1096 def define_foreign_key_setter(relationship) if relationship.polymorphic? define_on_resource "#{relationship.foreign_key}=" do |v| _model.method("#{relationship.foreign_key}=").call(v[:id]) _model.public_send("#{relationship.polymorphic_type}=", v[:type]) end else define_on_resource "#{relationship.foreign_key}=" do |value| _model.method("#{relationship.foreign_key}=").call(value) end end end
# File lib/jsonapi/basic_resource.rb, line 1109 def define_on_resource(method_name, &block) return if method_defined?(method_name) define_method(method_name, block) end
ResourceBuilder methods
# File lib/jsonapi/basic_resource.rb, line 1087 def define_relationship_methods(relationship_name, relationship_klass, options) relationship = register_relationship( relationship_name, relationship_klass.new(relationship_name, options) ) define_foreign_key_setter(relationship) end
# File lib/jsonapi/basic_resource.rb, line 995 def exclude_link?(link) _exclude_links.include?(link.to_sym) end
# File lib/jsonapi/basic_resource.rb, line 987 def exclude_links(exclude) @_exclude_links = parse_exclude_links(exclude) end
# File lib/jsonapi/basic_resource.rb, line 701 def fields @_fields_cache ||= _relationships.keys | _attributes.keys end
# File lib/jsonapi/basic_resource.rb, line 661 def filter(attr, *args) @_allowed_filters[attr.to_sym] = args.extract_options! end
# File lib/jsonapi/basic_resource.rb, line 657 def filters(*attrs) @_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h }) end
# File lib/jsonapi/basic_resource.rb, line 619 def has_many(*attrs) _add_relationship(Relationship::ToMany, *attrs) end
# File lib/jsonapi/basic_resource.rb, line 606 def has_one(*attrs) _add_relationship(Relationship::ToOne, *attrs) end
Generate a hashcode from the value to be used as part of the cache lookup
# File lib/jsonapi/basic_resource.rb, line 1020 def hash_cache_field(value) value.hash end
# File lib/jsonapi/basic_resource.rb, line 962 def immutable(val = true) @immutable = val end
# File lib/jsonapi/basic_resource.rb, line 421 def inherited(subclass) subclass.abstract(false) subclass.immutable(false) subclass.caching(_caching) subclass.cache_field(_cache_field) if @_cache_field subclass.singleton(singleton?, (_singleton_options.dup || {})) subclass.exclude_links(_exclude_links) subclass.paginator(@_paginator) subclass._attributes = (_attributes || {}).dup subclass.polymorphic(false) subclass.key_type(@_resource_key_type) subclass._model_hints = (_model_hints || {}).dup unless _model_name.empty? || _immutable 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 subclass._allowed_sort = _allowed_sort.dup type = subclass.name.demodulize.sub(/Resource$/, '').underscore subclass._type = type.pluralize.to_sym unless subclass._attributes[:id] subclass.attribute :id, format: :id, readonly: true end check_reserved_resource_name(subclass._type, subclass.name) subclass._routed = false subclass._warned_missing_route = false subclass._clear_cached_attribute_options subclass._clear_fields_cache end
# File lib/jsonapi/basic_resource.rb, line 725 def is_filter_relationship?(filter) filter == _type || _relationships.include?(filter) end
# File lib/jsonapi/basic_resource.rb, line 761 def key_type(key_type) @_resource_key_type = key_type end
# File lib/jsonapi/basic_resource.rb, line 638 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_class is inherited from superclass, and this causes some issues: “` 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.
# File lib/jsonapi/basic_resource.rb, line 629 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
# File lib/jsonapi/basic_resource.rb, line 1048 def module_path if name == 'JSONAPI::Resource' '' else name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : '' end end
# File lib/jsonapi/basic_resource.rb, line 970 def mutable? !@immutable end
# File lib/jsonapi/basic_resource.rb, line 912 def paginator(paginator) @_paginator = paginator end
# File lib/jsonapi/basic_resource.rb, line 974 def parse_exclude_links(exclude) case exclude when :default, "default" [:self] when :none, "none" [] when Array exclude.collect {|link| link.to_sym} else fail "Invalid exclude_links" end end
# File lib/jsonapi/basic_resource.rb, line 920 def polymorphic(polymorphic = true) @_polymorphic = polymorphic end
# File lib/jsonapi/basic_resource.rb, line 461 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 options[:inverse_relationship] = relationship.inverse_relationship _add_relationship(relationship.class, relationship.name, options) end end end
# File lib/jsonapi/basic_resource.rb, line 1114 def register_relationship(name, relationship_object) @_relationships[name] = relationship_object end
# File lib/jsonapi/basic_resource.rb, line 591 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
# File lib/jsonapi/basic_resource.rb, line 711 def resource_for(model_record, context) resource_klass = self.resource_klass_for_model(model_record) resource_klass.new(model_record, context) end
# File lib/jsonapi/basic_resource.rb, line 765 def resource_key_type @_resource_key_type || JSONAPI.configuration.resource_key_type end
# File lib/jsonapi/basic_resource.rb, line 476 def resource_klass_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
# File lib/jsonapi/basic_resource.rb, line 488 def resource_klass_for_model(model) resource_klass_for(resource_type_for(model)) end
# File lib/jsonapi/basic_resource.rb, line 496 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
# File lib/jsonapi/basic_resource.rb, line 705 def resources_for(records, context) records.collect do |record| resource_for(record, context) end end
# File lib/jsonapi/basic_resource.rb, line 950 def root? @root end
# File lib/jsonapi/basic_resource.rb, line 944 def root_resource @abstract = true @immutable = true @root = true end
# File lib/jsonapi/basic_resource.rb, line 516 def routing_options(options) @_routing_resource_options = options end
# File lib/jsonapi/basic_resource.rb, line 520 def routing_resource_options @_routing_resource_options ||= {} end
# File lib/jsonapi/basic_resource.rb, line 644 def singleton(*attrs) @_singleton = (!!attrs[0] == attrs[0]) ? attrs[0] : true @_singleton_options = attrs.extract_options! end
# File lib/jsonapi/basic_resource.rb, line 653 def singleton? @_singleton ||= false end
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`
# File lib/jsonapi/basic_resource.rb, line 772 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
# File lib/jsonapi/basic_resource.rb, line 665 def sort(sorting, options = {}) self._allowed_sort[sorting.to_sym] = options end
# File lib/jsonapi/basic_resource.rb, line 697 def sortable_field?(key, context = nil) sortable_fields(context).include? key.to_sym end
Override in your resource to filter the sortable keys
# File lib/jsonapi/basic_resource.rb, line 693 def sortable_fields(_context = nil) _allowed_sort.keys end
# File lib/jsonapi/basic_resource.rb, line 669 def sorts(*args) options = args.extract_options! _allowed_sort.merge!(args.inject({}) { |h, sorting| h[sorting.to_sym] = options.dup; h }) end
Override in your resource to filter the updatable keys
# File lib/jsonapi/basic_resource.rb, line 683 def updatable_fields(_context = nil) _updatable_relationships | _updatable_attributes - [:id] end
Either add a custom :verify lambda or override verify_custom_filter
to allow for custom filters
# File lib/jsonapi/basic_resource.rb, line 823 def verify_custom_filter(filter, value, _context = nil) [filter, value] end
# File lib/jsonapi/basic_resource.rb, line 729 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 values = call_method_or_proc(strategy, filter_values, context) [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
# File lib/jsonapi/basic_resource.rb, line 716 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
# File lib/jsonapi/basic_resource.rb, line 787 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
override to allow for key processing and checking
# File lib/jsonapi/basic_resource.rb, line 816 def verify_keys(keys, context = nil) return keys.collect do |key| verify_key(key, context) end end
Either add a custom :verify lambda or override verify_relationship_filter
to allow for custom relationship logic, such as uuids, multiple keys or permission checks on keys
# File lib/jsonapi/basic_resource.rb, line 829 def verify_relationship_filter(filter, raw, _context = nil) [filter, raw] end
Public Instance Methods
# File lib/jsonapi/basic_resource.rb, line 35 def _model @model end
# File lib/jsonapi/basic_resource.rb, line 47 def cache_id [id, self.class.hash_cache_field(_model.public_send(self.class._cache_field))] end
# File lib/jsonapi/basic_resource.rb, line 55 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
# File lib/jsonapi/basic_resource.rb, line 82 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
# File lib/jsonapi/basic_resource.rb, line 170 def custom_links(_options) {} end
Override this on a resource instance to override the fetchable keys
# File lib/jsonapi/basic_resource.rb, line 125 def fetchable_fields self.class.fields end
# File lib/jsonapi/basic_resource.rb, line 39 def id _model.public_send(self.class._primary_key) end
# File lib/jsonapi/basic_resource.rb, line 43 def identity JSONAPI::ResourceIdentity.new(self.class, id) end
# File lib/jsonapi/basic_resource.rb, line 51 def is_new? id.nil? end
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
# File lib/jsonapi/basic_resource.rb, line 161 def meta(_options) {} end
# File lib/jsonapi/basic_resource.rb, line 129 def model_error_messages _model.errors.messages end
# File lib/jsonapi/basic_resource.rb, line 76 def remove run_callbacks :remove do _remove end end
# File lib/jsonapi/basic_resource.rb, line 106 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
# File lib/jsonapi/basic_resource.rb, line 112 def remove_to_one_link(relationship_type, options = {}) change :remove_to_one_link do _remove_to_one_link(relationship_type, options) end end
# File lib/jsonapi/basic_resource.rb, line 118 def replace_fields(field_data) change :replace_fields do _replace_fields(field_data) end end
# File lib/jsonapi/basic_resource.rb, line 100 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
# File lib/jsonapi/basic_resource.rb, line 88 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
# File lib/jsonapi/basic_resource.rb, line 94 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
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.
# File lib/jsonapi/basic_resource.rb, line 152 def validation_error_metadata {} end
Private Instance Methods
# File lib/jsonapi/basic_resource.rb, line 243 def _create_to_many_links(relationship_type, relationship_key_values, options) relationship = self.class._relationships[relationship_type] relation_name = relationship.relation_name(context: @context) if options[:reflected_source] @model.public_send(relation_name) << options[:reflected_source]._model return :completed end # load requested related resources # make sure they all exist (also based on context) and add them to relationship related_resources = relationship.resource_klass.find_by_keys(relationship_key_values, context: @context) if related_resources.count != relationship_key_values.count # todo: obscure id so not to leak info fail JSONAPI::Exceptions::RecordNotFound.new('unspecified') end reflect = reflect_relationship?(relationship, options) related_resources.each do |related_resource| if reflect if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany) related_resource.create_to_many_links(relationship.inverse_relationship, [id], reflected_source: self) else related_resource.replace_to_one_link(relationship.inverse_relationship, id, reflected_source: self) end @reload_needed = true else unless @model.public_send(relation_name).include?(related_resource._model) @model.public_send(relation_name) << related_resource._model end end end :completed end
# File lib/jsonapi/basic_resource.rb, line 221 def _remove unless @model.destroy fail JSONAPI::Exceptions::ValidationErrors.new(self) end :completed rescue ActiveRecord::DeleteRestrictionError => e fail JSONAPI::Exceptions::RecordLocked.new(e.message) end
# File lib/jsonapi/basic_resource.rb, line 347 def _remove_to_many_link(relationship_type, key, options) relationship = self.class._relationships[relationship_type] reflect = reflect_relationship?(relationship, options) if reflect related_resource = relationship.resource_klass.find_by_key(key, context: @context) if related_resource.nil? fail JSONAPI::Exceptions::RecordNotFound.new(key) else if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany) related_resource.remove_to_many_link(relationship.inverse_relationship, id, reflected_source: self) else related_resource.remove_to_one_link(relationship.inverse_relationship, reflected_source: self) end end @reload_needed = true else @model.public_send(relationship.relation_name(context: @context)).delete(key) end :completed rescue ActiveRecord::DeleteRestrictionError => e fail JSONAPI::Exceptions::RecordLocked.new(e.message) rescue ActiveRecord::RecordNotFound fail JSONAPI::Exceptions::RecordNotFound.new(key) end
# File lib/jsonapi/basic_resource.rb, line 379 def _remove_to_one_link(relationship_type, _options) relationship = self.class._relationships[relationship_type] send("#{relationship.foreign_key}=", nil) @save_needed = true :completed end
# File lib/jsonapi/basic_resource.rb, line 388 def _replace_fields(field_data) field_data[:attributes].each do |attribute, value| begin send "#{attribute}=", value @save_needed = true rescue ArgumentError # :nocov: Will be thrown if an enum value isn't allowed for an enum. Currently not tested as enums are a rails 4.1 and higher feature raise JSONAPI::Exceptions::InvalidFieldValue.new(attribute, value) # :nocov: end end field_data[:to_one].each do |relationship_type, value| if value.nil? remove_to_one_link(relationship_type) else case value when Hash replace_polymorphic_to_one_link(relationship_type.to_s, value.fetch(:id), value.fetch(:type)) else replace_to_one_link(relationship_type, value) end end end if field_data[:to_one] field_data[:to_many].each do |relationship_type, values| replace_to_many_links(relationship_type, values) end if field_data[:to_many] :completed end
# File lib/jsonapi/basic_resource.rb, line 338 def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type, _options) relationship = self.class._relationships[relationship_type.to_sym] send("#{relationship.foreign_key}=", {type: key_type, id: key_value}) @save_needed = true :completed end
# File lib/jsonapi/basic_resource.rb, line 282 def _replace_to_many_links(relationship_type, relationship_key_values, options) relationship = self.class._relationship(relationship_type) reflect = reflect_relationship?(relationship, options) if reflect existing_rids = self.class.find_related_fragments([identity], relationship_type, options) existing = existing_rids.keys.collect { |rid| rid.id } to_delete = existing - (relationship_key_values & existing) to_delete.each do |key| _remove_to_many_link(relationship_type, key, reflected_source: self) end to_add = relationship_key_values - (relationship_key_values & existing) _create_to_many_links(relationship_type, to_add, {}) @reload_needed = true elsif relationship.polymorphic? relationship_key_values.each do |relationship_key_value| relationship_resource_klass = self.class.resource_klass_for(relationship_key_value[:type]) ids = relationship_key_value[:ids] related_records = relationship_resource_klass .records(options) .where({relationship_resource_klass._primary_key => ids}) missed_ids = ids - related_records.pluck(relationship_resource_klass._primary_key) if missed_ids.present? fail JSONAPI::Exceptions::RecordNotFound.new(missed_ids) end relation_name = relationship.relation_name(context: @context) @model.send("#{relation_name}") << related_records end @reload_needed = true else send("#{relationship.foreign_key}=", relationship_key_values) @save_needed = true end :completed end
# File lib/jsonapi/basic_resource.rb, line 329 def _replace_to_one_link(relationship_type, relationship_key_value, _options) relationship = self.class._relationships[relationship_type] send("#{relationship.foreign_key}=", relationship_key_value) @save_needed = true :completed end
Override this on a resource to return a different result code. Any value other than :completed will result in operations returning `:accepted`
For example to return `:accepted` if your model does not immediately save resources to the database you could override `_save` as follows:
“` def _save
super return :accepted
end “`
# File lib/jsonapi/basic_resource.rb, line 195 def _save(validation_context = nil) unless @model.valid?(validation_context) fail JSONAPI::Exceptions::ValidationErrors.new(self) end if defined? @model.save saved = @model.save(validate: false) unless saved if @model.errors.present? fail JSONAPI::Exceptions::ValidationErrors.new(self) else fail JSONAPI::Exceptions::SaveFailed.new end end else saved = true end @model.reload if @reload_needed @reload_needed = false @save_needed = !saved :completed end
# File lib/jsonapi/basic_resource.rb, line 231 def reflect_relationship?(relationship, options) return false if !relationship.reflect || (!JSONAPI.configuration.use_relationship_reflection || options[:reflected_source]) inverse_relationship = relationship.resource_klass._relationships[relationship.inverse_relationship] if inverse_relationship.nil? warn "Inverse relationship could not be found for #{self.class.name}.#{relationship.name}. Relationship reflection disabled." return false end true end
# File lib/jsonapi/basic_resource.rb, line 176 def save run_callbacks :save do _save end end