ApiBlocks provides simple and consistent Rails API extensions.
Links:
gem 'api-blocks'In an initializer such as config/initializers/api_blocks.rb you can enable the
optional blueprinter and
batch-loader integration:
ApiBlocks.configure do |config|
  config.blueprinter.use_batch_loader = true
endThis allows you to use batch-loader in order to avoid n+1 queries when
serializing associations in blueprints.
This has some caveats which are documented in association_extractor.rb.
Include ApiBlocks::Controller in your api controller:
class Api::V1::ApplicationController < ActionController::API
  include ApiBlocks::Controller
  pundit_scope :api, :v1
endIncluding the module will:
- Setup ApiBlocks::Responder as a responder.
- Add the verify_request_format!before_action hook.
- Setup Pundit, rescue its errors, setup its validation hooks and provide the pundit_scopemethod.
An ActionController::Responder with better error handling and Dry::Monads::Result support.
Errors are handled for the following cases:
- The responded resource is an ApplicationRecordsubclass and has error.
- The responded resource is a ActiveRecord::RecordInvalidexception.
- Otherwise the error is re-raised to be handled through the usual Ruby On Rails error handlers.
In addition, the responder will render resources on POST and PUT rather than
returning a redirection.
It implements a basic interactor base class using dry-transaction and dry-validation under the hood.
It provides to predefined steps:
- validate_input!which will validate the interactor input according to its schema.
- database_transaction!an around step that wraps the interactor in an ActiveRecord transaction.
Example:
class Requests::MarkAsRead < ApiBlocks::Interactor
  input do
    schema do
      required(:request).filled(type?: Request)
    end
  end
  around :database_transaction!
  step :validate_input!
  try :update_request!, catch: ActiveRecord::RecordInvalid
  try :create_history_item!, catch: ActiveRecord::RecordInvalid
  def update_request!(request:)
    request.update!(read_at: Time.now.utc)
    request
  end
  def create_history_item!(request)
    request.request_history_items.create!(kind: :read)
    request
  end
endImplement an API for passwords reset using doorkeeper and devise.
Include the ApiBlocks::Doorkeeper::Passwords::Controller module in your
passwords api controller and define the user_model method to return the
concerned devise user model.
# app/controllers/api/v1/passwords_controller.rb
class Api::V1::PasswordsController < Api::V1::ApplicationController
  include ApiBlocks::Doorkeeper::Passwords::Controller
  private
  def user_model
    User
  end
endThen add the approriate routes to your configuration.
# config/routes.rb
Rails.application.routes.draw do
  scope module: :api do
    namespace :v1 do
      resources :passwords, only: %i[create] do
        get :callback, on: :collection
        put :update, on: :collection
      end
    end
  end
endInclude the ApiBlocks::Doorkeeper::ResetPassword module so devise will forward
the doorkeeper application to the mailer.
# app/models/user.rb
class User < ApplicationRecord
  include ApiBlocks::Doorkeeper::ResetPassword
endInclude the reset password Doorkeeper::Application extensions.
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
  # ...
end
class ::Doorkeeper::Application < ActiveRecord::Base
  include ApiBlocks::Doorkeeper::Passwords::Application
endOverride your devise mailer #reset_password_instructions method to add the
application parameter.
# app/mailers/devise_mailer.rb
class DeviseMailer < Devise::Mailer
  def reset_password_instructions(record, token, application = nil, _opts = {})
    @token = token
    @application = application
  end
endUpdate the devise mailer template to link to the callback API.
# app/views/devise/mailer/reset_password_instructions.html.erb
<p><%= link_to "Change my password", callback_v1_passwords_url(reset_password_token: @token) %></p>Finally, generate the required migrations:
bundle exec rails g api_blocks:doorkeeper:passwords:migrationImplement an API for devise_invitable using doorkeeper.
Include the ApiBlocks::Doorkeeper::Invitations::Controller module in your api
controller and define the user_model method to return the concerned devise
user model.
# app/controllers/api/v1/invitations_controller.rb
class Api::V1::InvitationsController < Api::V1::ApplicationController
  include ApiBlocks::Doorkeeper::Invitations::Controller
  private
  def user_model
    User
  end
endAdd the approriate routes to your configuration.
# config/routes.rb
Rails.application.routes.draw do
  scope module: :api do
    namespace :v1 do
      resources :invitations, only: %i[create show] do
        get :callback, on: :collection
        put :update, on: :collection
      end
    end
  end
endInclude the invitations Doorkeeper::Application extensions.
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
  # ...
end
class ::Doorkeeper::Application < ActiveRecord::Base
  include ApiBlocks::Doorkeeper::Invitations::Application
endOverride your devise mailer #invitation_instructions method to add the
application parameter.
# app/mailers/devise_mailer.rb
class DeviseMailer < Devise::Mailer
  def invitation_instructions(_record, token, application: nil, **_opts)
    @token = token
    @application = application
    super
  end
endUpdate the devise mailer template to link to the callback API.
# app/views/devise/mailer/invitation_instructions.html.erb
<p><%= link_to  t("devise.mailer.invitation_instructions.accept"), callback_v1_invitations_url(invitation_token: @token, client_id: @application.uid) %></p>Finally, generate the required migrations:
bundle exec rails g api_blocks:doorkeeper:invitations:migrationLicensed under the MIT license, see the separate LICENSE.txt file.