Month: May 2016

Trailblazer + Devise: Integrating Devise validatable model with Trailblazer operation + error propagation

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

Dry-Configurable lazy evaluated default settings

Dry-configurable isĀ  a simple mixin to make Ruby classes configurable. It is quite nice, uses Struct and allows you to have default values. Unfortunately, it does not support (yet hopefully) default values that are evaluated upon each setting key request.

When would that be useful? Let's take a simple example: Kafka + Zookeeper. Zookeeper stores informations about Kafka cluster. They may vary in time, so unless you take them in real time, you might have problem if the cluster were reconfigured. The best option is to set a proc that will be evaluated on each setting request. This is how to obtain such a behavior (note that this will evaluate only default values - if you assign proc in configure block, it will be returned):

# Patch that will allow to use proc based lazy evaluated settings with Dry Configurable
class Dry::Configurable::Config
  # @param [Array] All arguments that a Struct accepts
  def initialize(*args)
    super
    setup_dynamics
  end

  private

  # Method that sets up all the proc based lazy evaluated dynamic config values
  def setup_dynamics
    self.to_h.each do |key, value|
      next unless value.is_a?(Proc)

      rebuild(key)
    end
  end

  # Method that rebuilds a given accessor, so when it consists a proc value, it will
  # evaluate it upon return
  # @param method_name [Symbol] name of an accessor that we want to rebuild
  def rebuild(method_name)
    metaclass = class << self; self; end

    metaclass.send(:define_method, method_name) do
      super().is_a?(Proc) ? super().call : super()
    end
  end
end

Example usage:

class Application
  extend Dry::Configurable

  setting :standard_setting
  setting :lazy_assigned
  setting :lazy_default, -> { "lazy_default-#{rand}" }

  # Note that this works for nested values as well
  setting :namespaced do
    setting :lazy, -> { "namespaced.lazy-#{rand}" }
  end
end

Application.configure do |config|
  config.standard_setting = 1
  config.lazy_assigned = -> { "lazy_assigned-#{rand}" }
end

Application.config.standard_setting #=> 1
Application.config.standard_setting #=> 1
Application.config.lazy_assigned #=> <Proc>
Application.config.lazy_default #=> "lazy_default-0.9601194173446863"
Application.config.lazy_default #=> "lazy_default-0.9450471949405372"
Application.config.namespaced.lazy #=> "namespaced.lazy-0.6938534114443213"
Application.config.namespaced.lazy #=> "namespaced.lazy-0.747101587617097"

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑