Class: JsonapiCompliable::Adapters::Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/jsonapi_compliable/adapters/abstract.rb

Overview

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.

Direct Known Subclasses

ActiveRecord, Null

Instance Method Summary collapse

Instance Method Details

#associate(parent, child, association_name, association_type) ⇒ Object

Assign these two objects together.

association_name and association_type come from your sideload configuration:

allow_sideload :the_name, type: the_type do
  # ... code.
end

Examples:

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

Parameters:

  • parent

    The parent object (via the JSONAPI 'relationships' graph)

  • child

    The child object (via the JSONAPI 'relationships' graph)

  • association_name

    The 'relationships' key we are processing

  • association_type

    The Sideload type (see Sideload#type). Usually :has_many/:belongs_to/etc



236
237
238
# 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

#average(scope, attr) ⇒ Float

Returns the average of the scope

Examples:

ActiveRecord default

def average(scope, attr)
  scope.average(attr).to_f
end

Parameters:

  • scope

    the scope object we are chaining

  • attr (Symbol)

    corresponding stat attribute name

Returns:

  • (Float)

    the average of the scope



138
139
140
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 138

def average(scope, attr)
  raise 'you must override #average in an adapter subclass'
end

#count(scope, attr) ⇒ Numeric

Returns the count of the scope

Examples:

ActiveRecord default

def count(scope, attr)
  column = attr == :total ? :all : attr
  scope.uniq.count(column)
end

Parameters:

  • scope

    the scope object we are chaining

  • attr (Symbol)

    corresponding stat attribute name

Returns:

  • (Numeric)

    the count of the scope



127
128
129
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 127

def count(scope, attr)
  raise 'you must override #count in an adapter subclass'
end

#create(model_class, create_params) ⇒ Object

Returns the model instance just created

Examples:

ActiveRecord default

def create(model_class, create_params)
  instance = model_class.new(create_params)
  instance.save
  instance
end

Parameters:

  • model_class (Class)

    The configured model class (see Resource.model)

  • create_params (Hash)

    Attributes + id

Returns:

  • the model instance just created

See Also:



303
304
305
# 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

#destroy(model_class, id) ⇒ Object

Returns the model instance just destroyed

Examples:

ActiveRecord default

def destroy(model_class, id)
  instance = model_class.find(id)
  instance.destroy
  instance
end

Parameters:

  • model_class (Class)

    The configured model class (see Resource.model)

  • id (Integer)

    the id for this model

Returns:

  • the model instance just destroyed

See Also:



331
332
333
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 331

def destroy(model_class, id)
  raise 'you must override #destroy in an adapter subclass'
end

#disassociate(parent, child, association_name, association_type) ⇒ Object

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 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

association_name and association_type come from your sideload configuration:

allow_sideload :the_name, type: the_type do
  # ... code.
end

Examples:

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

Parameters:

  • parent

    The parent object (via the JSONAPI 'relationships' graph)

  • child

    The child object (via the JSONAPI 'relationships' graph)

  • association_name

    The 'relationships' key we are processing

  • association_type

    The Sideload type (see Sideload#type). Usually :has_many/:belongs_to/etc



276
277
278
# 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

#filter(scope, attribute, value) ⇒ Object

Returns the scope

Examples:

ActiveRecord default

def filter(scope, attribute, value)
  scope.where(attribute => value)
end

Parameters:

  • scope

    The scope object we are chaining

  • attribute (Symbol)

    The attribute name we are filtering

  • value

    The corresponding query parameter value

Returns:

  • the scope



88
89
90
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 88

def filter(scope, attribute, value)
  raise 'you must override #filter in an adapter subclass'
end

#maximum(scope, attr) ⇒ Numeric

Returns the maximum value of the scope

Examples:

ActiveRecord default

def maximum(scope, attr)
  scope.maximum(attr)
end

Parameters:

  • scope

    the scope object we are chaining

  • attr (Symbol)

    corresponding stat attribute name

Returns:

  • (Numeric)

    the maximum value of the scope



160
161
162
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 160

def maximum(scope, attr)
  raise 'you must override #maximum in an adapter subclass'
end

#minimum(scope, attr) ⇒ Numeric

Returns the maximum value of the scope

Examples:

ActiveRecord default

def maximum(scope, attr)
  scope.maximum(attr)
end

Parameters:

  • scope

    the scope object we are chaining

  • attr (Symbol)

    corresponding stat attribute name

Returns:

  • (Numeric)

    the maximum value of the scope



171
172
173
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 171

def minimum(scope, attr)
  raise 'you must override #maximum in an adapter subclass'
end

#order(scope, attribute, direction) ⇒ Object

Returns the scope

Examples:

ActiveRecord default

def order(scope, attribute, direction)
  scope.order(attribute => direction)
end

Parameters:

  • scope

    The scope object we are chaining

  • attribute (Symbol)

    The attribute name we are sorting

  • direction (Symbol)

    The direction we are sorting (asc/desc)

Returns:

  • the scope



101
102
103
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 101

def order(scope, attribute, direction)
  raise 'you must override #order in an adapter subclass'
end

#paginate(scope, current_page, per_page) ⇒ Object

Returns the scope

Examples:

ActiveRecord default

# via kaminari gem
def paginate(scope, current_page, per_page)
  scope.page(current_page).per(per_page)
end

Parameters:

  • scope

    The scope object we are chaining

  • current_page (Integer)

    The current page number

  • per_page (Integer)

    The number of results per page

Returns:

  • the scope



115
116
117
# 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(scope) ⇒ Object

Resolve the scope. This is where you'd actually fire SQL, actually make an HTTP call, etc.

Examples:

ActiveRecordDefault

def resolve(scope)
  scope.to_a
end

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

Parameters:

  • scope

    The scope object to resolve

Returns:

  • an array of Model instances

See Also:



210
211
212
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 210

def resolve(scope)
  scope
end

#sideloading_moduleObject

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



289
290
291
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 289

def sideloading_module
  Module.new
end

#sum(scope, attr) ⇒ Numeric

Returns the sum of the scope

Examples:

ActiveRecord default

def sum(scope, attr)
  scope.sum(attr)
end

Parameters:

  • scope

    the scope object we are chaining

  • attr (Symbol)

    corresponding stat attribute name

Returns:

  • (Numeric)

    the sum of the scope



149
150
151
# File 'lib/jsonapi_compliable/adapters/abstract.rb', line 149

def sum(scope, attr)
  raise 'you must override #sum in an adapter subclass'
end

#transaction(model_class) ⇒ Object

This method must yield the code to run within the transaction. This method should roll back the transaction if an error is raised.

Examples:

ActiveRecord default

def transaction(model_class)
  model_class.transaction do
    yield
  end
end

Parameters:

  • model_class (Class)

    The class we're operating on

See Also:



187
188
189
# 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

#update(model_class, update_params) ⇒ Object

Returns the model instance just created

Examples:

ActiveRecord default

def update(model_class, update_params)
  instance = model_class.find(update_params.delete(:id))
  instance.update_attributes(update_params)
  instance
end

Parameters:

  • model_class (Class)

    The configured model class (see Resource.model)

  • update_params (Hash)

    Attributes + id

Returns:

  • the model instance just created

See Also:



317
318
319
# 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