views:

394

answers:

1

I have problems to get rspec running properly to test validates_inclusion_of my migration looks like this:

class CreateCategories < ActiveRecord::Migration
  def self.up
    create_table :categories do |t|
      t.string :name
      t.integer :parent_id
      t.timestamps
    end
  end

  def self.down
    drop_table :categories
  end
end

my model looks like this:

class Category < ActiveRecord::Base
  acts_as_tree

  validates_presence_of :name
  validates_uniqueness_of :name
  validates_inclusion_of :parent_id, :in => Category.all.map(&:id), :unless => Proc.new { |c| c.parent_id.blank? }
end

my factories:

Factory.define :category do |c|
  c.name "Category One"
end

Factory.define :category_2, :class => Category do |c|
  c.name "Category Two"
end

my model spec looks like this:

require 'spec_helper'

describe Category do
  before(:each) do
    @valid_attributes = {
      :name => "Category"
    }
  end

  it "should create a new instance given valid attributes" do
    Category.create!(@valid_attributes)
  end

  it "should have a name and it shouldn't be empty" do
    c = Category.new :name => nil
    c.should be_invalid
    c.name = ""
    c.should be_invalid
  end

  it "should not create a duplicate names" do
    Category.create!(@valid_attributes)
    Category.new(@valid_attributes).should be_invalid
  end

  it "should not save with invalid parent" do
    parent = Factory(:category)
    child = Category.new @valid_attributes
    child.parent_id = parent.id + 100
    child.should be_invalid
  end

  it "should save with valid parent" do
    child = Factory.build(:category_2)
    child.parent = Factory(:category)
    # FIXME: make it pass, it works on cosole, but I don't know why the test is failing
    child.should be_valid
  end
end

I get the following error:

'Category should save with valid parent' FAILED Expected #<Category id: nil, name: "Category Two", parent_id: 5, created_at: nil, updated_at: nil> to be valid, but it was not Errors:

Parent is missing

On console everything seems to be fine and work as expected:

c1 = Category.new :name => "Parent Category"
c1.valid? #=> true
c1.save #=> true
c1.id #=> 1
c2 = Category.new :name => "Child Category"
c2.valid? #=> true
c2.parent_id = 100
c2.valid? #=> false
c2.parent_id = 1
c2.valid? #=> true

I'm running rails 2.3.5, rspec 1.3.0 and rspec-rails 1.3.2

Anybody, any idea?

+2  A: 

The problem is that you can't put a call to Category.all.map(&:id) inside the called to validates_inclusion_of.

The first indication that this is the case will be apparent when you try to run

rake db:migrate:down VERSION=<n>
rake db:migrate:up VERSOIN=<n>

where <n> is the version number of the migration that creates the Category model.

You will get something like:

in /Users/sseefried/tmp/so)
==  CreateCategories: reverting ===============================================
-- drop_table(:categories)
    -> 0.0032s
==  CreateCategories: reverted (0.0034s) ======================================

(in /Users/sseefried/tmp/so)
rake aborted!
SQLite3::SQLException: no such table: categories: SELECT * FROM "categories" 

(See full trace by running task with --trace)

This is because rake tries to load app/models/category.rb before running the migration. Because the Category model does not exist it fails.

Another way to see the problem is to do tail -f log/development.log and then try to open a console with script/console. You will see an SQL query of the form:

SELECT * FROM "categories"

in the output. This corresponds to the call to Category.all.map(:&id). However, once you start typing commands like:

c1 = Category.new, :name => "Category 1"

you will see that the query SELECT * from "categories" does not reappear in the log. The moral of the story is only constants can appear in calls to validations_inclusion_of because the code in there will only be evaluated once..

The only reason your console code worked was because, in a previous console session, you had created a Category object with id=1

You can write a custom validation that does what you want with:

validate :parent_exists

protected

def parent_exists
  ids = Category.all.map(&:id)
  if !parent_id.blank? && !ids.member?(parent_id)
    errors.add(:parent_id, "does not point to a valid parent record")
  end
end

Your rspec tests will pass once you have added this code.

Sean Seefried
Thank you, for your full explanation, now everything works like it should.
jigfox