-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
Operational is meant to enhance your Rails application, it can be included in your application and only used where you want it. You don't have to start your application with Operational, its designed to fit in when you start to need it.
The main concepts introduced by Operational are:
Operations are an orchestrator, or wrapper, around a business process. They don't do a lot themselves but provide an interface for executing all the business logic in an action while keeping the data persistence and service objects isolated and decoupled.
A functional way to define the business logic steps in an action, simplifying the handling of happy paths and failure paths in easy to understand tracks. You define steps that execute in order, the result of the step (truthy or falsey) moves execution to the success or failure tracks. Reduce complicated conditionals with explicit methods.
The idea that each step in an operation receives parameters from the previous step, much like Unix pipe passing output from one command to the next. This state is a single hash that allows you to encapsulate all the information an operation might need, like current_user
, params
, or remote_ip
and provide it in a single point of access. It is mutable within the execution of the operation, so steps may add to it, but immutable at the end of the operation.
A pattern to help decouple data persistence from your view layer. They allow you to define a form or API that may touch many different ActiveRecord models without needing to couple those models together. Validation can be done in the form, where it is more contextually appropriate, rather than on the model. Form Objects more securely define what attributes a request may submit without the need for StrongParameters
, as they are not directly database backed and do not suffer from mass assignment issues StrongParameters tries to solve.
gem "operational"
A Form Object allows you to decouple your UI and API from the persistence/database. It is much the same as a regular ActiveRecord model, but it is not limited to a single one. You can present forms and endpoints with any params without coupling multiple models together.
module API::Lists
class ListForm < Operational::Form
# Define attributes and type that this form will accept. Replaces the implicitly
# defined ActiveRecord attributes with an explicit list not coupled to any model.
# Strong Parameters is no longer required.
attribute :description, :string
# Define active model validations as normal.
validates :description, presence: true, length: { maximum: 500 }
end
end
See Form Objects for more information.
An operation sits between a controller and an ActiveRecord model (or any other business process). It orchestrates the action by executing steps in order. It delegates to methods, classes or Procs to execute each step in the railway. The result of each step controls the path to the next step, allowing you to easily halt, pass, or recover without messy conditionals. State is passed to each step, allowing your operation to build a consistent, rich result passed back up to the controller and view that isn't limited to a single model.
module API::Lists
class CreateOperation < Operational::Operation
# Create the ActiveRecord model that will eventually be persisted.
step :setup_new_entry
# Instantiate the Form Object (what we defined in step 2).
step Contract::Build(contract: ListForm, model_key: :entry)
# Apply parameters and validate the Form Object
step Contract::Validate()
# Copy the Form Object attributes back to the ActiveRecord model.
step Contract::Sync(model_key: :entry)
# Persist the ActiveRecord model... we only get here if everything else
# had a truthy return.
step :persist
def setup_new_entry(state)
state[:entry] = ListEntry.new(user: state[:current_user])
end
def persist(state)
state[:entry].save!
end
end
See Operations for more information.
An operation can be run from anything, an async job process, the console, etc. but primarily you will use them in controllers. They replace the logic within a controller. Here we mix-in the Operational::Controller
module and use the run
method to execute the operation and set the instance variable @state
, so the results of your operation can be used in the controller or views.
class ListsController < ActionController::Base
include Operational::Controller
# By default, the run command seeds the operation's state with the current_user and
# params (ActionController::Parameters or a Hash) from the controller.
def create
if run Lists::CreateOperation
return redirect_to todo_path(@state[:entry].id), notice: "Created Successfully."
else
render :new
# The UI can reference @state["contract"] which is the Form Object. It is an ActiveModel
# object and responds to errors hashes and all other Rails form helpers.
# ie. @state["contract"].errors.full_messages
end
end
end
See Controller Mixin for more information.
Explore more of this wiki and the Examples if some of these concepts are new to you, or dive right into Operations to see all that you can do.
Next: Operations