Adapters
If you find yourself repeatedly making customizations to a group of
Resources and seek DRYer code, package those customizations into an
Adapter. Here we’ll be starting from a previous example,
ElasticSearch.
Adapters are simpler than you might think. It’s little more than copy-pasting those low-level customizations into a common class.
Start by creating lib/elasticsearch_adapter.rb. Cut/past the sorting,
pagination, and #resolve overrides from EmployeeResource into the adapter,
turning into def methods along the way:
# lib/elasticsearch_adapter.rb
class ElasticsearchAdapter
def paginate(scope, current_page, per_page)
scope.metadata.pagination.current_page = current_page
scope.metadata.pagination.per_page = per_page
scope
end
def order(scope, att, dir)
scope.metadata.sort = [{att: att, dir: dir}]
scope
end
def resolve(scope)
scope.query!
scope.results
end
endEnsure our adapter gets loaded:
# config/initializers/jsonapi.rb
require 'elasticsearch_adapter'And switch to that adapter in EmployeeResource:
use_adapter ElasticsearchAdapterBounce your server. You can still hit the /api/v1/employees endpoint
with the same sort and paginate functionality, but the code has been
moved to an adapter.
Let’s ensure our users can filter as well:
def filter(scope, att, val)
scope.condition(att).eq(val)
endFor all the methods and functionality an adapter supports, see the Adapter documenation.
Association Macros
We probably also want has_many-style macros to avoid writing similar
allow_sideload code time after time. Start by specifying where this
functionality is defined, and add a has_many macro:
module Sideloading
def has_many(association_name,
scope:,
resource:,
foreign_key:,
primary_key: :id,
&blk)
# our code will go here
instance_eval(&blk) if blk
end
end
def sideloading_module
Sideloading
endThe instance_eval is there so we can always drop down to a lower-level
customization in our Resource.
We can basically cut/paste our existing sideload code and rewrite it as variables:
scope do |parents|
parent_ids = parents.map { |p| p.send(primary_key) }
scope.call.condition(foreign_key).or(parent_ids.uniq.compact)
end
assign do |parents, children|
parents.each do |p|
relevant_children = children.select do |c|
c.send(foreign_key) == p.send(primary_key)
end
p.send(:"#{association_name}=", relevant_children)
end
endYou can now remove any customizations from your Resource classes. You
can continue to build the adapter, adding belongs_to, statistics, and
more. View the adapter documentation for the full API.