Skip to content
Mike Nelson edited this page Jan 29, 2020 · 1 revision

When using Subroutine in a Rails application, you can avoid cluttering your controllers and models with code from specific use cases. Here's an example of how you could implement a signup flow:

Connecting it all

app/
  |
  |- controllers/
  |  |- users_controller.rb
  |
  |- models/
  |  |- user.rb
  |
  |- ops/
     |- signup_op.rb

Route

  resources :users, only: [] do
    collection do
      post :signup
    end
  end

Model

When ops are around, the point of the model is to ensure data validity. That's essentially it. So most of your models are a series of validations, common accessors, queries, etc.

class User
  validates :name,   presence: true
  validates :email,     email: true

  has_secure_password
end

Controller(s)

I've found that a great way to handle errors with ops is to allow you top level controller to appropriately render errors in a consistent way. This is exceptionally easy for api-driven apps.

class Api::Controller < ApplicationController
  rescue_from ::Subroutine::Failure, with: :render_op_failure

  def render_op_failure(e)
    # however you want to do this, `e` will be similar to an ActiveRecord::RecordInvalid error
    # e.record.errors, etc
  end
end

With ops, your controllers are essentially just connections between routes, operations, and whatever you use to build responses.

class UsersController < ::Api::Controller
  def sign_up
    # If the op fails, a ::Subroutine::Failure will be raised because we're invoking `submit!`.
    op = SignupOp.submit!(params)

    # If the op succeeds, it will be returned so you can access it's information.
    render json: op.signed_up_user
  end
end
Clone this wiki locally