WideFix tech post

Do not give name Test for your models

There is a list of reserved words for Ruby On Rails framework. Full list is here. But it doesn’t contain at least one word which I will describe in this post. There are many reasons to don’t give name Test for your models in Ruby On Rails. I will try to describe in this post problems which you will have if you have model with Test name.

Do not give name Test for your models

Let’s start with creating new application:

~$ rails new test_app --skip-test-unit --old-style-hash --skip-javascript --skip-bundle --skip-git
~$ cd test_app
~/test_app$ bundle install

Make models Test and TestPart:

test_app$ rails g model test name:string
test_app$ rails g model test_part name:string test_id:integer
test_app$ rake db:migrate

Reason 1: Test is not a class. It is a module!

I want to debug my code. So I’m going to rails console by typing rails c in terminal and trying to find all Test instances in my database (I did’t create one that way I have to get blank collection):

irb(main):001:0> Test.all
NoMethodError: undefined method `all' for Test:Module

What is happened? I’m trying to get all tests in my db but I get error instead of empty collection. We will find out for this question in rails console:

irb(main):002:0> Test.class
=> Module

This is a module but not a class how we expected. Module Test exist in TestUnit and even we aren’t using TestUnit in our application we will have this confusion. To avoid this negative situation we can rename class Test to MyTest for example. Let’s do it manually:

test_app$ mv app/models/test.rb app/models/my_test.rb

Remember if we have model MyTest it would connect with table my_tests but we don’t have this table. So we have 2 ways to solve this problem:

  1. Rename table tests to my_tests. We should create new migration to do it
  2. Set custom table name for model MyTest. In our case it is tests

I will use 2nd variant because it will take less time:

app/models/my_test.rb

class MyTest < ActiveRecord::Base
  self.table_name = 'tests'
end

You must use here self.table_name but not table_name =. It won’t give expected effect in another case.

Try to test what we did and how it works now:

irb(main):003:0> reload! # we have changes in code so we have to reload console to get changes here
irb(main):004:0> MyTest.all
  MyTest Load (0.1ms)  SELECT "tests".* FROM "tests"
=> []

And, yes! It’s working now correctly!

Reason 2: validates uniqueness in scope

Assume MyTest has_many TestParts and TestPart belongs to MyTest:

app/motest/test_part.rb

class TestPart < ActiveRecord::Base
  attr_accessible :name, :test_id
  belongs_to :test, :class_name => 'MyTest'
end

app/models/my_test.rb

class MyTest < ActiveRecord::Base
  attr_accessible :name
  self.table_name = 'tests'
  has_many :test_parts, :foreign_key => 'test_id'
end

Check it out:

irb(main):009:0> reload!
irb(main):010:0> t = MyTest.new(:name => 'My first test')
=> #<MyTest id: nil, name: "My first test", created_at: nil, updated_at: nil>
irb(main):011:0> t.test_parts.build(:name => 'Test part 1')
=> #<TestPart id: nil, name: "Test part 1", test_id: nil, created_at: nil, updated_at: nil>
irb(main):012:0> t.test_parts.build(:name => 'Test part 2')
=> #<TestPart id: nil, name: "Test part 2", test_id: nil, created_at: nil, updated_at: nil>
irb(main):013:0> t.save
=> true
irb(main):014:0> t.test_parts
=> [#<TestPart id: 1, name: "Test part 1", test_id: 1, created_at: "2012-03-02 22:49:51", updated_at: "2012-03-02 22:49:51">, #<TestPart id: 2, name: "Test part 2", test_id: 1, created_at: "2012-03-02 22:49:51", updated_at: "2012-03-02 22:49:51">]

Perfect! Everything is working.

Let’s try to add validation for test parts name’s uniqueness in scope test_id:

app/motest/test_part.rb

class TestPart < ActiveRecord::Base
  attr_accessible :name, :test_id
  belongs_to :test, :class_name => 'MyTest'
  validates :name, :uniqueness => {:scope => :test_id}
end

Try to save the same test partials to my test:

irb(main):016:0> reload!
irb(main):017:0> t = MyTest.create(:name => 'My second test')
=> #<MyTest id: 2, name: "My second test", created_at: "2012-03-02 23:06:30", updated_at: "2012-03-02 23:06:30">
irb(main):018:0> t.test_parts.build(:name => 'Test part')
=> #<TestPart id: nil, name: "Test part", test_id: 2, created_at: nil, updated_at: nil>
irb(main):019:0> t.test_parts.build(:name => 'Test part')
=> #<TestPart id: nil, name: "Test part", test_id: 2, created_at: nil, updated_at: nil>
irb(main):020:0> t.save
=> true
irb(main):021:0> t.test_parts
=> [#<TestPart id: 3, name: "Test part", test_id: 2, created_at: "2012-03-02 23:06:56", updated_at: "2012-03-02 23:06:56">, #<TestPart id: nil, name: "Test part", test_id: 2, created_at: nil, updated_at: nil>]

Ooops! We have trouble here. We added uniqueness validation for name field test parts and we should able to save only one test part with same name for my test. But… we did it!

To fix this problem we have 2 ways:

  1. Rename table tests to my_tests and remove line self.table_name = 'tests' from app/models/my_test.rb
  2. Rename colum test_id to my_test_id and fix code according this changing: use everywhere test_id instead of my_test_id in application

I think it is very important to find this bugs in your application before you started write it and use in production. So I hope these rails confusions described in my post will help you to avoid problems with coding in the future.

That’s it what I wanted to tell you today. See you in next posts!

Are you seeking assistance with Ruby on Rails development?

Read also