Devise is one of those gems that are tightly bound to the Rails stack. It means that as long as you follow the "Rails way" and you do things the recommended way, you should not have any problems.
However, Trailblazer is not the recommended way and the way it works, not always makes it painless to integrate with external gems (note: it's not a Trailblazer fault, more often poorly designed external gems). Unfortunately Devise is one of those libraries. Models have validations, things happen magically in the controllers and so on.
Most of the time, you can leave it that way, as the scope of what Devise does is pretty isolated (authentication + authorization). But what if you want to integrate it with Trailblazer, for example to provide a custom built password change page? You can do this by following those steps
- Provide a contract with similar (or the same) validation rules as Devise (this will make it easier to migrate if you decide to drop model validations)
- Create an operation that will save the contract and propagate changes to the model
- Copy model errors (if any) into contract errors
Here's the code you need (I removed more complex validation rules to simplify things):
# Contract object
class Contracts::Update < Reform::Form
include Reform::Form::ActiveRecord
# Devise validatable model
model User
property :password
property :password_confirmation
validates :password,
presence: true,
confirmation: true
validates :password_confirmation,
presence: true
end
Operation is fairly simple as well:
class Operations::Update < Trailblazer::Operation
include Trailblazer::Operation::Model
contract Contracts::Update
# Devise validatable model
model User
def process(params)
validate(params[:user]) do
# When our validations passes, we can try to save contract
contract.save do |hash|
# update our user instance
model.update(hash)
# and propagate any model based errors to our contract and operation
model.errors.each { |name, desc| errors.add(name, desc) }
end
end
end
And the best part - controller:
class PasswordsController < BaseController
def edit
respond_with(form Operations::Update)
end
def update
respond_with(run Operations::Update)
end