lib/jsonapi/resource.rb in jsonapi-resources-0.4.4 vs lib/jsonapi/resource.rb in jsonapi-resources-0.5.0
- old
+ new
@@ -11,26 +11,26 @@
define_jsonapi_resources_callbacks :create,
:update,
:remove,
:save,
- :create_has_many_link,
- :replace_has_many_links,
- :create_has_one_link,
- :replace_has_one_link,
- :replace_polymorphic_has_one_link,
- :remove_has_many_link,
- :remove_has_one_link,
+ :create_to_many_link,
+ :replace_to_many_links,
+ :create_to_one_link,
+ :replace_to_one_link,
+ :replace_polymorphic_to_one_link,
+ :remove_to_many_link,
+ :remove_to_one_link,
:replace_fields
def initialize(model, context = nil)
@model = model
@context = context
end
def id
- model.send(self.class._primary_key)
+ model.public_send(self.class._primary_key)
end
def is_new?
id.nil?
end
@@ -60,43 +60,43 @@
run_callbacks :remove do
_remove
end
end
- def create_has_many_links(association_type, association_key_values)
- change :create_has_many_link do
- _create_has_many_links(association_type, association_key_values)
+ 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
- def replace_has_many_links(association_type, association_key_values)
- change :replace_has_many_links do
- _replace_has_many_links(association_type, association_key_values)
+ 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
- def replace_has_one_link(association_type, association_key_value)
- change :replace_has_one_link do
- _replace_has_one_link(association_type, association_key_value)
+ 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
- def replace_polymorphic_has_one_link(association_type, association_key_value, association_key_type)
- change :replace_polymorphic_has_one_link do
- _replace_polymorphic_has_one_link(association_type, association_key_value, association_key_type)
+ 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
- def remove_has_many_link(association_type, key)
- change :remove_has_many_link do
- _remove_has_many_link(association_type, key)
+ def remove_to_many_link(relationship_type, key)
+ change :remove_to_many_link do
+ _remove_to_many_link(relationship_type, key)
end
end
- def remove_has_one_link(association_type)
- change :remove_has_one_link do
- _remove_has_one_link(association_type)
+ def remove_to_one_link(relationship_type)
+ change :remove_to_one_link do
+ _remove_to_one_link(relationship_type)
end
end
def replace_fields(field_data)
change :replace_fields do
@@ -109,12 +109,12 @@
self.class.fields
end
# Override this on a resource to customize how the associated records
# are fetched for a model. Particularly helpful for authorization.
- def records_for(association_name, _options = {})
- model.send association_name
+ def records_for(relationship_name, _options = {})
+ model.public_send relationship_name
end
private
def save
@@ -136,11 +136,11 @@
# return :accepted
# end
# ```
def _save
unless @model.valid?
- fail JSONAPI::Exceptions::ValidationErrors.new(@model.errors.messages)
+ fail JSONAPI::Exceptions::ValidationErrors.new(self)
end
if defined? @model.save
saved = @model.save
fail JSONAPI::Exceptions::SaveFailed.new unless saved
@@ -157,69 +157,69 @@
@model.destroy
:completed
end
- def _create_has_many_links(association_type, association_key_values)
- association = self.class._associations[association_type]
+ def _create_to_many_links(relationship_type, relationship_key_values)
+ relationship = self.class._relationships[relationship_type]
- association_key_values.each do |association_key_value|
- related_resource = association.resource_klass.find_by_key(association_key_value, context: @context)
+ relationship_key_values.each do |relationship_key_value|
+ related_resource = relationship.resource_klass.find_by_key(relationship_key_value, context: @context)
# TODO: Add option to skip relations that already exist instead of returning an error?
- relation = @model.send(association.type).where(association.primary_key => association_key_value).first
+ relation = @model.public_send(relationship.type).where(relationship.primary_key => relationship_key_value).first
if relation.nil?
- @model.send(association.type) << related_resource.model
+ @model.public_send(relationship.type) << related_resource.model
else
- fail JSONAPI::Exceptions::HasManyRelationExists.new(association_key_value)
+ fail JSONAPI::Exceptions::HasManyRelationExists.new(relationship_key_value)
end
end
:completed
end
- def _replace_has_many_links(association_type, association_key_values)
- association = self.class._associations[association_type]
+ def _replace_to_many_links(relationship_type, relationship_key_values)
+ relationship = self.class._relationships[relationship_type]
- send("#{association.foreign_key}=", association_key_values)
+ send("#{relationship.foreign_key}=", relationship_key_values)
@save_needed = true
:completed
end
- def _replace_has_one_link(association_type, association_key_value)
- association = self.class._associations[association_type]
+ def _replace_to_one_link(relationship_type, relationship_key_value)
+ relationship = self.class._relationships[relationship_type]
- send("#{association.foreign_key}=", association_key_value)
+ send("#{relationship.foreign_key}=", relationship_key_value)
@save_needed = true
:completed
end
- def _replace_polymorphic_has_one_link(association_type, key_value, key_type)
- association = self.class._associations[association_type.to_sym]
+ def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
+ relationship = self.class._relationships[relationship_type.to_sym]
- model.send("#{association.foreign_key}=", key_value)
- model.send("#{association.polymorphic_type}=", key_type.to_s.classify)
+ model.public_send("#{relationship.foreign_key}=", key_value)
+ model.public_send("#{relationship.polymorphic_type}=", key_type.to_s.classify)
@save_needed = true
:completed
end
- def _remove_has_many_link(association_type, key)
- association = self.class._associations[association_type]
+ def _remove_to_many_link(relationship_type, key)
+ relationship = self.class._relationships[relationship_type]
- @model.send(association.type).delete(key)
+ @model.public_send(relationship.type).delete(key)
:completed
end
- def _remove_has_one_link(association_type)
- association = self.class._associations[association_type]
+ def _remove_to_one_link(relationship_type)
+ relationship = self.class._relationships[relationship_type]
- send("#{association.foreign_key}=", nil)
+ send("#{relationship.foreign_key}=", nil)
@save_needed = true
:completed
end
@@ -233,34 +233,34 @@
raise JSONAPI::Exceptions::InvalidFieldValue.new(attribute, value)
# :nocov:
end
end
- field_data[:has_one].each do |association_type, value|
+ field_data[:to_one].each do |relationship_type, value|
if value.nil?
- remove_has_one_link(association_type)
+ remove_to_one_link(relationship_type)
else
case value
when Hash
- replace_polymorphic_has_one_link(association_type.to_s, value.fetch(:id), value.fetch(:type))
+ replace_polymorphic_to_one_link(relationship_type.to_s, value.fetch(:id), value.fetch(:type))
else
- replace_has_one_link(association_type, value)
+ replace_to_one_link(relationship_type, value)
end
end
- end if field_data[:has_one]
+ end if field_data[:to_one]
- field_data[:has_many].each do |association_type, values|
- replace_has_many_links(association_type, values)
- end if field_data[:has_many]
+ field_data[:to_many].each do |relationship_type, values|
+ replace_to_many_links(relationship_type, values)
+ end if field_data[:to_many]
:completed
end
class << self
def inherited(base)
base._attributes = (_attributes || {}).dup
- base._associations = (_associations || {}).dup
+ base._relationships = (_relationships || {}).dup
base._allowed_filters = (_allowed_filters || Set.new).dup
type = base.name.demodulize.sub(/Resource$/, '').underscore
base._type = type.pluralize.to_sym
@@ -276,11 +276,11 @@
fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
end
resource
end
- attr_accessor :_attributes, :_associations, :_allowed_filters, :_type, :_paginator
+ attr_accessor :_attributes, :_relationships, :_allowed_filters, :_type, :_paginator
def create(context)
new(create_model, context)
end
@@ -312,28 +312,43 @@
end
@_attributes ||= {}
@_attributes[attr] = options
define_method attr do
- @model.send(attr)
+ @model.public_send(attr)
end unless method_defined?(attr)
define_method "#{attr}=" do |value|
- @model.send "#{attr}=", value
+ @model.public_send "#{attr}=", value
end unless method_defined?("#{attr}=")
end
def default_attribute_options
{ format: :default }
end
+ 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
+
def has_one(*attrs)
- _associate(Association::HasOne, *attrs)
+ _add_relationship(Relationship::ToOne, *attrs)
end
def has_many(*attrs)
- _associate(Association::HasMany, *attrs)
+ _add_relationship(Relationship::ToMany, *attrs)
end
def model_name(model)
@_model_name = model.to_sym
end
@@ -365,51 +380,51 @@
end
# :nocov:
# Override in your resource to filter the updatable keys
def updatable_fields(_context = nil)
- _updatable_associations | _attributes.keys - [:id]
+ _updatable_relationships | _attributes.keys - [:id]
end
# Override in your resource to filter the creatable keys
def creatable_fields(_context = nil)
- _updatable_associations | _attributes.keys
+ _updatable_relationships | _attributes.keys
end
# Override in your resource to filter the sortable keys
def sortable_fields(_context = nil)
_attributes.keys
end
def fields
- _associations.keys | _attributes.keys
+ _relationships.keys | _attributes.keys
end
- def resolve_association_names_to_relations(resource_klass, model_includes, options = {})
+ def resolve_relationship_names_to_relations(resource_klass, model_includes, options = {})
case model_includes
when Array
return model_includes.map do |value|
- resolve_association_names_to_relations(resource_klass, value, options)
+ resolve_relationship_names_to_relations(resource_klass, value, options)
end
when Hash
model_includes.keys.each do |key|
- association = resource_klass._associations[key]
+ relationship = resource_klass._relationships[key]
value = model_includes[key]
model_includes.delete(key)
- model_includes[association.relation_name(options)] = resolve_association_names_to_relations(association.resource_klass, value, options)
+ model_includes[relationship.relation_name(options)] = resolve_relationship_names_to_relations(relationship.resource_klass, value, options)
end
return model_includes
when Symbol
- association = resource_klass._associations[model_includes]
- return association.relation_name(options)
+ relationship = resource_klass._relationships[model_includes]
+ return relationship.relation_name(options)
end
end
def apply_includes(records, options = {})
include_directives = options[:include_directives]
if include_directives
- model_includes = resolve_association_names_to_relations(self, include_directives.model_includes, options)
+ model_includes = resolve_relationship_names_to_relations(self, include_directives.model_includes, options)
records = records.includes(model_includes)
end
records
end
@@ -434,16 +449,16 @@
def apply_filters(records, filters, options = {})
required_includes = []
if filters
filters.each do |filter, value|
- if _associations.include?(filter)
- if _associations[filter].is_a?(JSONAPI::Association::HasMany)
+ if _relationships.include?(filter)
+ if _relationships[filter].is_a?(JSONAPI::Relationship::ToMany)
required_includes.push(filter.to_s)
- records = apply_filter(records, "#{filter}.#{_associations[filter].primary_key}", value, options)
+ records = apply_filter(records, "#{filter}.#{_relationships[filter].primary_key}", value, options)
else
- records = apply_filter(records, "#{_associations[filter].foreign_key}", value, options)
+ records = apply_filter(records, "#{_relationships[filter].foreign_key}", value, options)
end
else
records = apply_filter(records, filter, value, options)
end
end
@@ -512,20 +527,20 @@
verified_filters[verified_filter[0]] = verified_filter[1]
end
verified_filters
end
- def is_filter_association?(filter)
- filter == _type || _associations.include?(filter)
+ def is_filter_relationship?(filter)
+ filter == _type || _relationships.include?(filter)
end
def verify_filter(filter, raw, context = nil)
filter_values = []
filter_values += CSV.parse_line(raw) unless raw.nil? || raw.empty?
- if is_filter_association?(filter)
- verify_association_filter(filter, filter_values, context)
+ if is_filter_relationship?(filter)
+ verify_relationship_filter(filter, filter_values, context)
else
verify_custom_filter(filter, filter_values, context)
end
end
@@ -546,40 +561,40 @@
# override to allow for custom filters
def verify_custom_filter(filter, value, _context = nil)
[filter, value]
end
- # override to allow for custom association logic, such as uuids, multiple keys or permission checks on keys
- def verify_association_filter(filter, raw, _context = nil)
+ # override to allow for custom relationship logic, such as uuids, multiple keys or permission checks on keys
+ def verify_relationship_filter(filter, raw, _context = nil)
[filter, raw]
end
# quasi private class methods
def _attribute_options(attr)
default_attribute_options.merge(@_attributes[attr])
end
- def _updatable_associations
- @_associations.map { |key, _association| key }
+ def _updatable_relationships
+ @_relationships.map { |key, _relationship| key }
end
- def _has_association?(type)
+ def _has_relationship?(type)
type = type.to_s
- @_associations.key?(type.singularize.to_sym) || @_associations.key?(type.pluralize.to_sym)
+ @_relationships.key?(type.singularize.to_sym) || @_relationships.key?(type.pluralize.to_sym)
end
- def _association(type)
+ def _relationship(type)
type = type.to_sym
- @_associations[type]
+ @_relationships[type]
end
def _model_name
@_model_name ||= name.demodulize.sub(/Resource$/, '')
end
def _primary_key
- @_primary_key ||= :id
+ @_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
end
def _as_parent_key
@_as_parent_key ||= "#{_type.to_s.singularize}_#{_primary_key}"
end
@@ -636,72 +651,89 @@
end
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, :href, :links].include?(name.to_sym)
+ if [:type, :href, :links, :model].include?(name.to_sym)
warn "[NAME COLLISION] `#{name}` is a reserved key in #{@@resource_types[_type]}."
end
end
- def check_reserved_association_name(name)
+ def check_reserved_relationship_name(name)
if [:id, :ids, :type, :types, :href, :hrefs, :link, :links].include?(name.to_sym)
- warn "[NAME COLLISION] `#{name}` is a reserved association name in #{@@resource_types[_type]}."
+ warn "[NAME COLLISION] `#{name}` is a reserved relationship name in #{@@resource_types[_type]}."
end
end
- def _associate(klass, *attrs)
+ def _add_relationship(klass, *attrs)
options = attrs.extract_options!
options[:module_path] = module_path
attrs.each do |attr|
- check_reserved_association_name(attr)
- @_associations[attr] = association = klass.new(attr, options)
+ check_reserved_relationship_name(attr)
+ @_relationships[attr] = relationship = klass.new(attr, options)
- associated_records_method_name = case association
- when JSONAPI::Association::HasOne then "record_for_#{attr}"
- when JSONAPI::Association::HasMany then "records_for_#{attr}"
+ associated_records_method_name = case relationship
+ when JSONAPI::Relationship::ToOne then "record_for_#{attr}"
+ when JSONAPI::Relationship::ToMany then "records_for_#{attr}"
end
- foreign_key = association.foreign_key
+ 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 |options = {}|
- relation_name = association.relation_name(options.merge({context: @context}))
+ options = options.merge({context: @context})
+ relation_name = relationship.relation_name(options)
records_for(relation_name, options)
end unless method_defined?(associated_records_method_name)
- if association.is_a?(JSONAPI::Association::HasOne)
- define_method foreign_key do
- @model.method(foreign_key).call
- end unless method_defined?(foreign_key)
+ 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 attr do |options = {}|
- if association.polymorphic?
- associated_model = public_send(associated_records_method_name)
- resource_klass = Resource.resource_for(self.class.module_path + associated_model.class.to_s.underscore) if associated_model
- return resource_klass.new(associated_model, @context) if resource_klass
- else
- resource_klass = association.resource_klass
+ define_method attr do |options = {}|
+ if relationship.polymorphic?
+ associated_model = public_send(associated_records_method_name)
+ resource_klass = Resource.resource_for(self.class.module_path + associated_model.class.to_s.underscore) 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?(attr)
+ else
+ define_method foreign_key do
+ 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 attr do |options = {}|
+ 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?(attr)
- elsif association.is_a?(JSONAPI::Association::HasMany)
+ end unless method_defined?(attr)
+ 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.send(association.resource_klass._primary_key)
+ record.public_send(relationship.resource_klass._primary_key)
end
end unless method_defined?(foreign_key)
define_method attr do |options = {}|
- resource_klass = association.resource_klass
+ 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)
@@ -717,10 +749,10 @@
if paginator
records = resource_klass.apply_pagination(records, paginator, order_options)
end
return records.collect do |record|
- if association.polymorphic?
+ if relationship.polymorphic?
resource_klass = Resource.resource_for(self.class.module_path + record.class.to_s.underscore)
end
resource_klass.new(record, @context)
end
end unless method_defined?(attr)