Everytime then you start your new RoR based project first steps you are doing is a basic architecture, directory structure, code organization and gems which you decide to use in your project.
Ruby on Rails have a lot of tutorials, best practices and a lot of gems for code organization and abstraction.
One of the interesting approach is a https://dry-rb.org.
dry-rb is a collection of next-generation Ruby libraries.
dry-rb helps you write clear, flexible, and more maintainable Ruby code. Each dry-rb gem fulfils a common task, and together they make a powerful platform for any kind of Ruby application.
Why dry.rb?
Dry ecosystem use a lot of gems for different purposes.
You can read about each gem here http://dry-rb.org/gems/.
Let’s take a look to the all of them:
dry-auto_inject Container-agnostic constructor injection mixin
dry-configurable Thread-safe configuration mixin
dry-container Simple and thread-safe IoC container
dry-core A toolset of small support modules used throughout the dry-rb & rom-rb ecosystems
dry-equalizer Simple mixin providing equality methods
dry-events Standalone pub/sub API
dry-inflector Standalone inflections
dry-initializer DSL for defining initializer params and options
dry-logic Predicate logic with composable rules
dry-matcher Flexible, expressive pattern matching
dry-monads Useful, common monads in idiomatic Ruby
dry-struct Attribute DSL for struct-like objects
dry-system Organize your code into reusable components
dry-transaction Business transaction DSL
dry-types Flexible type system with many built-in types
dry-validation Powerful data validation based on predicate logic
dry-view Functional, standalone view rendering
dry-web-roda A simple web stack combining Roda with the dry-rb and rom-rb gems
Using with Rails
In this post I want to show you using two gems from this list:
Gemfile
gem 'dry-validation', '~> 0.11.1'
gem 'dry-transaction', '~> 0.10.2'
dry-validation
http://dry-rb.org/gems/dry-validation
Dry validation has a simple DSL to describe Schemas of your model and use predicates logic to implement validation logic.
Let’s take a look into QuestionSchema:
require 'dry-validation'
QuestionSchema = Dry::Validation.Schema do
required(:title).filled
required(:body).filled
end
We just require dry-validation and use .filled to mark field required.
You can read more at: http://dry-rb.org/gems/dry-validation/basics/predicate-logic/
dry-transaction
http://dry-rb.org/gems/dry-transaction
Dry transaction has a simple DSL to describe Schemas of your model and use predicates logic to implement validation logic.
Let’s take a look into CreateQuestion:
require 'dry-transaction'
class CreateQuestion
include Dry::Transaction
step :validate
step :persist
def validate(input)
validation = QuestionSchema.(input)
if validation.success?
Success(input)
else
Failure(validation.errors)
end
end
def persist(input)
question = repo.create(input)
Success(question)
end
private
def repo
QuestionRepository.new(ROM.env)
end
end
We just require dry-transaction and include Dry::Transaction to turn everything on.
When we describe step attributes, those steps - like steps in databases transactions, in our example there are two steps and two methods for each of them: validate and persist, if everything going good, we return Success and if something fails - Failure.
Depending on the result, execution going to the next step, or cancel entire code execution.
You can read more at: http://dry-rb.org/gems/dry-transaction/basic-usage/
Controller
Let’s move into more Rails specific things, our contollers.
All what we need here - call for CreateQuestion transaction command and move execution flow depending on success or failure.
class QuestionsController < ApplicationController
before_action :authorize, except: [:show, :index]
before_action :load_searches, only: [:show, :new, :create]
def index
@questions = repo.query(params[:q], current_user)
load_searches
end
def show
@question = repo.by_id_with_answers_and_users(params[:id])
end
def new
@question = Question.new
end
def create
transaction = CreateQuestion.new
transaction.(question_params.merge!({ user_id: @current_user.id })) do |result|
result.success do |question|
flash[:notice] = "Question has been created."
redirect_to questions_path
end
result.failure :validate do |errors|
@question = Question.new(question_params)
@errors = errors
flash[:alert] = "Question could not be created."
render :new
end
end
end
private
def question_params
params.require(:question).permit(:title, :body, :q).to_h.symbolize_keys
end
def repo
QuestionRepository.new(ROM.env)
end
end
You can find interesting thing here - QuestionRepository - you can read about it in the next post.
Full project at my GitHub: https://github.com/maratgaliev/qboard.
Stay tuned!