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
end
This 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
end
Including 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_scope
method.
An ActionController::Responder
with better error handling and Dry::Monads::Result
support.
Errors are handled for the following cases:
- The responded resource is an
ApplicationRecord
subclass and has error. - The responded resource is a
ActiveRecord::RecordInvalid
exception. - 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
end
Implement 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
end
Then 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
end
Include 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
end
Include the reset password Doorkeeper::Application
extensions.
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
# ...
end
class ::Doorkeeper::Application < ActiveRecord::Base
include ApiBlocks::Doorkeeper::Passwords::Application
end
Override 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
end
Update 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:migration
Implement 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
end
Add 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
end
Include the invitations Doorkeeper::Application
extensions.
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
# ...
end
class ::Doorkeeper::Application < ActiveRecord::Base
include ApiBlocks::Doorkeeper::Invitations::Application
end
Override 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
end
Update 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:migration
Licensed under the MIT license, see the separate LICENSE.txt file.