Everything started out smoothly, building a MVP in a few days is amazing, but after some months of non-stop development it just isn't that simple to implement that great new feature. Your models are full of validations, the controllers are calling methods from another galaxy, everything seems to be tested but bugs keep on rising, like zombies from an apocalyptic (not so distant) future.

Ok. This might been a bit too harsh, but I really felt a bit like that.

The bottom-line was: we were really unhappy with the lack of structure in a vanilla Rails Way. Besides some conventions about database table names and directories for god classes called controller or model, Rails didn't give us any other guidance. So, we started looking around for architectures, or better, gems that enforce architectures and not just talk about how things could be done.

That's when I met Trailblazer, it's a "gem" idealized and created by Nick Sutterer aka @apotonick, yes he's the guy from cells.

Trailblazer is an architecture based on Operations, those are the entry-points of our app, they will define the "internal" API of your application. So it helps you to give back to your models and controllers theirs original responsibilities. A controller shouldn't really know about the underlying domain model, in the same way your Model shouldn't care about end user validations or crazy business logic, models are entities that define what are the data that you can work with. So, what trailblazer give us are some of those "missing" layers on top of the basic MVC stack.

Here's how an Operation looks like:

class Post::Create < Trailblazer::Operation contract do property :title, validates: { presence: true } property :body, validates: { presence: true } end def process(params) validate(params[:post], model) do contract.save end end private def model! Post.new end end

The contract block specifies the properties used by this operation, note that any other :property not described in there will just be ignored and won't be saved to the model. That means that you don't need params.permit(:something) anymore. #winwin

The #process methods receive a hash of params and "processes" them. In our case we want to validate our post params and if everything is ok, save them to our model.

Fine, that's cool, but how do I use those "operations" of yours?

Easy:

class PostsController < ApplicationController # other actions... def create Post::Create.run(params) do |op| return redirect_to posts_path end render :new end end

The great thing here is that our controller actions doesn't need to care about any business logic, they sole role is to call an Operation and operate on its result, that is: if everything went fine we want to redirect our users to the post index page, but if something went wrong we will re-render the :new view.

But dude, I could've done that with an usual if @model.valid? call in my action. Yes you could, but controllers shouldn't be dealing with business logic, what if you wanted to publish! a post whenever it's saved? You would need to add that into the action, and that's not cool. Controllers are there to handle HTTP stuff, they are great for rendering, redirecting, etc.

Another benefit is that operations use Form Objects by default, so we can exterminate all validations in our models. The contract block of an Operation uses reform to create our Form Object, it's responsible for validating and "syncing" data to our model.

class Admin::Post::Create < Post::Create contract do # properties :title and :body # are inherited from Post::Create 'contract' property :author, validates: { presence: true } end def process(params) validate(params[:post], model) do contract.save # you could do special admin stuff here if you wanted/needed end end # model! is inherited end

This Operation defines an extra attribute/property :author , both Operations use the same underlying model, but each one uses only the attributes they need, and process those attributes in their own way.

If you didn't had those separated contracts/form objects your model would probably look like that:

class Post < ActiveRecord::Base attr_accessor :validate_author validates :title, presence: true validates :body, presence: true, limit: 3000 validates :author, presence: true, if: :validate_author end # And 'call' our desired 'optional' validation like that Post.new(title: 'foo', body: 'bar', author: '', validate_author: true).save

But since we're using a Form Object to do our validations, we can clean up our model entirely, having it nice and tidy.

class Post < ActiveRecord::Base # We would only list relations here, # or maybe attributes if this weren't an AR model. end

This separation of concerns and responsibilities also helps into creating better tests.

This is a RSpec test for our Operation:

RSpec.describe Post::Create do subject { described_class } describe "#contract" do # If our operation had some custom validations, coercions # and stuff like that we could test it here. # I usually don't test basic validations, # they just work, but it's up to you. end describe "#process" do context "with valid params" do let(:params) { { title: "Isn't it cool?", body: "something useful here" } } it "persists the params" do res, op = subject.run(params) expect(op.model.persisted?).to be_truthy expect(res).to be_truthy end # If our operation had to generate tokens, # send emails and those kind of things # all of them would be tested over here. end context "with invalid params" do let(:params) { { title: "Isn't it cool?", body: "" } } it "does NOT persist the params" do res, op = subject.run(params) expect(op.model.persisted?).to be_falsy expect(res).to be_falsy end end end end

And here is the spec for the controller using our super awesome operation.

RSpec.describe PostsController, :type => :controller do # Other tests ommited... describe "POST#create" do before(:each) do post :create, params end context "with valid params" do let(:params) { { title: 'foo', body: 'not foo enough' } } it { expect(response).to redirect_to(posts_path) } it { expect(flash[:notice]).to be_present } end context "with invalid params" do let(:params) { { customer: {} } } it { expect(response).to render_template :new } it { expect(flash.now[:alert]).to be_present } end end end

See, our controller spec doesn't need to care about persistence or anything business related, it knows that if an Operation succeeds it should redirect to posts_path and if not just re-render the view, and that's what we're testing.

And since we could clean up our model we don't really need a test for it xD

That's all folks!

In the next days I'll post a more in-deep step-by-step guide on using Trailblazer with Rails.

But, if you need any help or just want to chat, checkout the Trailblazer channel on gitter: https://gitter.im/trailblazer/chat