{"id":27598151,"url":"https://github.com/tymate/api-blocks","last_synced_at":"2025-11-11T18:40:07.748Z","repository":{"id":35547796,"uuid":"218328650","full_name":"tymate/api-blocks","owner":"tymate","description":"Simple and consistent rails api extensions","archived":false,"fork":false,"pushed_at":"2023-03-08T20:21:18.000Z","size":258,"stargazers_count":9,"open_issues_count":15,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-09T04:36:16.555Z","etag":null,"topics":["api","dry-transaction","dry-validation","pundit","rails-api","responders","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tymate.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-29T16:07:10.000Z","updated_at":"2024-11-21T17:03:51.000Z","dependencies_parsed_at":"2024-11-15T05:38:07.788Z","dependency_job_id":null,"html_url":"https://github.com/tymate/api-blocks","commit_stats":{"total_commits":150,"total_committers":3,"mean_commits":50.0,"dds":"0.30666666666666664","last_synced_commit":"883acc41b5c36b9e3c0b72ea99ec18ce7a2c3e00"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/tymate/api-blocks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tymate%2Fapi-blocks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tymate%2Fapi-blocks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tymate%2Fapi-blocks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tymate%2Fapi-blocks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tymate","download_url":"https://codeload.github.com/tymate/api-blocks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tymate%2Fapi-blocks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283700370,"owners_count":26879643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-10T02:00:06.292Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","dry-transaction","dry-validation","pundit","rails-api","responders","ruby","ruby-on-rails"],"created_at":"2025-04-22T14:12:30.601Z","updated_at":"2025-11-11T18:40:07.706Z","avatar_url":"https://github.com/tymate.png","language":"Ruby","readme":"[gem]: https://rubygems.org/gems/api-blocks\n[code_climate]: https://codeclimate.com/github/tymate/api-blocks\n[inch]: https://inch-ci.org/github/tymate/api-blocks?branch=master\n\n# ApiBlocks\n\n[![Gem](https://img.shields.io/gem/v/api-blocks?style=flat-square)][gem]\n[![Code Climate](https://img.shields.io/codeclimate/maintainability/tymate/api-blocks?style=flat-square)][code_climate]\n[![Inch](https://inch-ci.org/github/tymate/api-blocks.svg?branch=master)][inch]\n\nApiBlocks provides simple and consistent Rails API extensions.\n\nLinks:\n\n- [API Documentation](https://www.rubydoc.info/gems/api-blocks/0.1.1)\n- [Source Code](https://github.com/tymate/api-blocks)\n\n## Installation\n\n```ruby\ngem 'api-blocks'\n```\n\n## Configuration\n\nIn an initializer such as `config/initializers/api_blocks.rb` you can enable the\noptional [blueprinter](https://github.com/procore/blueprinter) and\n[batch-loader](https://github.com/exAspArk/batch-loader) integration:\n\n```ruby\nApiBlocks.configure do |config|\n  config.blueprinter.use_batch_loader = true\nend\n```\n\nThis allows you to use `batch-loader` in order to avoid n+1 queries when\nserializing associations in blueprints.\n\nThis has some caveats which are documented in\n[association_extractor.rb](lib/api_blocks/blueprinter/association_extractor.rb).\n\n## ApiBlocks::Controller\n\nInclude `ApiBlocks::Controller` in your api controller:\n\n```ruby\nclass Api::V1::ApplicationController \u003c ActionController::API\n  include ApiBlocks::Controller\n\n  pundit_scope :api, :v1\nend\n```\n\nIncluding the module will:\n\n- Setup [ApiBlocks::Responder](#ApiBlocks::Responder) as a responder.\n- Add the `verify_request_format!` before_action hook.\n- Setup Pundit, rescue its errors, setup its validation hooks and provide the `pundit_scope` method.\n\n## ApiBlocks::Responder\n\nAn `ActionController::Responder` with better error handling and `Dry::Monads::Result` support.\n\nErrors are handled for the following cases:\n\n- The responded resource is an `ApplicationRecord` subclass and has error.\n- The responded resource is a `ActiveRecord::RecordInvalid` exception.\n- Otherwise the error is re-raised to be handled through the usual Ruby On Rails\n  error handlers.\n\nIn addition, the responder will render resources on `POST` and `PUT` rather than\nreturning a redirection.\n\n## ApiBlocks::Interactor\n\nIt implements a basic interactor base class using `dry-transaction` and `dry-validation` under the hood.\n\nIt provides to predefined steps:\n\n- `validate_input!` which will validate the interactor input according to its schema.\n- `database_transaction!` an around step that wraps the interactor in an\n  ActiveRecord transaction.\n\nExample:\n\n```ruby\nclass Requests::MarkAsRead \u003c ApiBlocks::Interactor\n  input do\n    schema do\n      required(:request).filled(type?: Request)\n    end\n  end\n\n  around :database_transaction!\n  step :validate_input!\n\n  try :update_request!, catch: ActiveRecord::RecordInvalid\n  try :create_history_item!, catch: ActiveRecord::RecordInvalid\n\n  def update_request!(request:)\n    request.update!(read_at: Time.now.utc)\n    request\n  end\n\n  def create_history_item!(request)\n    request.request_history_items.create!(kind: :read)\n    request\n  end\nend\n```\n\n## ApiBlocks::Doorkeeper::Passwords\n\nImplement an API for passwords reset using doorkeeper and devise.\n\nInclude the `ApiBlocks::Doorkeeper::Passwords::Controller` module in your\npasswords api controller and define the `user_model` method to return the\nconcerned devise user model.\n\n```ruby\n# app/controllers/api/v1/passwords_controller.rb\nclass Api::V1::PasswordsController \u003c Api::V1::ApplicationController\n  include ApiBlocks::Doorkeeper::Passwords::Controller\n\n  private\n\n  def user_model\n    User\n  end\nend\n```\n\nThen add the approriate routes to your configuration.\n\n```ruby\n# config/routes.rb\nRails.application.routes.draw do\n  scope module: :api do\n    namespace :v1 do\n      resources :passwords, only: %i[create] do\n        get :callback, on: :collection\n        put :update, on: :collection\n      end\n    end\n  end\nend\n```\n\nInclude the `ApiBlocks::Doorkeeper::ResetPassword` module so devise will forward\nthe doorkeeper application to the mailer.\n\n```ruby\n# app/models/user.rb\nclass User \u003c ApplicationRecord\n  include ApiBlocks::Doorkeeper::ResetPassword\nend\n```\n\nInclude the reset password `Doorkeeper::Application` extensions.\n\n```ruby\n# config/initializers/doorkeeper.rb\n\nDoorkeeper.configure do\n  # ...\nend\n\nclass ::Doorkeeper::Application \u003c ActiveRecord::Base\n  include ApiBlocks::Doorkeeper::Passwords::Application\nend\n```\n\nOverride your devise mailer `#reset_password_instructions` method to add the\n`application` parameter.\n\n```ruby\n# app/mailers/devise_mailer.rb\n\nclass DeviseMailer \u003c Devise::Mailer\n  def reset_password_instructions(record, token, application = nil, _opts = {})\n    @token = token\n    @application = application\n  end\nend\n```\n\nUpdate the devise mailer template to link to the callback API.\n\n```erb\n# app/views/devise/mailer/reset_password_instructions.html.erb\n\u003cp\u003e\u003c%= link_to \"Change my password\", callback_v1_passwords_url(reset_password_token: @token) %\u003e\u003c/p\u003e\n```\n\nFinally, generate the required migrations:\n\n```sh\nbundle exec rails g api_blocks:doorkeeper:passwords:migration\n```\n\n## ApiBlocks::Doorkeeper::Invitations\n\nImplement an API for devise_invitable using doorkeeper.\n\nInclude the `ApiBlocks::Doorkeeper::Invitations::Controller` module in your api\ncontroller and define the `user_model` method to return the concerned devise\nuser model.\n\n```ruby\n# app/controllers/api/v1/invitations_controller.rb\nclass Api::V1::InvitationsController \u003c Api::V1::ApplicationController\n  include ApiBlocks::Doorkeeper::Invitations::Controller\n\n  private\n\n  def user_model\n    User\n  end\nend\n```\n\nAdd the approriate routes to your configuration.\n\n```ruby\n# config/routes.rb\nRails.application.routes.draw do\n  scope module: :api do\n    namespace :v1 do\n      resources :invitations, only: %i[create show] do\n        get :callback, on: :collection\n        put :update, on: :collection\n      end\n    end\n  end\nend\n```\n\nInclude the invitations `Doorkeeper::Application` extensions.\n\n```ruby\n# config/initializers/doorkeeper.rb\n\nDoorkeeper.configure do\n  # ...\nend\n\nclass ::Doorkeeper::Application \u003c ActiveRecord::Base\n  include ApiBlocks::Doorkeeper::Invitations::Application\nend\n```\n\nOverride your devise mailer `#invitation_instructions` method to add the\n`application` parameter.\n\n```ruby\n# app/mailers/devise_mailer.rb\n\nclass DeviseMailer \u003c Devise::Mailer\n  def invitation_instructions(_record, token, application: nil, **_opts)\n    @token = token\n    @application = application\n\n    super\n  end\nend\n```\n\nUpdate the devise mailer template to link to the callback API.\n\n```erb\n# app/views/devise/mailer/invitation_instructions.html.erb\n\u003cp\u003e\u003c%= link_to  t(\"devise.mailer.invitation_instructions.accept\"), callback_v1_invitations_url(invitation_token: @token, client_id: @application.uid) %\u003e\u003c/p\u003e\n```\n\nFinally, generate the required migrations:\n\n```sh\nbundle exec rails g api_blocks:doorkeeper:invitations:migration\n```\n\n# External Resources\n\n- [Pundit](https://github.com/varvet/pundit)\n- [Responders](https://github.com/plataformatec/responders)\n- [Dry Transaction](https://dry-rb.org/gems/dry-transaction/0.13/)\n- [Dry Validation](https://dry-rb.org/gems/dry-validation/1.3/)\n- [Problem Details](https://github.com/nikushi/problem_details)\n\n# License\n\nLicensed under the MIT license, see the separate LICENSE.txt file.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftymate%2Fapi-blocks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftymate%2Fapi-blocks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftymate%2Fapi-blocks/lists"}