valibuk.net

A place for opinions and notes of a valibuk.

Using Migrations for Generating Initial or Test Data II.

Tagged with: — ondrej at 2:58 pm on Sunday, July 16, 2006

Ruby on RailsLast week I wrote a post how to use migrations to enter initial (or test) data into one table. Now I will show how does it work for two tables with a join.

Firstly, we will create the second model Employee; the User model defined in the previous post is the first model.

The Employee model represents the domain specific part of person details such as first name, surname and date of birth; the User model represents the technically specific part such as login name and password.


Two Tables

Create the second Employee model:

ruby script/generate model employee

I do not include the output, because it is very similar to the output for the User model.

The generated contains the default structure:

  1. class CreateEmployees < ActiveRecord::Migration
  2.   def self.up
  3.   end
  4.  
  5.   def self.down
  6.   end
  7. end

We will add the above mentioned domain specific data such as first name, surname and date of birth.

Of course, we have to somehow save the relationship between the two models too. This is done by adding a column of the integer type that should be named model_id, where the model prefix is a name of the referenced model — in our case the name of the coloumn is user_id.

The modified 003_create_employees.rb migration looks like:

  1. class CreateEmployees < ActiveRecord::Migration
  2.   def self.up
  3.     create_table :employees do |t|
  4.       t.column :user_id,            :integer, :null => false
  5.  
  6.       t.column :first_name,         :string, :limit => 60, :null => false
  7.       t.column :surname,            :string, :limit => 60, :null => false
  8.       t.column :date_of_birth,      :date
  9.       t.column :note,               :text
  10.     end
  11.   end
  12.  
  13.   def self.down
  14.     drop_table :employees
  15.   end

The Employee model, stored in the app/models/employee.rb file, is quite simple:

  1. class Employee < ActiveRecord::Base
  2.   belongs_to              :user
  3.   validates_presence_of   :first_name,
  4.                           :surname
  5. end

Line 2 defines the join between the two models. Lines 3 and 4 says that the first name and surname are mandatory.

It is also necessary to add information about the join to the User model:

  1. class User < ActiveRecord::Base
  2.   has_one :employee
  3.   #the rest of the source code
  4. end

When I started programming in RoR, I was suprised that it is necessary to declare a join between two models in both models. In EJB 3 (in 2.x too) it is enough to write the @OneToOne annotation for a getter method (for EJB 2.x in a descriptor file).

When the join is defined in the RoR models, the database does not know about it. If tables are not be used by another application then it is up to you if your database should know about it. A join between two tables is expressed by a foreign key.
To add a foreign key we have to alter the Employee migration (lines 11, 12 and 13 were added):

  1. class CreateEmployees < ActiveRecord::Migration
  2.   def self.up
  3.     create_table :employees do |t|
  4.       t.column :user_id,            :integer, :null => false
  5.  
  6.       t.column :first_name,         :string, :limit => 60, :null => false
  7.       t.column :surname,            :string, :limit => 60, :null => false
  8.       t.column :date_of_birth,      :date
  9.       t.column :note,               :text
  10.     end
  11.  
  12.     execute "alter table employees " +
  13.             "add constraint fk_employee_user " +
  14.             "foreign key (user_id) references users(id)"
  15.   end
  16.  
  17.   def self.down
  18.     drop_table :employees
  19.   end

The added lines execute an SQL code that adds the fk_employee_user foreign key (in general fs_this-model_referenced-model) that is stored in the user_id column and it references the id column in the user table — in other words: user_id column will contain a value of the id column from the user table. (The id column is added automatically by RoR. It is a primary key of a table. The employee table contains also one.)

It looks somehow complicated, doesn’t it? Well, you do not have to use it. It adds a double check if your data is correct. It will be checked by the RoR framework and by the database (that is a robust and well tested application used by many users).
A foreign key should be added when tables are accessed by another application, you execute additional SQL statements or your data is important for you.

And finally, let’s create a migration that will add some data to the Employee model:

script/generate migration add_employee_ondrej

It will generate the 004_add_employee_ondrej.rb migration with the default content:

  1. class AddEmployeeOndrej < ActiveRecord::Migration
  2.   def self.up
  3.   end
  4.  
  5.   def self.down
  6.   end
  7. end

To insert data we need to call the constructor of the Employee model in the up method. To fill out fields like first name is easy, but how to set a value for the user_id field? We can check a value of the id column in the user table and use it :), but there is a better solution: we can access the User model and use the automatically created find_by_login_name method (RoR automatically creates a find_by_field finder method for every field):

User.find_by_login_name("ondrej").id

This returns a value of the id field of the ondrej user.

Let’s use the finder method and fill out also other fields in the 004_add_employee_ondrej.rb migration:

  1. class AddEmployeeOndrej < ActiveRecord::Migration
  2.   def self.up
  3.     Employee.create(
  4.       :user_id => User.find_by_login_name("ondrej").id,
  5.       :first_name => "Ondrej",
  6.       :surname => "Jaura",
  7.       :date_of_birth => "1978-01-01".to_date,
  8.       :note => "A diligent and committed employee.")
  9.   end
  10.  
  11.   def self.down
  12.     Employee.delete_all
  13.   end
  14. end

In the down method we delete all employees.

Now we execute the two new migrations:

rake db:migrate

The output should look like:

  1. == CreateEmployees: migrating =================================================
  2. – create_table(:employees)
  3. NOTICE:  CREATE TABLE will create implicit sequence "employees_id_seq" for serial column "employees.id"
  4. NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "employees_pkey" for table "employees"
  5.    -> 0.1011s
  6. – execute("alter table employees add constraint fk_employee_user foreign key (user_id) references users(id)")
  7.    -> 0.0065s
  8. == CreateEmployees: migrated (0.1081s) ========================================
  9.  
  10. == AddEmployeeOndrej: migrating ===============================================
  11. == AddEmployeeOndrej: migrated (0.1136s) ======================================

Conclusion

It was shown how to use migrations for creating a model and (particularly) for inserting initial or test data to created models. The tutorial was divided into two parts: how to insert data to one table and how to insert data to two tables with a join.

Does this tutorial help you? Let me know :)

These icons link to social bookmarking sites where readers can share and discover new web pages.
  • del.icio.us
  • DZone
  • Digg
  • Reddit
  • Technorati
  • Furl
  • NewsVine
  • Slashdot
  • Ma.gnolia
  • StumbleUpon

1 Comment »

9

Comment by ondrej

July 31, 2006 @ 11:58 pm

There was a mistake:
User.find_by_login_name("ondrej")
returned always the 1 value.
The new code
User.find_by_login_name("ondrej").id
returns a correct id.

RSS feed for comments on this post. TrackBack URI

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Comment Preview


commercial break :)

Make an account on slicehost.com -- a really good hosting where you have your own virtual machine. I installed Gentoo there = I like it very much ;)