Tuesday, 16 March 2010

Rails - Day 5 - Rails Migrations

Rails  - Day 1 - The RubyGem Love Story
Rails -  Day 10 - Testing Rails Controllers
Rails -  Day 13 -ActiveRecord Relationships Part II
Rails - Day 15 - Rails Forms Part II
In the last post I introduced the concept of generators and how they help the workflow of the rails developer.

Also in the last post, I mentioned that one of the files that rails generated for us was the rather cryptically named 20100307123141_create_users.rb which which can be found in the db/migrate folder.

The observant amongst you will have noticed that this is a time stamped file with our resource (user) included in the name.  The name of the file is important as it is yet another Rails convention.  As our application increases, so will the number of migrations.  The timestamp tells Rails the order to run the files.  

So what is this crazy concept of migrations all about?  

Below is a look at the above file

A migration is a ruby script that could be classified as a DSL.  Rails will take care of all the lowlevel SQL for you. This is all done platform agnostic.  We can in theory switch from MySQL to SQL server with ease.  I must add the caveat that I have never done this.

You can see from the above that there are two methods to the migration, up and down.  The up method tells Rails what to do when migrating up and the down tells rails what to do when rolling back.

It is worth noting that the script generator has only generated the files.  The database migration does not take place until we run the file.  It is now time to introduce another hero in this rails story which you may well have heard of.  I speak of the one they call RAKE.

Rake is Ruby's automation and task-running utility. Rake is what first pricked my interest in Ruby on Rails.  After years of fighting with nant's horrendous Xml syntax, moving to Rake proved to be a breath of fresh air.  If you are currently using Rake to build your .NET projects then you might possibly be unaware of just how integral rake is to the workflow of a Rails project.  I am going to devote another post to Rake, so I will just leave it at that.  You might not be totally surprised that when you generate a rails project, you get a rake file in the root of the project that is ready for use.  We are again being prompted to have a build script.  I have watched developers copy .dlls directly from the bin folders and do manual changes to the web.config before releasing to production.  Shame on you!!  One of your first tasks in any project is to have a way of building everything.

To see what commands are available to us for rake that are migration related, we can simply type the following into the command prompt rake -T db:migrate which will display something similar to the following:

We can see from the above that we have rake tasks that correspond to up and down methods that are methods in our migration script.

Let us run our first migration.  I enter the command rake db:migrate and get the following confirmation:

So a table has been created, but where?  We are in the land of abstractions do not forget.  Remember that we are executing these migrations on the development environment and unless we tell rails otherwise, Rails will use the SQLite3 engine for our development db and test environments.  If we open our db folder, we can see there is a new development.sqlite3 file.

OK great, we have created a table which is all well and good but what is the workflow if we want to add or remove columns for our table.  What we don't want to do is to start messing about with the database scheama ourselves.  We want to stay within migrations.  This way our migrations are available to other developers who can run the scripts and we also have a complete upgrade and rollback plan if needs be.  Staying with the scenario of our example, we are going to add a date_of_birth date field to our user.

Our first step in making the required change to the schema is to use a rails generator named unsurprisingly migration to generate us a migration stub.  We simply issue the command ruby script/generate migration add_date_of_birth_to_users which will result in the following confirmation:

If we look at our db/migrate folder, we can see the generated file:

We now need to add the following code to the self.up method of the newly created migration and the code for the down method of the newly created migration:

We now need to run the migration with the same command as we used last time, namely rake db:migrate.  This results in the following confirmation:


Before we leave the topic of rails migrations which is one well worth digging deeper into, I want to bring up the convention Rails uses for seeding or initialising your database with test or look up data.

If we look in the db folder of our rails application you will notice that by default rails has added a seeds.rb file:

It is this file which will contain our initial data.  There is also a rake task named rake db:seed that will execute the command.  Guess what, this is another rails convention that I find really exciting.  Maybe I should get out more but this says to me how serious rails is about conventions.  It even has a convention for seed data. 

To add some test users to the users table, I add the following code:

User.create!(:user_name => "admin", :password => "password", :full_name => "Paul Cowan", :emailaddress => "dagda1@scotalt.net", :date_of_birth => Date.new(1970, 12, 5))

If I issue the command rake db:seed then the above code in the seeds.rb file will be ran.  Nothing exciting here but a nice convention none the less.

And that is all I have to say about migrations for now.  As usual the underpinnings of migrations are down to rails conventions.

The Rails Console

Another great utility I find exciting that is created for you when you create a rails project, is the rails console. It is a lot like the interactive shell irb in that it lets you issue commands with immediate response. The difference with the rails console is that it is running in the context of your rails web application. This means that, in addition to evaluating ruby interactively, you can do things like manipulate your application's database.In order to start the rails console, we cd into our application directory and issue the command ruby script/console:

As we have just created and seeded our virgin database, we can check that the data is where we think it is by running some ruby through the command prompt. We are going to touch on the Ruby ORM ActiveRecord later.
In order to retrieve all the users that are are currently in the users table of our db, we are input the following ruby text User.all which will list all the records in the users table of our SQLLite3 development database. You can think of the all method as a static method on the User class that will return all records in the user table and load each record into a user object for us:

Obviously, the exciting thing here is that we are running these commands in the context of our rails application.  This is a great scratch pad to test and query.

The Rails Logs

Another great tool that you get out of the box is logging already configured and ready to go. If you look in your project folder, you will see a ~/log folder that contains log files for all our application environments:

If we want to examine what SQL was generated from our ActiveRecord statements like the User.all statement we just issued, we can simply look in the specific environment log in question, in our case development log. Below, you can see the actual SQL issued:

If you are familiar with Nhibernate, you can contrast this with having to configure your application in order to read the sql statements, you need to set up log4net and set the show_sql = 'true' etc.

In Rails, everything is already configured and logging.  This is something I find very frustrating when I return to .NET land.  Nothing is configured and everything is left to you, the beleaguered user.

In this post, we have introduced migrations, the rails application console and logging.

In my next post, I want to move onto views in rails.

No comments:

Post a Comment