class JsonapiCompliable::Adapters::Abstract
Adapters
DRY up common resource logic.
For instance, there's no reason to write ActiveRecord
logic like this in every Resource:
allow_filter :title do |scope, value| scope.where(title: value) end sort do |scope, att, dir| scope.order(att => dir) end paginate do |scope, current_page, per_page| scope.page(current_page).per(per_page) end
This logic can be re-used through an Adapter:
use_adapter JsonapiCompliable::Adapters::ActiveRecord allow_filter :title
Adapters
are pretty simple to write. The corresponding code for the above ActiveRecord
adapter, which should look pretty familiar:
class JsonapiCompliable::Adapters::ActiveRecord def filter(scope, attribute, value) scope.where(attribute => value) end def order(scope, attribute, direction) scope.order(attribute => direction) end def paginate(scope, current_page, per_page) scope.page(current_page).per(per_page) end end
An adapter can have a corresponding sideloading_module
. This module gets mixed in to a Sideload
. In other words, Resource is to Adapter as Sideload is to *Adapter#sideloading_module*. Use this module to define DSL methods that wrap allow_sideload:
class MyAdapter < JsonapiCompliable::Adapters::Abstract # ... code ... def sideloading_module MySideloadingAdapter end end module MySideloadingAdapter def belongs_to(association_name) allow_sideload association_name do # ... code ... end end end # And now in your Resource: class MyResource < ApplicationResource # ... code ... use_adapter MyAdapter belongs_to :my_association end
If you need the adapter to do nothing, because perhaps the API you are hitting does not support sorting, use JsonapiCompliable::Adapters::Null
.
@see Resource.use_adapter
@see Adapters::ActiveRecord
@see Adapters::ActiveRecordSideloading
@see Adapters::Null
Public Instance Methods
Assign these two objects together.
@example Basic accessor
def associate(parent, child, association_name, association_type) if association_type == :has_many parent.send(association_name).push(child) else child.send(:"#{association_name}=", parent) end end
association_name
and association_type
come from your sideload configuration:
allow_sideload :the_name, type: the_type do # ... code. end
@param parent The parent object (via the JSONAPI 'relationships' graph) @param child The child object (via the JSONAPI 'relationships' graph) @param association_name The 'relationships' key we are processing @param association_type The Sideload
type (see Sideload#type
). Usually :has_many/:belongs_to/etc
# File lib/jsonapi_compliable/adapters/abstract.rb, line 236 def associate(parent, child, association_name, association_type) raise 'you must override #associate in an adapter subclass' end
@param scope the scope object we are chaining @param [Symbol] attr corresponding stat attribute name @return [Float] the average of the scope @example ActiveRecord
default
def average(scope, attr) scope.average(attr).to_f end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 138 def average(scope, attr) raise 'you must override #average in an adapter subclass' end
@param scope the scope object we are chaining @param [Symbol] attr corresponding stat attribute name @return [Numeric] the count of the scope @example ActiveRecord
default
def count(scope, attr) column = attr == :total ? :all : attr scope.uniq.count(column) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 127 def count(scope, attr) raise 'you must override #count in an adapter subclass' end
@param [Class] model_class The configured model class (see Resource.model
) @param [Hash] create_params Attributes + id @return the model instance just created @see Resource.model
@example ActiveRecord
default
def create(model_class, create_params) instance = model_class.new(create_params) instance.save instance end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 303 def create(model_class, create_params) raise 'you must override #create in an adapter subclass' end
@param [Class] model_class The configured model class (see Resource.model
) @param [Integer] id the id for this model @return the model instance just destroyed @see Resource.model
@example ActiveRecord
default
def destroy(model_class, id) instance = model_class.find(id) instance.destroy instance end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 331 def destroy(model_class, id) raise 'you must override #destroy in an adapter subclass' end
Remove the association without destroying objects
This is NOT needed in the standard use case. The standard use case would be:
def update(attrs) # attrs[:the_foreign_key] is nil, so updating the record disassociates end
However, sometimes you need side-effect or elsewise non-standard behavior. Consider using {{github.com/mbleigh/acts-as-taggable-on acts_as_taggable_on}} gem:
# Not actually needed, just an example def disassociate(parent, child, association_name, association_type) parent.tag_list.remove(child.name) end
@example Basic accessor
def disassociate(parent, child, association_name, association_type) if association_type == :has_many parent.send(association_name).delete(child) else child.send(:"#{association_name}=", nil) end end
association_name
and association_type
come from your sideload configuration:
allow_sideload :the_name, type: the_type do # ... code. end
@param parent The parent object (via the JSONAPI 'relationships' graph) @param child The child object (via the JSONAPI 'relationships' graph) @param association_name The 'relationships' key we are processing @param association_type The Sideload
type (see Sideload#type
). Usually :has_many/:belongs_to/etc
# File lib/jsonapi_compliable/adapters/abstract.rb, line 276 def disassociate(parent, child, association_name, association_type) raise 'you must override #disassociate in an adapter subclass' end
@param scope The scope object we are chaining @param [Symbol] attribute The attribute name we are filtering @param value The corresponding query parameter value @return the scope
@example ActiveRecord
default
def filter(scope, attribute, value) scope.where(attribute => value) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 88 def filter(scope, attribute, value) raise 'you must override #filter in an adapter subclass' end
@param scope the scope object we are chaining @param [Symbol] attr corresponding stat attribute name @return [Numeric] the maximum value of the scope @example ActiveRecord
default
def maximum(scope, attr) scope.maximum(attr) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 160 def maximum(scope, attr) raise 'you must override #maximum in an adapter subclass' end
@param scope the scope object we are chaining @param [Symbol] attr corresponding stat attribute name @return [Numeric] the maximum value of the scope @example ActiveRecord
default
def maximum(scope, attr) scope.maximum(attr) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 171 def minimum(scope, attr) raise 'you must override #maximum in an adapter subclass' end
@param scope The scope object we are chaining @param [Symbol] attribute The attribute name we are sorting @param [Symbol] direction The direction we are sorting (asc/desc) @return the scope
@example ActiveRecord
default
def order(scope, attribute, direction) scope.order(attribute => direction) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 101 def order(scope, attribute, direction) raise 'you must override #order in an adapter subclass' end
@param scope The scope object we are chaining @param [Integer] current_page The current page number @param [Integer] per_page The number of results per page @return the scope
@example ActiveRecord
default
# via kaminari gem def paginate(scope, current_page, per_page) scope.page(current_page).per(per_page) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 115 def paginate(scope, current_page, per_page) raise 'you must override #paginate in an adapter subclass' end
Resolve the scope. This is where you'd actually fire SQL, actually make an HTTP call, etc.
@example ActiveRecordDefault
def resolve(scope) scope.to_a end
@example Suggested Customization
# When making a service call, we suggest this abstraction # 'scope' here is a hash def resolve(scope) # The implementation of .where can be whatever you want SomeModelClass.where(scope) end
@see Adapters::ActiveRecord#resolve
@param scope The scope object to resolve @return an array of Model instances
# File lib/jsonapi_compliable/adapters/abstract.rb, line 210 def resolve(scope) scope end
This module gets mixed in to Sideload
classes This is where you define methods like has_many, belongs_to etc that wrap the lower-level Sideload#allow_sideload
@see Resource#allow_sideload @see Sideload#allow_sideload
@see Adapters::ActiveRecord#sideloading_module
@see Adapters::ActiveRecordSideloading
@return the module to mix in
# File lib/jsonapi_compliable/adapters/abstract.rb, line 289 def sideloading_module Module.new end
@param scope the scope object we are chaining @param [Symbol] attr corresponding stat attribute name @return [Numeric] the sum of the scope @example ActiveRecord
default
def sum(scope, attr) scope.sum(attr) end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 149 def sum(scope, attr) raise 'you must override #sum in an adapter subclass' end
This method must yield
the code to run within the transaction. This method should roll back the transaction if an error is raised.
@param [Class] model_class The class we're operating on @example ActiveRecord
default
def transaction(model_class) model_class.transaction do yield end end
@see Resource.model
# File lib/jsonapi_compliable/adapters/abstract.rb, line 187 def transaction(model_class) raise 'you must override #transaction in an adapter subclass, it must yield' end
@param [Class] model_class The configured model class (see Resource.model
) @param [Hash] update_params Attributes + id @return the model instance just created @see Resource.model
@example ActiveRecord
default
def update(model_class, update_params) instance = model_class.find(update_params.delete(:id)) instance.update_attributes(update_params) instance end
# File lib/jsonapi_compliable/adapters/abstract.rb, line 317 def update(model_class, update_params) raise 'you must override #update in an adapter subclass' end