Manuel van Rijn

Bit blog

Developing Ruby on Rails on Windows

I do a lot of C# web development which means I have to work with Visual Studio on Windows. I usually grab the Mac to do my Ruby on Rails development, but it would be easier not to switch every time I have to do C# and Ruby development.

A while ago I tried several things getting a fast Rails experience on Windows, but it failed. It was just too slow … until now! In this post I’d like to share my current setup which I plan to use as long as it pleases me.

What are we going to do?

We are going to install Vagrant which uses VirtualBox to build a complete development environment. Also we will create and use some recipes for Chef to automatically install and configure the development environment. Think of installing Ruby, PostgreSQL, and MySQL etc. automatically.

Installing Vagrant and VirtualBox

First let’s setup our Windows machine with the needed software.

  1. Install VirtualBox
  2. Install Vagrant
  3. After reboot add the VirtualBox folder to your PATH variable
    (something like: c:\Program Files\Oracle\VirtualBox)
  4. Install a base box (I used Ubuntu precise 64 VirtualBox) with the following command vagrant box add precise64 http://files.vagrantup.com/precise64.box

Prepare your Rails project

Next we have to configure our project to create a box from the base box and make it install all our goodies we need to run our Rails application.

Setup Chef (Librarian)

I’m using a gem called librarian-chef which actually is a bundler for your Chef recipes.

To install run the following from the root of you project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Install librarian-chef
gem install librarian-chef

# Add its work directories to your gitignore
echo chef/cookbooks >> .gitignore
echo chef/tmp >> .gitignore
# OPTIONAL: you can add the Cheffile.lock which holds the currently installed versions
#           just like bundler does
echo chef/Cheffile.lock >> .gitignore

# Create the Librarian config file and needed folders
mkdir -p chef
cd chef
mkdir -p roles
mkdir -p site-cookbooks
librarian-chef init

WUT?! gem install librarian-chef?

I know we are trying to create a non-Ruby Windows solution but we do need this gem to work on Windows to complete our setup with Chef. If you really don’t want Ruby being installed on Windows, you should do the above steps on UNIX or OSX and also have to commit the cookbooks. This means we’re back at 1900 where everybody stuffed there vendor folder with code they found… If you do want to perform this on Windows and haven’t already installed Ruby, you should do this now (I suggest RubyInstaller).

Configure Chef

The command librarian-chef init created the file chef/Cheffile where we can add our cookbooks. Mine contains the following:

chef/Cheffile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env ruby
#^syntax detection

site 'http://community.opscode.com/api/v1'

cookbook 'apt'
cookbook 'git'
cookbook 'sqlite'
cookbook 'mysql'
cookbook 'postgresql'
cookbook 'database', :git => 'git://github.com/manuelvanrijn/cookbook-database.git', :ref => 'grant-roles'
cookbook 'nodejs'
cookbook 'build-essential'
cookbook 'ruby_build'
cookbook 'rbenv', :git => 'git://github.com/fnichol/chef-rbenv.git', :ref => 'v0.7.2'

As you can see I’ve setup Git, Ruby with rbenv, SQLite, MySQL, PostgreSQL and node.js for the asset pipeline. The apt cookbook is for running apt-get dist-upgrade and the cookbook database will be used later to create users to access the databases.

You could skip one or two of the database servers if you like, but it can’t harm if you do install them all and don’t use them.

Note: the database cookbook is provided with additional actions to set superuser privileges to PostgreSQL and MySQL.

Downloading the cookbooks

Now that we have defined our cookbooks we need to run our final command so that Chef downloads them. Just as you would do with bundler we now run the following command from our chef/ folder

1
librarian-chef install

Adding our custom recipes

I had some difficulties with running bundle install because the Vagrant user didn’t have permissions on the rbenv gem folder, so I had to add a custom recipe for this. Also I’ve added a recipe to modify the pg_hba.conf file of PostgreSQL so that we can connect with the virtual machine. Finally I’ve created a file to add our database users with full permissions. Here are the 3 files I added:

chef/site-cookbooks/database/recipes/default.rb
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
38
39
40
41
42
43
44
45
46
47
48
49
# Setup database servers
#
# 1. Setup MySQL and PostgreSQL
# 2. Create a rails user with a blank password with full control

# MySQL
mysql_connection_info = {
  :host     => "localhost",
  :username => 'root',
  :password => node['mysql']['server_root_password']
}

# 'rails'@'localhost'
mysql_database_user 'rails' do
  connection mysql_connection_info
  password ''
  host 'localhost'
  privileges ["ALL PRIVILEGES"]
  grant_option true
  action :grant
end

# 'rails'@'%'
mysql_database_user 'rails' do
  connection mysql_connection_info
  password ''
  host '%'
  privileges ["ALL PRIVILEGES"]
  grant_option true
  action :grant
end

# PostgreSQL
template "#{node[:postgresql][:dir]}/pg_hba.conf" do
  source "pg_hba.conf.erb"
  notifies :restart, "service[postgresql]", :immediately
end

postgresql_connection_info = {
  :host     => "localhost",
  :password => node['postgresql']['password']['postgres']
}

postgresql_database_user 'rails' do
  connection postgresql_connection_info
  password ''
  role_attributes :superuser => true, :createdb => true
  action :create
end
chef/site-cookbooks/database/templates/default/pg_hba.conf.erb
1
2
3
local   all             all                                     trust
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
chef/site-cookbooks/postinstall/recipes/default.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
group "rbenv" do
  action :create
  members "vagrant"
  gid 1100
  append true
end

bash "chgrp and chmod" do
  user "root"
  cwd "/usr/local"
  code <<-EOH
    chgrp -R rbenv rbenv
    chmod -R g+rwxX rbenv
  EOH
end

Adding our role

Next we have to create a role that defines what recipes should be executed in what order. Create the file chef/roles/rails-dev.rb containing the following content:

chef/roles/rails-dev.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name "rails-dev"
description "setup for ruby on rails core development"
run_list(
  "recipe[apt]",
  "recipe[git]",
  "recipe[sqlite]",
  "recipe[mysql::client]",
  "recipe[mysql::ruby]",
  "recipe[mysql::server]",
  "recipe[postgresql::ruby]",
  "recipe[postgresql::server]",
  "recipe[nodejs::install_from_binary]",
  "recipe[ruby_build]",
  "recipe[rbenv::system]",
  "recipe[rbenv::vagrant]",
  "recipe[database]",
  "recipe[postinstall]"
)
default_attributes(
  "build_essential" => {
    "compiletime" => true
  }
)

If you removed some cookbooks from the chef/Cheffile you should also remove them here.

Setup Vagrant

Next we need to create a Vagrantfile that contains the configurations for creating a box from the base box. Create the config file by running from the root of you project:

1
vagrant init precise64

Note: precise64 is the name we used with the vagrant box add command from step 4 above.

Also we want to add the virtual machine folder to our .gitignore file:

1
echo .vagrant >> .gitignore

Modify the Vagrantfile

Here’s the stripped version of how my file looks like:

Vagrantfile
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
38
39
Vagrant.configure("2") do |config|
  config.vm.box = "precise64"

  config.vm.box_url = "http://files.vagrantup.com/precise64.box"

  config.vm.network :forwarded_port, guest: 3000, host: 3000  # forward the default rails port
  config.vm.network :forwarded_port, guest: 3306, host: 3306  # forward the MySQL port
  config.vm.network :forwarded_port, guest: 5432, host: 5432  # forward the PostgreSQL port

  config.vm.provision :shell, :inline => "gem install chef --version 11.6.0"

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["chef/cookbooks", "chef/site-cookbooks"]
    chef.roles_path     = [[:host, "chef/roles"]]

    chef.add_role "rails-dev"
    chef.json = {
      "mysql" => {
        "server_root_password"   => "",
        "server_debian_password" => "",
        "server_repl_password"   => ""
      },
      "postgresql" => {
        "password" => {
          "postgres" => ""
        }
      },
      "rbenv" => {
        "global" => "2.0.0-p247",
        "rubies" => ["2.0.0-p247"],
        "gems" => {
          "2.0.0-p247" => [
            { "name" => "bundler" }
          ]
        }
      }
    }
  end
end

Ready to rock!

Alright we’re ready. We now can boot up Vagrant which will take some time the first time because it will create a new box from the base box and install all the cookbooks. From then you’re able to connect with the box and use it just like you would do on UNIX or OSX.

1
2
3
4
5
6
7
8
9
10
11
12
# starts and setups the box
vagrant up

# ... now we wait ...

# connect to the box with ssh
vagrant ssh

# install gems and run the server on the box for a Rails application
cd /vagrant
bundle install
bundle exec rails s

What we’ve created

A VirtualBox with Ubuntu precise 64 installed and containing the following software/configurations:

  • Software:
    1. rbenv with ruby-build
    2. ruby-2.0.0-p247
    3. Git
    4. SQLite
    5. MySQL with user: rails with a empty password
    6. PostgreSQL with user: rails with a empty password
    7. node.js
  • Ports:
    1. localhost:3000 goes to vagrant-box:3000
    2. localhost:3306 goes to vagrant-box:3306
    3. localhost:5432 goes to vagrant-box:5432

Develop workflow

If you have to run specific Ruby (on Rails) tasks like rspec or bundle exec rails g migration you should do this within the ssh session of your box.

Editing files can be done on your Windows machine, because the root of you project is directly binded to the /vagrant/ folder on the box.

Comments