mike927 mike927 - 13 days ago 5
Ruby Question

Rails - validation inside decorator

I'm struggling with some kind of issue. I have a rails model (mongoid).

class User
include Mongoid::Document
include ActiveModel::SecurePassword

validate :password_presence,
:password_confirmation_match,
:email_presence,

field :email
field :password_digest

def password_presence
end

def email_presence
end

def password_confirmation_match
end
end


My goal is to call validations depends on which decorator I will use. Let's say I've got two decorators:

class PasswordDecorator < Draper::Decorator
def initialize(user)
@user = user
end
end

def RegistraionDecorator < Draper::Decorator
def initialize(user)
@user = user
end
end


So now when I create/save/update my user object inside RegistraionDecorator I would like to perform all validation methods.

RegistraionDecorator.new(User.new(attrbiutes))


But when I will do it inside PasswordDecorator I want to call for example only password_presence method.

PasswordDecorator.new(User.first)


When I move validations to decorator it won't work cuz its different class than my model.

How can I achieve that?

Answer

Try to use a Form Object pattern instead.

Here is an example (from a real project) of how it could be done with reform.

class PromocodesController < ApplicationController
  def new
    @form = PromocodeForm.new(Promocode.new)
  end

  def create
    @form = PromocodeForm.new(Promocode.new)

    if @form.validate(promo_params)
      Promocode.create!(promo_params)
      redirect_to promocodes_path
    else
      render :edit
    end
  end

  private

  def promo_params
    params.require(:promocode).
      permit(:token, :promo_type, :expires_at, :usage_limit, :reusable)
  end
end



class PromocodeForm < Reform::Form
  model :promocode

  property :token
  property :promo_type
  property :expires_at
  property :usage_limit
  property :reusable

  validates_presence_of :token, :promo_type, :expires_at, :usage_limit, :reusable
  validates_uniqueness_of :token

  validates :usage_limit, numericality: { greater_or_equal_to: -1 }
  validates :promo_type, inclusion: { in: Promocode::TYPES }
end

Bonus: The model does not trigger validations and much easy to use in tests.