May 18, 2016
Table of contents:
In last week’s tutorial we looked at defining our first Operation for creating new users.
A Trailblazer Operation encapsulates a given action of the application and takes responsibility for accepting data from the request, validating that data, and creating or updating the database.
It’s often the case that the create and update actions of a given resource are similar, but not quite the same. For example, each action will typically have the same properties, and perhaps many of the same validation rules.
But the create and update actions are usually not exactly the same.
In today’s tutorial we will be taking a closer look at the Contract aspect of the Operation.
An important part of last week’s tutorial was defining the Contract of the Operation:
require 'reform/form/validation/unique_validator.rb'
class User < ActiveRecord::Base
class Create < Trailblazer::Operation
include Model
model User, :create
contract do
property :email
property :password, virtual: true
validates :email, presence: true, email: true, unique: true
validates :password, presence: true
end
end
end
The Contract defines the properties and validation rules of the Operation.
But what is the Contract?
If you remember back to Trailblazer - A New Architecture For Rails [Review] I mentioned that the Operation, whilst on the surface seems like it has a lot of responsibility, actually is more of a conductor for the underlying composed collaborators.
The Contract is actually Reform in disguise! We’ve previously looked at Reform in Using Form Objects in Ruby on Rails with Reform.
So the Contract functionality of your Operations is actually implemented with Reform.
In last week’s tutorial we defined the Contract inside of the Operation. This is a perfectly acceptable way of defining the Contract as it keeps everything in one file.
However, it is also possible to define the Contract as a separate class, and then set that class as the Contract for the Operation.
I find this makes it easier to see the properties and validation rules for a set of Operations that share many of the same properties.
To separate out the Contract, first create a new file called contract.rb
under the concepts/user
directory:
module User::Contract
end
First I’m going to define all of the shared properties and rules in a Base
class that can be extended. Ruby does not have the idea of an abstract class, but this is essentially an abstract class because I won’t be using it directly, but instead, only using the children of this class:
require 'reform/form/validation/unique_validator.rb'
module User::Contract
class Base < Reform::Form
model User
property :email
property :password, virtual: true
validates :email, email: true, unique: true
validates :password, length: { minimum: 8 }
end
end
As you can see, the Base
class should inherit from Reform::Form
. Finally we see how the Contract is actually just an instance of Reform in disguise!
Next I can basically just cut and paste the rules from the Operation that will always apply and add them to this class.
I’ve also added a password length validation just to show that business rules that should always be satisfied should be placed in this base class.
Next I can add the Create
Contract:
require 'reform/form/validation/unique_validator.rb'
module User::Contract
class Base < Reform::Form
model User
property :email
property :password, virtual: true
validates :email, email: true, unique: true
validates :password, length: { minimum: 8 }
end
class Create < Base
validates :email, presence: true
validates :password, presence: true
end
end
The Create
class inherits from the Base
class so we do not need to define the properties and the validation rules again. In this class we can simply add the requirements that the email and password must be present.
Finally I can add the Update
class:
require 'reform/form/validation/unique_validator.rb'
module User::Contract
class Base < Reform::Form
model User
property :email
property :password, virtual: true
validates :email, email: true, unique: true
validates :password, length: { minimum: 8 }
end
class Create < Base
validates :email, presence: true
validates :password, presence: true
end
class Update < Base
end
end
In this example I haven’t added any additional validation rules for the update action, but as you can probably imagine, in a more complex scenario you might have additional validation rules that must be satisfied to update a given resource. In that case you could add these rules here.
With the Contract defined, we can now add it to the Operation like this:
require 'bcrypt'
class User < ActiveRecord::Base
class Create < Trailblazer::Operation
include Model
model User, :create
contract User::Contract::Create
def process(params)
validate(params[:user]) do |f|
generate_digest
f.save
end
end
def generate_digest
model.password_digest = BCrypt::Password.create(contract.password)
end
end
end
As you can see, I can remove all of the Contract specific code and replace it with following line to set the Contract on the Operation:
contract User::Contract::Create
If you run the tests again you should see that they all still pass!
If you are already familiar with Reform, it should come as no surprise that Operation classes in Trailblazer are really powerful. Once I understood that the Contract of an Operation was Reform, the pieces really started to fall into place.
The ability to define a “contract” and use inheritance really makes it easy to build out your business rules for a particular set of actions.
This is much easier than trying to do it in a Model class, especially if the rules are slightly different for create and update actions!