Search code examples
ruby-on-railscallbackbefore-filter

Skip before_validation on: :create in test


I am having issues testing a rather simple thing, i.e. duplicity of codes not being allowed in the db.

Let's say I have this model:

class Ticket < ActiveRecord::Base
  attr_accessible :code, :series, :sec_code
  validates_uniqueness_of :code, scope:[:series,:sec_code]

  before_validation :generate_codes, on: :create  


  private
  def generate_codes
    self.code=rand.to_s[2..9]  #-> 8 digit code
    self.sec_code=rand.to_s[2..4] #-> 2 digit security code
    self.series=('A'..'Z').to_a.sample #-> one character series
  end

end

Then I would like to test that the uniqueness validation does work, in this way:

require 'spec_helper'

describe Ticket do
  before do
    @ticket=Ticket.create
  end
  subject{@ticket}

  it{ should respond_to(:code)}
  ...

  describe ", duplicate codes" do
    before do
      [email protected]
      #Here lays the problem since this calls `generate_codes` 
      #before saving, so it ends up not being a duplicate!
      dup_ticket.save  #<--- 
    end
    it{ should_not be_valid}
  end

end

I was considering the use of after_initialize, but this is called everytime the object is instantiated, i.e. after being extracted from the DB, which is not desired. I was wondering, why is generate_codes called even when there is an on: :create at the filter callback?


Solution

  • Inspired by jklina's answer/comment, I found out that using update_attribute helps in this case, and I can update the field without triggering the before validation callbacks. So the test turns into:

    require 'spec_helper'
    
    describe Ticket do
      before do
        @ticket=Ticket.create
      end
      subject{@ticket}
    
      it{ should respond_to(:code)}
      ...
    
      describe ", duplicate codes" do
        before do
          [email protected]
          dup_ticket.update_attribute(:code, @ticket.code)
          dup_ticket.update_attribute(:sec_code, @ticket.sec_code)
          dup_ticket.update_attribute(:series, @ticket.series)
        end
        it{ should_not be_valid}
      end
    
    end
    

    Hence reflecting the same value for code, sec_code, and series at the ActiveRecord table. And the test does what it's expected it to.