Adapters
If you find yourself repeatedly making customizations to a group of
Resource
s 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
end
Ensure our adapter gets loaded:
# config/initializers/jsonapi.rb
require 'elasticsearch_adapter'
And switch to that adapter in EmployeeResource
:
use_adapter ElasticsearchAdapter
Bounce 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)
end
For 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
end
The 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
end
You 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.