Modify an existing database column in a Laravel migration

I’ve been messing around with Laravel – the ‘Clean & Classy PHP Framework’… I’m not totally sold yet, but there is a lot to like, so I am persevering.

One useful feature that Laravel includes is migrations – in essence the ability to place your database schema under version control along with your code.

Laravel handles migrations though it’s CLI ‘Artisan

Rather inexplicably (to me at least*) the migrations tool – Schema Builder does include the ability to modify columns once they have been created, however you can use use raw SQL as a workaround.

*Schemas evolve. I find myself often adding columns, tweaking existing columns regularly and dropping columns rather rarely…

Let’s build some migrations…

First things first – create the migrations table. This is used to keep track of migration within your application.

1. Open a terminal in the root of your application.

2. Create the migrations table:

 
php artisan migrate:install

3. Create an empty migrations script

 
php artisan migrate:make create_users

create_users is simply a descriptive name for your migration – you can call it anything you want…

This will create a skeleton file in /path_to_laravel/application/migrations that includes the timestamp in it’s name e.g.

2013_05_23_201939_create_users.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
class Create_Users {
 
	/**
	 * Make changes to the database.
	 *
	 * @return void
	 */
	public function up()
	{
		//
	}
 
	/**
	 * Revert the changes to the database.
	 *
	 * @return void
	 */
	public function down()
	{
		//
	}
 
}

This isn’t much use – we need to:

4. Write our migration – edit the file that we just created. Create the table and insert some test data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
class Create_Users {
 
	/**
	 * Make changes to the database.
	 *
	 * @return void
	 */
	public function up()
	{
		Schema::create('users',function($table){
			$table->increments('id');
			$table->string('username', 20); 
			$table->string('email', 255); 
			$table->string('password', 255); 
			$table->boolean('active');
			$table->timestamps(); 
		});
 
		DB::table('users')->insert(array(
					'username' => 'admin',
					'email' => 'test@test.com',
					'password' => Hash::make('123456'),
					'active' => 1
 
		));
	}
 
	/**
	 * Revert the changes to the database.
	 *
	 * @return void
	 */
	public function down()
	{
		//
	}

5. Run the migration:

 
php artisan migrate

6. Ooops – forgotten to set a default on one of the fields.

Unfortunately you can’t change this via the Schema Builder, but we can still use another migration:

 
php artisan migrate:make update_users

This will create a file called something like 2013_05_23_202930_update_users.php

7. We need to write our migration. This needs to be actual SQL which we call with DBquery e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
class Modify_Users {
 
	/**
	 * Make changes to the database.
	 *
	 * @return void
	 */
	public function up()
	{
		DB::query("ALTER TABLE `ltest`.`users` CHANGE COLUMN `active` `active` tinyint(1) NOT NULL DEFAULT '1';");
 
		DB::table('users')->insert(array(
				'username' => 'test2',
				'email' => 'test2@test.com',
				'password' => Hash::make('123456'),
 
 
		));
	}
 
	/**
	 * Revert the changes to the database.
	 *
	 * @return void
	 */
	public function down()
	{
		//
	}
 
}

I’ll insert another user and see if the default for the ‘active’ column is set correctly.

8. Run the migrations again.

 
php artisan migrate

Done!

Installing Homebrew & Fabric on OSX

I recently started using Fabric to simplify my deployment process.

Fabric is a Python (2.5 or higher) library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks.

I quite like Fabric – I’m not 100% sold, but it’s easy to get started with and it won’t be long until you can write complex scripts.

Being able to deploy a complex app with a single command is great. I do not miss long checklists and having to run through step after step in a hurry.

fab deploy

Pretty easy isn’t it?

Why Fabric? To be honest – why not. I know a little bit of Python, so why not learn a bit more? I like the glueyness, the fact that it (mostly) lets you get on with things and doesn’t force you to work in a particular way.

I had a devil of a time getting it up and running though on my Mac running 10.6.8 (Snow Leopard).

Getting started…

Homebrew http://mxcl.github.io/homebrew/
is a package manager for OSX and alternative to MacPorts. Install Fabric and Python via Homebrew to (hopefully) make your life a bit easier…

Install Homebrew

Open a terminal. Run the commands as your user, not sudo

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

Install Homebrew and then check the install.

brew doctor

Running brew doctor will identify any problems with your install. Any problems are displayed. Work through any issues it flags up one by one. In my case I ended up having to remove MacPorts and had to reinstall Xcode.

Remove MacPorts

MacPorts is another package manager, but apparently it often doesn’t play with Homebrew.

Follow the instructions http://guide.macports.org/chunked/installing.macports.uninstalling.html

sudo port -fp uninstall installed
sudo rm -rf \
    /opt/local \
    /Applications/DarwinPorts \
    /Applications/MacPorts \
    /Library/LaunchDaemons/org.macports.* \
    /Library/Receipts/DarwinPorts*.pkg \
    /Library/Receipts/MacPorts*.pkg \
    /Library/StartupItems/DarwinPortsStartup \
    /Library/Tcl/darwinports1.0 \
    /Library/Tcl/macports1.0 \
    ~/.macports

Remove Xcode

Xcode is Apple’s Integrated Development Environment (IDE) containing tools for developing software for OS X and iOS.

Based on issues listed by brew doctor I removed Xcode and the re-installed it.

sudo /Developer/Library/uninstall-devtools --mode=all

Make sure you have your Apple ID to hand if you don’t have the .dmg and need to download it from Apple.

Install Git

Next I installed Git using Homebrew if you do not already have it (I use Mercurial day to day, so I didn’t)

brew install git
brew upgrade git

Install python

brew install python

Update your paths to use the version of Python that you’ve just installed.

sudo nano /etc/paths
nano ~/.bashrc

Install Fabric

pip install fabric

Edit: To install Fabric you need to use the pip package manager which was installed as part of Python.

Phew! Everything should be working now… time to write a fabfile.py

Default form values in Rails

One of the problems I initially ran into using Rails was the problem of how the hell you set initial values for your forms? (For example a select menu with a default value chosen but which a user can change). It turns out that as long as you don’t try and set the values in your views, it’s easy and there are several solutions.

As an example I started off with a classic Post model, then moved on to Ghost and Roast to try out alternatives (well you’ve got to call your models something…) I generated an identical scaffold for each and then ran a migration e.g.

 
rails generate scaffold Post name:string title:string type_id:integer
 
rake db:migrate

1. Use model after_initialize

/app/models/post.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
class Post < ActiveRecord::Base
  attr_accessible :name, :title, :type_id
  validates :name,  :presence => true
 
  after_initialize :init
 
  private
    def init
      if self.new_record? && self.type_id.nil?
        self.type_id = 5
      end
    end
 
end

Edit the model and add an after_initialize callback. This callback is called after a new object is instantiated. The init method sets the default value, but only if the value has not been set yet.

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

2. Set the default in the controller

/app/controllers/ghosts_controller.rb

1
2
3
4
5
6
7
8
9
10
  # GET /ghosts/new
  # GET /ghosts/new.json
  def new
    @ghost = Ghost.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.json { render :json => @ghost }
    end
  end

Using this alternative solution, you simply set the default value in the new method using @ghost.type_id = 5. This value will only ever be set before the new view for this controller is created.

1
2
3
4
5
6
7
8
9
10
11
  # GET /ghosts/new
  # GET /ghosts/new.json
  def new
    @ghost = Ghost.new
    @ghost.type_id = 5
 
    respond_to do |format|
      format.html # new.html.erb
      format.json { render :json => @ghost }
    end
  end

This solution is easy implement and has the advantage that what you are doing is obvious – the default isn’t tucked away where you might forget about it.

3. Set the default in the database

When the rails generate command was run, a migration was automatically created:

/db/migrate/20130209234708_create_roasts.rb

1
2
3
4
5
6
7
8
9
10
class CreateRoasts < ActiveRecord::Migration
  def change
    create_table :roasts do |t|
      t.string :name
      t.string :title
      t.integer :type_id
      t.timestamps
    end
  end
end

We can edit the migration to set a default value here e.g.

1
2
3
4
5
6
7
8
9
10
class CreateRoasts < ActiveRecord::Migration
  def change
    create_table :roasts do |t|
      t.string :name
      t.string :title
      t.integer :type_id, :default => 5
      t.timestamps
    end
  end
end

Conclusion

Here are three ways of setting the default values. What you choose to do is up to you, I think it is important that a solution is consistent with the rest of the code that you are working with (as long as it isn’t bad code). Maintainability and clarity shouldn’t be neglected and I subscribe to the school of thought that you should always keep things simple – when complicated problems crop up, write complicated solutions, but don’t complicate things for the sake of it.

Personally I would be disinclined to use the final method because I think it would be easy to forget about.

Creating a method in the model may be overkill most of the time, but if you are setting a lot of default values or your default values change with context, then setting the default values in the method makes sense to me.

These code examples were built with Ruby on Rails 3.2.9