Created
December 10, 2019 14:32
-
-
Save mtortonesi/e9ea2639e0089004de3aa27a555352d9 to your computer and use it in GitHub Desktop.
Example of dry-validation and dry-transaction adoption within Rails 6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative '../operations/create_actor' | |
| require_relative '../operations/update_actor' | |
| class ActorsController < ApplicationController | |
| # GET /actors | |
| # GET /actors.json | |
| def index | |
| # @actors = Actor.all | |
| @actors = Actor.order(:name).page(params[:page]) | |
| end | |
| # GET /actors/1 | |
| # GET /actors/1.json | |
| def show | |
| @actor = Actor.find(params[:id]) | |
| end | |
| # GET /actors/new | |
| def new | |
| @actor = Actor.new | |
| end | |
| # GET /actors/1/edit | |
| def edit | |
| @actor = Actor.find(params[:id]) | |
| end | |
| # POST /actors | |
| # POST /actors.json | |
| def create | |
| # need to call .to_unsafe_h because dry-validation won't accept Rails's wacky params format | |
| CreateActor.new.call(params.to_unsafe_h[:actor]) do |m| | |
| m.success do |actor| | |
| respond_to do |format| | |
| format.html { redirect_to actor, notice: 'Actor was successfully created.' } | |
| format.json { render :show, status: :created, location: actor } | |
| end | |
| end | |
| m.failure do |errors| | |
| @actor = Actor.new; @errors = errors | |
| respond_to do |format| | |
| format.html { render :new } | |
| format.json { render json: @errors, status: :unprocessable_entity } | |
| end | |
| end | |
| end | |
| end | |
| # PATCH/PUT /actors/1 | |
| # PATCH/PUT /actors/1.json | |
| def update | |
| # need to call .to_unsafe_h because dry-validation won't accept Rails's wacky params format | |
| UpdateActor.new.call(params[:id], params.to_unsafe_h[:actor]) do |m| | |
| m.success do |actor| | |
| respond_to do |format| | |
| format.html { redirect_to actor, notice: 'Actor was successfully created.' } | |
| format.json { render :show, status: :ok, location: actor } | |
| end | |
| end | |
| m.failure do |errors| | |
| @actor = Actor.find(params[:id]); | |
| @errors = errors | |
| respond_to do |format| | |
| format.html { render :new } | |
| format.json { render json: @errors, status: :unprocessable_entity } | |
| end | |
| end | |
| end | |
| end | |
| # DELETE /actors/1 | |
| # DELETE /actors/1.json | |
| def destroy | |
| # this action is very simple: no need to define a dedicated operation for it | |
| Actor.destroy(params[:id]) | |
| respond_to do |format| | |
| format.html { redirect_to actors_url, notice: 'Actor was successfully destroyed.' } | |
| format.json { head :no_content } | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'dry-transaction' | |
| require_relative '../validators/actor_contract' | |
| class CreateActor | |
| include Dry::Transaction | |
| step :validate | |
| step :persist | |
| private | |
| def validate(input) | |
| result = ActorContract.new.call(input) | |
| if result.success? | |
| Success(result.to_h) | |
| else | |
| Failure(result.errors(full: true)) | |
| end | |
| end | |
| def persist(input) | |
| actor = Actor.new(input) | |
| if actor.save | |
| Success(actor) | |
| else | |
| Failure(OpenStruct.new(messages: [ "cannot persist actor" ])) | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'dry-transaction' | |
| require_relative '../validators/actor_contract' | |
| class UpdateActor | |
| include Dry::Transaction | |
| step :validate | |
| step :persist | |
| private | |
| def validate(input) | |
| result = ActorContract.new.call(input[:actor]) | |
| if result.success? | |
| Success(input) | |
| else | |
| Failure(result.errors(full: true)) | |
| end | |
| end | |
| def persist(input) | |
| actor = Actor.find(input[:id]) | |
| if actor.update(input[:actor]) | |
| Success(actor) | |
| else | |
| Failure(OpenStruct.new(messages: [ "cannot persist actor" ])) | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'dry-validation' | |
| class ActorContract < Dry::Validation::Contract | |
| params do | |
| required(:name).filled(:string) | |
| required(:dob).value(:date) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'test_helper' | |
| class CreateActorTest < ActiveSupport::TestCase | |
| def setup | |
| @valid_input = { name: "Terence Hill", dob: "1939-03-29" } | |
| @invalid_input = @valid_input.except(@valid_input.keys.sample) | |
| end | |
| test "it creates an actor in case of valid input" do | |
| result = CreateActor.new.call(@valid_input) | |
| assert result.success? | |
| end | |
| test "it should not create an actor given invalid input" do | |
| result = CreateActor.new.call(@invalid_input) | |
| assert result.failure? | |
| end | |
| test "it should return error messages in case of failure" do | |
| result = CreateActor.new.call(@invalid_input) | |
| assert result.failure.messages | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'test_helper' | |
| class UpdateActorTest < ActiveSupport::TestCase | |
| def setup | |
| @actor = actors(:one) | |
| @valid_input = { name: "Terence Hill", dob: "1939-03-29" } | |
| @invalid_input = @valid_input.except(@valid_input.keys.sample) | |
| end | |
| test "it updates an actor in case of valid input" do | |
| result = UpdateActor.new.call(id: @actor.id, actor: @valid_input) | |
| assert result.success? | |
| end | |
| test "it should not update an actor given invalid input" do | |
| result = UpdateActor.new.call(id: @actor.id, actor: @invalid_input) | |
| refute result.success? | |
| end | |
| test "it should return error messages in case of failure" do | |
| result = UpdateActor.new.call(@invalid_input) | |
| assert result.failure.messages | |
| end | |
| end |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Dear @solnic,
thank you so very much for your kind suggestions. I refactored the code as follows.
However, note that by doing so I hit Issue 115, which was kind of hard to debug. (Rails is a complicated beast which, to use a kind euphemism, doesn't really go the extra mile to avoid breaking the principle of least surprise.) Anyway, I was able to get the code working by changing the single occurrence of EMPTY_ARRAY in dry-monads' do.rb to [].
app/operations/create_actor.rb:
app/operations/update_actor.rb:
app/controllers/actors_controller.rb: