Rails 4 on OSX

From Zero to Hello World

Development with Ruby on Rails can be a very productive and rewarding experience, but it has gained a reputation among the development community as being very painful to initially set up, especially if you are using Rails for the first time. A recent factory reset on OSX means coaxing Ruby 2 and Rails 4 to play nicely with each other once again, so I’ve decided to take some extra time and document each step along the way for reference. The result should be a working rails project.

Step 1: Homebrew

Go over to http://brew.sh and install and update homebrew.

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

Running brew doctor, warns that I haven't accepted the XCode license and don't have XCode's Command Line Tools.

Step 2: XCode + Command Line Tools

Install the latest version of XCode from the App Store, fire it up and open Preferences. Under the Downloads tab, install Command Line Tools.

The default gcc compiler installed with XCode works, but some features are buggy, so let's grab Unix's standard command line tools, as well as a stand alone version of gcc-4.2.

$ brew tap homebrew/dupes && brew install apple-gcc42

Running brew doctor again reveals another warning that /usr/bin occurs before /usr/local/bin. To fix this error, add the following to ~/.profile

homebrew=/usr/local/bin:/usr/local/sbin
export PATH=$homebrew:$PATH

Running brew doctor should reveal the system is "ready to brew".

Step 3: RVM (Ruby Version Manager)

Install RVM by running the following in terminal.

$ bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

Now add the following to your ~/.profile.

[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"

Restarting the terminal, let's confirm RVM is actually there.

$ rvm -v

rvm 1.21.8 () by Wayne E. Seguin <wayneeseguin@gmail.com>, Michal Papis <mpapis@gmail.com> [https://rvm.io/]

Step 4: Ruby 2.0

At this point, we don't have Ruby 2 yet.

$ ruby -v

ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin11.0]`

Let's upgrade to Ruby 2.

$ rvm install ruby-2.0.0-p247

Assuming all goes smoothly, running rvm list should show Ruby 2.

$ rvm list

rvm rubies

=* ruby-2.0.0-p247 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

Just to make sure…

$ ruby -v

ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin11.4.2]

Neat.

Step 5: Rails 4.0

We don't yet have rails, so let's install it.

$ gem install rails --version 4.0.0

Sanity check.

$ rails -v

Rails 4.0.0

Step 6: Test Drive

Let's try setting up a brand new rails project.

$ rails new TestDrive && cd TestDrive

Make sure all gems are updated.

$ bundle update

Now start up your rails server.

$ rails server

Navigate to localhost:3000 and you should see the "Welcome Aboard" page.

Rails 4 on OSX

Environment-Aware YAML Config With a Side of ERB

Hard coded values such as database credentials and other common values duplicated between files can become messy and hard to maintain, but given a few minutes of work you can have a clean, dynamic configuration file that is easy to work with.

Rails Global Variables

The first and probably least desirable way to use global variables in Rails is to define them in your environment configuration file, like so.

# config/environment.rb

module AwesomeSite
  class Application < Rails::Application
    api_key = '1234'
    ...
  end
end

Using this method we can access the global variable in a javascript file or stylesheet, for example, like this.

<%= AwesomeSite::Application::api_key %>

Not only is this ugly and verbose but it will become quite messy after you begin to add more configuration variables, or if you'd like to make them environment specific.

The YAML Configuration File

Declaring all your configuration variables in a YAML file is much better solution. Start by creating your YAML configuration file.

# config/config.yml

api_key: '1234'

Now we create an initializer for parsing the YAML file. All files in the config/initializers directory are initialized automatically upon starting the server. Note you'll have to restart your rails server to see any changes in your config.

# config/initializers/config.rb

APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")

Now we can be more concise.

<%= APP_CONFIG['api_key'] %>

Environment Awareness

We can go a step further and define configuration variables specific to which environment we are in.

# config/config.yml

development:
  db_username: dev_user
  db_password: dev_pass

test:
  db_username: test_user
  db_password: test_pass

production:
  db_username: production_user
  db_password: production_pass

Now edit your initializer to only load the configuration settings for the current environment.

# config/initializers/config.rb

  APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[Rails.env]

We can improve the above by defining a group of common variables, and passing those to each environment to prevent redundancy.

# config/config.yml

common: &common
  api_key: 1234

development:
  <<: *common
  db_username: dev_user
  db_password: dev_pass

test:
  <<: *common
  db_username: test_user
  db_password: test_pass

production:
  <<: *common
  db_username: production_user
  db_password: production_pass

Using YAML and ERB

In order to use Ruby code in your YAML file in the same way you would in a View, we'll manually parse the contents of the config using ERB, and load the result with YAML.

# config/initializers/config.rb
require 'yaml'

raw_config = File.read("#{Rails.root}/config/config.yml")
erb_result = ERB.new(raw_config).result
APP_CONFIG = YAML.load(erb_result)[Rails.env]

Now we can have dynamic values!

# config/config.yml

common: &common
  api_key: 1234
  some_path: <%= RAILS_ROOT %>/example/dir

...

OpenStruct

Even with the above, we are still accessing our config variables like so.

<%= APP_CONFIG['api_key'] %>

We can clean this up a bit by using OpenStruct, which is available in stdlib. In order to use OpenStruct, simply require it at the top of your config.rb, and pass the results of YAML.load() to it's constructor.

# config/initializers/config.rb
require 'ostruct'
require 'yaml'

raw_config = File.read("#{Rails.root}/config/config.yml")
erb_result = ERB.new(raw_config).result
config = YAML.load(erb_result)[Rails.env]
APP_CONFIG = OpenStruct.new(config)

Available in a one-liner, if you prefer.

# config/initializers/config.rb
require 'ostruct'
require 'yaml'

APP_CONFIG = OpenStruct.new(YAML.load(ERB.new(File.read("#{Rails.root}/config/config.yml")).result)[Rails.env])

Now we can access our configuration variables in a much cleaner fashion.

<%= APP_CONFIG.api_key %>