Introduction
Before dry-rails hits 1.0.0 it should be considered as beta software. Various usage patterns should emerge as more people try it out, so please do so and provide feedback!
dry-rails
is the official dry-rb railtie for Ruby on Rails framework. It provides an application container using dry-system
with additional features:
:safe_params
- a small controller extension that adds the ability to define schemas for controller actions:application_contract
- sets upApplicationContract
class for you:controller_helpers
- convenient methods for working with the application container
Installation
To generate a new application skeleton with dry-rails
installed, you can use the default template provided by the gem. Here's a minimalistic example:
rails new my_app -MOPCSJT --template https://raw.githubusercontent.com/dry-rb/dry-rails/master/templates/default.rb
If you already have a Rails application, simply add dry-rails
to your Gemfile
:
gem "dry-rails", "~> 0.1"
Overview
The railtie integrates dry-system with the Rails runtime by setting up a system container for you that works out of the box with no configuration required. This is based on a couple of conventions:
- The system container is defined as
Container
constant within your application namespace defined byconfig/application.rb
. For example, if you generate a new rails application and call itblog
, then the application namespace will be calledBlog
, and so your system container will be available asBlog::Container
- The auto-injection mixin is defined as
Import
under the application namespace too - You can tweak the system container via an initializer, it can be called however you want, but the convention that we use is to call it
config/initializers/system.rb
- Bootable components are expected to be found in
config/system/*.rb
files
The railtie supports code-reloading in development mode - Container
, Import
and boot files get reloaded upon every request (or when you manually reload!
in the console).
Using auto-registration
Currently, the railtie does not make any assumptions about your directory/file structure. This means you are expected to specify where your components are located. Here's an example:
# config/initializers/system.rb
Dry::Rails.container do
auto_register!("app/operations")
end
Then, in app/operations
you can add your own classes and have them auto-registered and exposed via application container:
# app/operations/users/create
module Users
class Create
end
end
You can easily verify this using the console:
irb(main):001:0> MyApp::Container['users.create']
=> #<Users::Create:0x00007fa8f7c04f48>
Using Import
mixin
The auto-injection mechanism is also set up for you automatically. Let's say you have a GitHub service that needs an HTTP client. The HTTP client will be part of your lib
but the GitHub service will be part of your app
. Here's how you could set it up:
# lib/my_app/http.rb
module MyApp
class HTTP
# some useful methods
end
end
# app/services/github.rb
class Github
include MyApp::Import[:http]
# more useful methods
end
You can verify that Github
has access to HTTP
object in the console:
# bin/rails console
irb(main):001:0> MyApp::Container[:github]
=> #<Github:0x00007fb38c2fae30 @http=#<MyApp::HTTP:0x00007fb38c2fb150>>
Inflector
By default, the railtie registers ActiveSupport::Inflector
as the default inflector. You can access it via container:
# bin/rails console
irb(main):007:0> MyApp::Container[:inflector].demodulize("MyApp::Container")
=> "Container"
It is recommended to inject the inflector via the import module, instead of referring to the global ActiveSupport::Inflector
constant. Otherwise you'll lose the ability to easily switch inflectors when you have a need, which can happen. Your future-self will be grateful.
Safe Params
Controllers can specify schemas for their actions and access safe params through safe_params
helper. This is like strong_parameters
but much more powerful as its powered by dry-schema.
Here's a simple example:
class UsersController < ApplicationController
schema(:show, :edit) do
required(:id).value(:integer)
end
before_action :set_user, only: %i[show edit]
def show
render :show, user: @user
end
def edit
render :edit, user: @user
end
private
def set_user
@user = User.find(safe_params[:id])
end
end
safe_params
returns a dry-schema result object, you can use it to return a default error response, ie:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action do
if safe_params && safe_params.failure?
render(:error, errors: safe_params.errors.to_h) and throw(:abort)
end
end
end
If you're curious about the throw(:abort)
part check out this article about early returns from action callbacks.
ApplicationContract
The railtie gives you access to ApplicationContract
class which is powered by dry-validation. You can define your own contract classes and simply inherit from ApplicationContract
:
# lib/users/contracts/new.rb
module Users
module Contracts
class New < ApplicationContract
# define the schema and rules
end
end
end
Controller Helpers
With :controller_helpers
feature you get a couple of convenient methods that makes it easier to access container and its registered objects:
ApplicationController#container
- a simple shortcut toYourApp::Container
constantApplicationController#resolve
- a shortcut that delegatesresolve
to the container
Here's a simple usage example how you could access an operation powered by dry-monads:
class UsersController < ApplicationController
def create
resolve("users.create").(safe_params[:user]) do |m|
m.success do |user|
render json: user
end
m.failure do |code, errors|
render json: { code: code, errors: errors.to_h }, status: :unprocessable_entity
end
end
end
end
The railtie will soon provide monadic operations too!
Turning features on/off
By default all the features are enabled but you can cherry-pick them if you want. Let's say you only want to use safe_params
and controller_helpers
, in such case you can configure that in the initializer:
# config/initializers/system.rb
Dry::Rails.container do
config.features = %i[
safe_params
controller_helpers
]
end
Learn more
The railtie simply puts together other dry-rb gems and make them work out-of-the-box in a typical Rails application. If you want to fully leverage the power of these tools, it is recommended to check out individual gem documentation pages:
- dry-system - which is the backbone of
Dry::Rails::Container
, your application container - dry-schema - which gives you the safe params feature
- dry-validation - which gives you the application contract feature