https://github.com/tymate/api-blocks
Simple and consistent rails api extensions
https://github.com/tymate/api-blocks
api dry-transaction dry-validation pundit rails-api responders ruby ruby-on-rails
Last synced: about 1 month ago
JSON representation
Simple and consistent rails api extensions
- Host: GitHub
- URL: https://github.com/tymate/api-blocks
- Owner: tymate
- License: mit
- Created: 2019-10-29T16:07:10.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-03-08T20:21:18.000Z (about 2 years ago)
- Last Synced: 2025-03-16T12:07:37.266Z (3 months ago)
- Topics: api, dry-transaction, dry-validation, pundit, rails-api, responders, ruby, ruby-on-rails
- Language: Ruby
- Size: 252 KB
- Stars: 9
- Watchers: 2
- Forks: 1
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
[gem]: https://rubygems.org/gems/api-blocks
[code_climate]: https://codeclimate.com/github/tymate/api-blocks
[inch]: https://inch-ci.org/github/tymate/api-blocks?branch=master# ApiBlocks
[][gem]
[][code_climate]
[][inch]ApiBlocks provides simple and consistent Rails API extensions.
Links:
- [API Documentation](https://www.rubydoc.info/gems/api-blocks/0.1.1)
- [Source Code](https://github.com/tymate/api-blocks)## Installation
```ruby
gem 'api-blocks'
```## Configuration
In an initializer such as `config/initializers/api_blocks.rb` you can enable the
optional [blueprinter](https://github.com/procore/blueprinter) and
[batch-loader](https://github.com/exAspArk/batch-loader) integration:```ruby
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](lib/api_blocks/blueprinter/association_extractor.rb).## ApiBlocks::Controller
Include `ApiBlocks::Controller` in your api controller:
```ruby
class Api::V1::ApplicationController < ActionController::API
include ApiBlocks::Controllerpundit_scope :api, :v1
end
```Including the module will:
- Setup [ApiBlocks::Responder](#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.## ApiBlocks::Responder
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.## ApiBlocks::Interactor
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:
```ruby
class Requests::MarkAsRead < ApiBlocks::Interactor
input do
schema do
required(:request).filled(type?: Request)
end
endaround :database_transaction!
step :validate_input!try :update_request!, catch: ActiveRecord::RecordInvalid
try :create_history_item!, catch: ActiveRecord::RecordInvaliddef update_request!(request:)
request.update!(read_at: Time.now.utc)
request
enddef create_history_item!(request)
request.request_history_items.create!(kind: :read)
request
end
end
```## ApiBlocks::Doorkeeper::Passwords
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.```ruby
# app/controllers/api/v1/passwords_controller.rb
class Api::V1::PasswordsController < Api::V1::ApplicationController
include ApiBlocks::Doorkeeper::Passwords::Controllerprivate
def user_model
User
end
end
```Then add the approriate routes to your configuration.
```ruby
# 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.```ruby
# app/models/user.rb
class User < ApplicationRecord
include ApiBlocks::Doorkeeper::ResetPassword
end
```Include the reset password `Doorkeeper::Application` extensions.
```ruby
# config/initializers/doorkeeper.rbDoorkeeper.configure do
# ...
endclass ::Doorkeeper::Application < ActiveRecord::Base
include ApiBlocks::Doorkeeper::Passwords::Application
end
```Override your devise mailer `#reset_password_instructions` method to add the
`application` parameter.```ruby
# app/mailers/devise_mailer.rbclass 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.
```erb
# app/views/devise/mailer/reset_password_instructions.html.erb<%= link_to "Change my password", callback_v1_passwords_url(reset_password_token: @token) %>
```Finally, generate the required migrations:
```sh
bundle exec rails g api_blocks:doorkeeper:passwords:migration
```## ApiBlocks::Doorkeeper::Invitations
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.```ruby
# app/controllers/api/v1/invitations_controller.rb
class Api::V1::InvitationsController < Api::V1::ApplicationController
include ApiBlocks::Doorkeeper::Invitations::Controllerprivate
def user_model
User
end
end
```Add the approriate routes to your configuration.
```ruby
# 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.
```ruby
# config/initializers/doorkeeper.rbDoorkeeper.configure do
# ...
endclass ::Doorkeeper::Application < ActiveRecord::Base
include ApiBlocks::Doorkeeper::Invitations::Application
end
```Override your devise mailer `#invitation_instructions` method to add the
`application` parameter.```ruby
# app/mailers/devise_mailer.rbclass DeviseMailer < Devise::Mailer
def invitation_instructions(_record, token, application: nil, **_opts)
@token = token
@application = applicationsuper
end
end
```Update the devise mailer template to link to the callback API.
```erb
# app/views/devise/mailer/invitation_instructions.html.erb<%= link_to t("devise.mailer.invitation_instructions.accept"), callback_v1_invitations_url(invitation_token: @token, client_id: @application.uid) %>
```Finally, generate the required migrations:
```sh
bundle exec rails g api_blocks:doorkeeper:invitations:migration
```# External Resources
- [Pundit](https://github.com/varvet/pundit)
- [Responders](https://github.com/plataformatec/responders)
- [Dry Transaction](https://dry-rb.org/gems/dry-transaction/0.13/)
- [Dry Validation](https://dry-rb.org/gems/dry-validation/1.3/)
- [Problem Details](https://github.com/nikushi/problem_details)# License
Licensed under the MIT license, see the separate LICENSE.txt file.