Redress

Motivation (Command pattern)

The command pattern is sometimes called a service object, an operation, an action, and probably more names that I’m not aware of. Whatever the name we gave it, the purpose of such a pattern is rather simple: take a business action and put it behind an object with a simple interface.

Table of Contents

Requirements

  1. Ruby 2.3

  2. wisper

  3. dry-struct

  4. hashie

Optional

To support validations just add activemodel to your project

  1. activemodel

Setup

For an insecure install, type the following:

gem install redress

Add the following to your Gemfile:

gem "redress"

Usage

Forms

# app/forms/application_form.rb
require 'redress/form'

class ApplicationForm < Redress::Form
end

Let’s define simple form (Built-in Types dry-rb.org/gems/dry-types/built-in-types/):

class SimpleForm < ApplicationForm
  mimic :user

  define_schema do
    attribute :nickname, Redress::Types::Strict::String.default('superman')
    attribute :name, Redress::Types::StrippedString
    attribute :email, Redress::Types::StrippedString
    attribute :name_with_email, String
    attribute :age, Redress::Types::Coercible::Integer
    attribute :terms_of_service, Redress::Types::Bool
  end

  validates :name, presence: true
  validates :email, presence: true

  def map_model(user)
    self.name_with_email = "#{user.name} <#{user.email}>"
  end
end

Form with default values (dry-rb.org/gems/dry-types/default-values/):

require 'securerandom'

class CommentForm < Redress::Form
  define_schema do
    attribute(:id, Redress::Types::Coercible::String.default { SecureRandom.uuid })
    attribute(:content, Redress::Types::String)
  end

  validates :content, presence: true
end

Form with multiple comments:

class CommentForm < Redress::Form
  define_schema do
    attribute :id, Redress::Types::Coercible::Integer
    attribute :content, Redress::Types::String
  end

  validates :content, presence: true
end

class OrderForm < Redress::Form
  mimic :order

  define_schema do
    attribute :title, Redress::Types::String
    attribute :comments, Redress::Types::Array.of(CommentForm)
  end
end

Form with context:

class CommentForm < Redress::Form
  define_schema do
    attribute :id, Redress::Types::Coercible::Integer
    attribute :content, Redress::Types::String
  end

  validates :content, presence: true
  validate :unsure_order_state_waiting

  private

  def unsure_order_state_waiting
    context.order.state?(:waiting)
  end
end

CommentForm.new(content: 'Hi').with_context(order: order)

Commands

# app/commands/application_command.rb
require 'redress/command'

class ApplicationCommand < Redress::Command
end

Simple command for user registration:

# app/commands/users/create_command.rb
module Users
  class CreateCommand < ApplicationCommand
    def initialize(form)
      @form = form
    end

    def call
      return Failure(@form) if @form.invalid?

      user = User.new(@form.attributes)

      if user.save
        Success(user)
      else
        Failure(user)
      end
    end
  end
end

Controllers

# app/controllers/users_controller.rb
class UsersController < Account::BaseController
  respond_to :json, only: :update

  def new
    @user_form = SimpleForm.new
  end

  def create
    @user_form = SimpleForm.from_params(params)

    Users::CreateCommand.call(@user_form) do |c|
      c.success { head status: 201 }
      c.failure { |form| render status: 422, json: { errors: form.errors } }
    end
  end
end

Tests

To test, run:

bundle exec rspec ./spec/

Versioning

Read Semantic Versioning for details. Briefly, it means:

Code of Conduct

Please note that this project is released with a CODE OF CONDUCT. By participating in this project you agree to abide by its terms.

Contributions

Read CONTRIBUTING for details.

License

Copyright © 2017 Fodojo LLC. Read LICENSE for details.

History

Read CHANGES for details. Built with Gemsmith.

Credits

Developed by Igor Galeta at Fodojo LLC.