Subdomain accounts with Ruby on Rails explained
DHH recently posted, How to do Basecamp-style subdomains in Rails on SvN and it just happens that I was implementing some similar stuff this last week for a project we’re developing internally.
In our project, not everything needs to be scoped per-account as we are building a namespace for administrators of the application and also want a promotional site for the product. Three different interfaces, with some overlap between them all.
Let’s walk through a few quick steps that you can follow to setup the two interfaces within the same application.
Suppose that we’re going to build a new web-based product and have the following requirements initially.
- We need a promotional site for sign-ups, frequently-asked-questions, support requests, etc.
- When people sign-up for an account, they’ll should have their own unique sub-domain
- There are two different visual layouts (promotional site and the account)
Note: I use RSpec and am going to skip the TDD process here and let you conquer that for yourself. Am using the default Rails commands in this tutorial.
Account model / Database
We’re going to generate a new model for Account, which will be responsible for scoping sub-domains and individual accounts.
account-demo : ruby script/generate model Account subdomain:string
create app/models/
create test/unit/
create test/fixtures/
create app/models/account.rb
create test/unit/account_test.rb
create test/fixtures/accounts.yml
exists db/migrate
create db/migrate/20090111220627_create_accounts.rb
Great, let’s migrate our application.
account-demo : rake db:migrate
== CreateAccounts: migrating =================================================
-- create_table(:accounts)
-> 0.0045s
== CreateAccounts: migrated (0.0052s) ========================================
Before we get too far, let’s make sure that we’re adding an index on this table for the subdomain, as it’ll improve performance in the database as the subdomain will used in SQL conditions quite often.
account-demo : ruby script/generate migration AddIndexToAccountSubdomain
exists db/migrate
create db/migrate/20090111221009_add_index_to_account_subdomain.rb
Let’s open up this new migration file and toss in a UNIQUE INDEX on subdomain
.
class AddIndexToAccountSubdomain < ActiveRecord::Migration
def self.up
add_index :accounts, :subdomain, :unique => true
end
def self.down
remove_index :accounts, :subdomain
end
end
Okay, let’s migrate this bad boy.
account-demo : rake db:migrate
== AddIndexToAccountSubdomain: migrating =====================================
-- add_index(:accounts, :subdomain, {:unique=>true})
-> 0.0047s
== AddIndexToAccountSubdomain: migrated (0.0050s) ============================
Great, we’re now ready to move on to the fun stuff.
Let’s open up app/models/account.rb
and throw some sugar in it.
Data Validation
Because we’re going to be dealing with subdomains, we need to make sure that we’re only allowing people to sign-up with valid data otherwise, there could be issues. URLs need to fit within certain conventions and we need to make it as graceful as possible for our customers.
Let’s make a quick list of what we need to enforce for the subdomain
attributes. This can easily be expanded, but let’s cover the basics.
- Each account should have a
subdomain
- Each
subdomain
should be unique within the application - A
subdomain
should be alpha-numeric with no characters or spaces with the exception of a dash (my requirement) - A
subdomain
should be stored as lowercase
So, let’s update the following default Account model….
class Account < ActiveRecord::Base
end
..and add some basic validations.
class Account < ActiveRecord::Base
validates_presence_of :subdomain
validates_format_of :subdomain, :with => /^[A-Za-z0-9-]+$/, :message => 'The subdomain can only contain alphanumeric characters and dashes.', :allow_blank => true
validates_uniqueness_of :subdomain, :case_sensitive => false
before_validation :downcase_subdomain
protected
def downcase_subdomain
self.subdomain.downcase! if attribute_present?("subdomain")
end
end
Reserved subdomains
In the project that our team is working on, we wanted to reserve several subdomains so that we could use them later on. We tossed in the following validation as well.
validates_exclusion_of :subdomain, :in => %w( support blog www billing help api ), :message => "The subdomain <strong>{{value}}</strong> is reserved and unavailable."
This will prevent people from using those when they sign up.
Controller / Handling Requests
Let’s now think about how we’ll handle requests so that we can scope the application to the current account when a subdomain is being referenced in the URL.
For example, let’s say that our application is going to be: http://purplecowapp.com/
[1]
Customers will get to sign-up and reserve http://customer-name.purplecowapp.com/
. I want my account subdomain to be green.purplecowapp.com
and everything under this subdomain should be related to my instance of the application.
I’ve begun working on my own module, which is inspired mostly by the account_location plugin with some additions to meet some of our product’s requirements.
Here is my attempt to simplify it for you (removed some other project-specific references) and have put this into a Gist for you.
#
# Inspired by
# http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb
#
module SubdomainAccounts
def self.included( controller )
controller.helper_method(:account_domain, :account_subdomain, :account_url, :current_account, :default_account_subdomain, :default_account_url)
end
protected
# TODO: need to handle www as well
def default_account_subdomain
''
end
def account_url( account_subdomain = default_account_subdomain, use_ssl = request.ssl? )
http_protocol(use_ssl) + account_host(account_subdomain)
end
def account_host( subdomain )
account_host = ''
account_host << subdomain + '.'
account_host << account_domain
end
def account_domain
account_domain = ''
account_domain << request.domain + request.port_string
end
def account_subdomain
request.subdomains.first || ''
end
def default_account_url( use_ssl = request.ssl? )
http_protocol(use_ssl) + account_domain
end
def current_account
Account.find_by_subdomain(account_subdomain)
end
def http_protocol( use_ssl = request.ssl? )
(use_ssl ? "https://" : "http://")
end
end
View gist here (embed wasn’t working right when I tried)
Just include this into your lib/
directory and require
it in config/environment.rb
. (if people think it’s worth moving into a plugin, I could do that)
Including AccountSubdomains
In the main application controller (app/controllers/application.rb
), just include this submodule.
class ApplicationController < ActionController::Base
include SubdomainAccounts
...
end
Now, we’ll want to add a check to verify that the requested subdomain is a valid account. (our code also checks for status on paid memberships, etc… but I’ll just show a basic version without that)
Let’s add in the following to app/controllers/application.rb
. This will only check on the status of the account (via subdomain) if the current subdomain is not the default. For example: purplecowapp.com
is just our promotion site, so we won’t look up the account status and/or worry about the subdomain. Otherwise, we’ll check on the status.
before_filter :check_account_status
protected
def check_account_status
unless account_subdomain == default_account_subdomain
# TODO: this is where we could check to see if the account is active as well (paid, etc...)
redirect_to default_account_url if current_account.nil?
end
end
Current Account meets Project model
When requests are made to an account’s subdomain, we want to be able to scope our controller actions.
WARNING: I’m going to gloss over the following steps because this is just standard Rails development stuff and I want to focus on how to scope your Rails code to account subdomains.
I’ll just say that this product gives each account many projects to do stuff within. I’ll assume that you’ll know how to handle all that and we’ll assume you have a Project model already.
What you will need is to add a foreign key to your table (projects in this example) that references Account. So, make sure that your model has an account_id
attribute with and that the database table column has an INDEX.
We’ll add our associations in the models so that they can reference each other.
# app/models/account.rb
class Account < ActiveRecord::Base
has_many :projects
# ...
end
# app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :account
# ...
end
Okay great… back to our controllers. The SubdomainAccounts module provides you with the current_account
variable, which you can use within your controllers/views. This allows us to do the following in our controllers. For example, if we had a ProjectsController.
class ProjectsController < ApplicationController
def index
@projects = current_account.projects.find(:all)
end
def new
@project = current_account.projects.new
end
def show
@project = current_account.projects.find(params[:id])
end
# ...
end
See, this wasn’t so hard, was it?
Handling layouts
I wanted to highlight one other thing here because I suspect that most projects that fit this will likely need a promotional/resource site where people will sign-up from. In our application, we have two application layouts. One for the main application that customers will interact with via their subdomain and the promotional site layout.
The default layout is just app/views/layouts/application.html.erb
and we have our promotional site layout at app/views/layouts/promo_site.html.erb
. A few of our controllers are specifically for the promotional site while the rest are for the application itself and in some cases, there is some overlap down to individual action within a controller.
What we did was add a few more before filters to our application controller to a) define the proper layout to render, and b) skip login_required on the promo site.
To have the proper layout get rendered, we’re just checking whether the current request was made to the promotional site or not.
class ApplicationController < ActionController::Base
# ...
layout :current_layout_name # sets the proper layout, for promo_site or application
protected
def promo_site?
account_subdomain == default_account_subdomain
end
def current_layout_name
promo_site? ? 'promo_site' : 'application'
end
# ...
end
Our application is using Restful Authentication and we just want to check to see if the current request is made to the promotional site or not. If it is, we’ll skip the login_required
filter. Let’s assume that you have the following before_filter set.
class ApplicationController < ActionController::Base
# ...
before_filter :login_required
We’ll just change this to:
class ApplicationController < ActionController::Base
# ..
before_filter :check_if_login_is_required
protected
def promo_site?
account_subdomain == default_account_subdomain
end
def current_layout_name
promo_site? ? 'promo_site' : 'application'
end
def check_if_login_is_required
login_required unless promo_site?
end
# ...
There we go. We can now render the proper layout given the request and only handle authentication when necessary.
Development with account subdomains
When you begin developing an application like this, you need to move beyond using http://locahost:3000
as we need to be able to develop and test with subdomains. You can open up your /etc/hosts
(within a Unix-based O/S) file and add the following.
127.0.0.1 purplecowapp.dev
127.0.0.1 green.purplecowapp.dev
127.0.0.1 sample.purplecowapp.dev
127.0.0.1 planetargon.purplecowapp.dev
127.0.0.1 lollipops.purplecowapp.dev
127.0.0.1 help.purplecowapp.dev
127.0.0.1 support.purplecowapp.dev
After you edit that file (with root permissions), you can flush your dns cache with dscacheutil -flushcache
(Mac OS X). This will let you make requests to http://purplecowapp.dev:3000/
and http://green.purplecowapp.dev:3000
. This is a convention that our team has begun using for our own projects (TLD ending in .dev
). It’s important to remember that the subdomain must be specified here in order to work for local requests. Unfortunately, hosts files don’t support wildcards (’*’).
Update
You can also use Ghost, which is a gem for managing DNS entries locally with Mac OS X. Read Get to know a gem: Ghost
Summary
I know that I glossed over some sections, but was hoping that the code itself would be the most beneficial for you. Feel free to leave any questions and/or provide some feedback on our approach. Perhaps you have some suggestions that I could incorporate into this so that we can improve on this pattern.
1 yeah, I’ve been reading more Seth Godin recently…
The HTTParty has just begun
After releasing the new RubyURL API, I decided that it was time to look around at libraries to interact with it. I came across a new Ruby gem from John Nunemaker named, HTTParty, which aims to make it easy to talk to XML and JSON-based web services. Be sure to read John’s announcement of HTTParty.
So, I decided it might be fun to introduce more people to the gem by showing you all how to use it to talk to the new RubyURL API.
Install HTTParty
Before we get started, you’ll need to install the HTTParty gem with the following command:
~ : sudo gem install httparty
Password:
When you HTTParty, you must party hard!
Successfully installed httparty-0.1.6
1 gem installed
Installing ri documentation for httparty-0.1.6...
Installing RDoc documentation for httparty-0.1.6...
Great! Now that we’re ready to party hard, let’s build something.
Talking to the RubyURL API
The RubyURL API currently supports both XML and JSON, which are each supported by HTTParty. The great thing about HTTParty is that all you need to do is include it in a class and you’re able to quickly talk to remote services.
In this following example, we’re going to create a new class called Rubyurl
.
class Rubyurl
end
What we’ll want to do now is include the HTTParty library. (note: you’ll need to require both rubygems and httparty gems and I’ll skip those lines in following code samples)
class Rubyurl
include HTTParty
end
The HTTParty provides a few class methods, which we can use to configure our library. We’ll go ahead and specify the base_uri
, which we’ll set to rubyurl.com
.
class Rubyurl
include HTTParty
base_uri 'rubyurl.com'
end
Now that our class is setup to talk to the Rubyurl.com site, we’ll want to add a new method which we can use to communicate with the RubyURL API. We’ll call this shorten
as we’re using RubyURL to shorten long URLs… right?
class Rubyurl
include HTTParty
base_uri 'localhost:3000'
def self.shorten( website_url )
end
end
Our new shorten
method will expect us to provide it with a website url, which we’ll want RubyURL to return a shortened URL for. The PATH for the API that we’ll want to talk to is: /api/links
, which we’re expected to pass XML or JSON to.
Here are two examples of using the RubyURL API with HTTParty.
RubyURL via JSON w/HTTParty
We’re going to use the post
method that is provided with HTTParty to send a request to /api/links.json
. As you can see, we’re providing the original website url to the web service.
class Rubyurl
include HTTParty
base_uri 'rubyurl.com'
def self.shorten( website_url )
post( '/api/links.json', :query => { :link => { :website_url => website_url } } )
end
end
When ran, it’ll produce the following:
>> Rubyurl.shorten( 'http://github.com/jnunemaker/httparty/tree/master/lib/httparty.rb' ).inspect
=> {"link"=>{"permalink"=>"http://rubyurl.com/uJVu", "website_url"=>"http://github.com/jnunemaker/httparty/tree/master/lib/httparty.rb"}}
Pretty simple, eh?
RubyURL via XML w/HTTParty
The great thing about HTTParty is that you can use XML without changing much.
class Rubyurl
include HTTParty
base_uri 'rubyurl.com'
def self.shorten( website_url )
post( '/api/links.xml', :query => { :link => { :website_url => website_url } } )
end
end
Produces the following
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<link>
<website_url>http://github.com/jnunemaker/httparty/tree/master/lib/httparty.rb</website_url>
<permalink>http://rubyurl.com/uJVu</permalink>
</link>
Closing thoughts
So… there you have it. HTTParty makes it extremely easy to interact with various web services that work over HTTP. I’d encourage you all to take a few minutes to experiment with it and see what crazy ideas that come to mind during the process. :-)
RSpec: It Should Behave Like
I was going through an older project of ours and cleaning up some specs and noticed how often we were doing the same thing in several places. When we started the project, we didn’t get the benefits of shared groups. Now that we have some time to go through and update some of our older specs, I’ve been trying to take advantage of the features currently available in RSpec. One feature that I haven’t seen a lot of mention of by people is shared groups, so I thought I’d take a few minutes to write up a quick intro to using it.
To pick some low-hanging fruit, let’s take an all-too-familiar method, which you might be familiar with… login_required
. Sound familiar? Have you found yourself stubbing login_required
over and over throughout your specs?
describe Admin::DohickiesController, 'index' do
before( :each ) do
controller.stub!( :login_required )
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
end
If you’re requiring that a user should be logged in when interacting with most of the application (as in the case of an administration section/namespace), you might want to consolidate some of your work into one shared specification group. The basic premise behind this is that you can write a typical describe
block and load it into any other spec groups that you need. For example, in our case, we’ll need to stub login_required
in several places. We can set this up in one shared group and reference it wherever necessary.
For example, here is what we’ll start off with.
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
...
However, the new describe block isn’t accessible from the block at the bottom of the example… yet. To do this, we just need to pass the option: :shared => true
as you’ll see in the following example.
describe "an admin user is signed in", :shared => true do
before( :each ) do
controller.stub!( :login_required )
end
end
Great, now we can reference it by referring to it with: it_should_behave_like SharedGroupName
. In our example above, this would look like:
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
end
describe Admin::DohickiesController, 'new' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
@dohicky = mock_model( Dohicky )
Dohicky.should_receive( :new ).and_return( @dohicky )
get :new
end
...
That’s it! Pretty simple, eh? We can now reference this shared group in any describe blocks that we want to. A benefit to this approach is that we can make change the authentication system (say, we decide to switch it entirely and/or even just change method names, set any other prerequisites necessary when an admin is signed in), we’ll have a single place to change in our specs. (tip: you can put these in your spec_helper
file)
You can learn more about it_should_behave_like
and other helpful features on the RSpec documentation site.
If you have any suggestions on better ways of handling things like this, please follow up and share your solutions. I’m always looking to sharpen my tools. :-)
Update
In response, Bryan Helmkamp suggests that a better solution is to define a method in our specs like, for example: build_mock_user_and_login
. then calling it in our before(:each)
. So, maybe the approach above isn’t the most ideal method but I did wantt o draw some attention to it_should_behave_like
. I suppose that I need a better example.. another post, perhaps? :-)
Also, Ed Spencer has posted an article titled, DRYing up your CRUD controller RSpecs, which will introduce you mor to it_should_behave_like
.
Thanks for feedback people!
Related Posts
Campfire messages in Growl
Our team has slowly been transitioning from IRC to Campfire (iPhone interface helped with this decision) for internal team discussions. Earlier today, I decided to setup Campfire to connect to Growl. There are a few scripts to do this, but I figured that I’d consolidate the steps here for my teammates and share with everyone else in the process.
Step 1: Get stuff installed
You’ll need to install the following programs on OSX.
- Growl (install and run it)
- Fluid.app (run a web site in it’s own desktop app)
- Follow instructions on their homepage (requires restart of Safari)
Step 2: Setup Campfire
Once you have everything installed, you can go ahead and create your Campfire Fluid application. You’ll need to provide your Campfire URL and a name for the application.
Once you get it running, you should be able to run your Campfire application in it’s own window.
Step 3: Install the Campfire Growl script for GreaseKit
Next, you’ll want to install this script, created by Tim Harper, on userscripts.org within your Campfire Fluid.app instance.
Under the Userscripts menu, you’ll see: Browse Userscripts.org.
Find your way to the script (search for: “Campfire Growl”) to find and install the script.
Once it installs, you’ll then need to activate it in the Fluid applications management interface. Within Campfire application, go to Userscripts > Manage Userscripts.
Then activate it like so:
..and that’s it! When you’re not focused on Campfire… you should see Growl notifications when other people are talking in the active room.
Installing Ruby on Rails and PostgreSQL on OS X, Third Edition
3 comments Latest by Scof Fri, 05 Feb 2010 17:03:22 GMT
Over the past few years, I’ve helped you walk through the process of getting Ruby on Rails up and running on Mac OS X. The last version has been getting a lot of comments related to issues with the new Apple Leopard, so I’m going this post will expand on previous installation guides with what’s working for me as of January 2008.
The following guide is how our development team at Planet Argon prefers to setup our development workstations
During this installation, we’ll have what we feel is the optimal development stack for building Ruby on Rails applications with our favorite database server, PostgreSQL.
Ready? Let’s get started…
Phase One
During this initial phase, we’re going to install the underlying dependencies that we’ll be building off of.
XCode 3.0
The first thing that you’ll need to install to get far with this process is XCode tools, which is distributed by Apple. You can find this on the DVD that your Leopard installer is on. You can also download the latest version from Apple’s developer site.
The current version (3.0) is 1.1 GB.. so the download time will vary depending on your connection speed. I would encourage you to drink some tea and/or read a book
Once you finish the installation, you can move forward. The rest of these installation will not work until XCode is installed. :-)
MacPorts
In this next step, we’ll install MacPorts (formerly known as DarwinPorts). The MacPorts web site describes itself as, “an open-source community initiative to design an easy-to-use system for compiling, installing, and upgrading either command-line, X11 or Aqua based open-source software on the Mac OS X operating system.”
This tool is about to become one of the most important tools on your operating system as it’ll be used time and time again to maintain your libraries and many of the Unix tools that you’ll be using. If you’re from the Linux or BSD world, you are likely familiar with similar tools… such as: apt-get, port, and yum.
First, you’ll want to download MacPorts and install the “dmg” disk file for Leopard at the following link.
Once downloaded, you’ll want to run the installer and install it on your workstation.
Work you way through the installer until successfully installed.
Once this finishes, you can open up your favorite terminal application and run the following to test that it installed properly.
In my case, I’m now using Terminal.app.
Issue the command: /opt/local/bin/port version
If it responds with a version number like mine did in the screenshot above, we’re moving along nicely.
Environment Paths
When we install MacPorts, the command to install/update ports installed to /opt/local/bin
. We had to provide the entire path as this isn’t currently showing up in the default $PATH
on Leopard. Let’s quickly remedy this by modifying the file /etc/profile
.
If you have Textmate installed, you can run the following from your terminal: mate /etc/profile
Add the following line to the bottom of /etc/profile
.
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
You can use your favorite editor to update this file. Once you save it, you’ll want to restart your terminal application (or open a new tab) to create a new session. When your new terminal opens, run the following to verify that port
is showing up in your $PATH
.
which port
You should see /opt/local/bin/port
show up as the result of this command.
Great, let’s continue to move forward.
Hiding Apple’s Ruby, Gems, and Rails
Before we install Ruby from MacPorts, we’ll go ahead and hide Apple’s Ruby installations.
:~ robbyrussell$ sudo su -
Password:
:~ root# mv /usr/bin/ruby /usr/bin/ruby.orig
:~ root# mv /usr/bin/gem /usr/bin/gem.orig
:~ root# mv /usr/bin/rails /usr/bin/rails.orig
:~ root# logout
If you ever decide to remove MacPorts, you can just rename ruby.orig
back to ruby
and you’re back where you started… and the same for the others listed.
Phase Two
During this next phase, we’re going to install Ruby and Ruby on Rails.
Installing Ruby via MacPorts
Now that we have MacPorts up and running, we’re going to use it for the first time. We’ll start by using it to install Ruby and the Rubygems package.
$ sudo port install ruby rb-rubygems
Okay, this will take a little while. I’d suggest that you step out to get some fresh air.
How was it outside? What’s the weather like there today? It’s currently 2:30am PST so it’s dark and an 28F outside so I didn’t stay outside very long.
If you’re still waiting for it to install, perhaps you could watch the following video. I might encourage you to check out more of Jam, which was recommended a few years ago to me by James Adam at Canada on Rails.
Be warned… it’s a strange show, but I find strange things like this funny. :-)
If you prefer something a bit more lighthearted…
Okay… when Ruby finishes installing, you’ll want to test that you can run it.
$ ruby -v
Great, let’s move forward!
Installing Ruby on Rails via RubyGems
We’re now going to install the libraries that make up Ruby on Rails via RubyGems.
$ sudo gem install --include-dependencies rails
This will install the following gems.
- rails-2.0.2
- rake-0.8.1
- activesupport-2.0.2
- activerecord-2.0.2
- actionpack-2.0.2
- actionmailer-2.0.2
- activeresource-2.0.2
Excellent, let’s move forward…
If you haven’t already purchased it, I recommend that you take a look at The Rails Way (Addison-Wesley Professional Ruby Series) by Obie Fernandez.
Installing Mongrel via RubyGems
Let’s now install Mongrel, which is an excellent Ruby-based web server for Ruby on Rails applications. We use it in development and production at Planet Argon and it’s also what we recommend to our hosting customers.
$ sudo gem install --include-dependencies mongrel mongrel_cluster
- Note: Be sure to select the proper platform for mongrel. (hint: OS X is NOT mswin32)
Select which gem to install for your platform (i686-darwin9.1.0)
1. mongrel 1.1.3 (java)
2. mongrel 1.1.3 (i386-mswin32)
3. mongrel 1.1.3 (ruby)
4. mongrel 1.1.2 (ruby)
5. mongrel 1.1.2 (mswin32)
6. mongrel 1.1.2 (java)
7. Skip this gem
8. Cancel installation
> 3
Select which gem to install for your platform (i686-darwin9.1.0)
1. fastthread 1.0.1 (mswin32)
2. fastthread 1.0.1 (ruby)
3. Skip this gem
4. Cancel installation
> 2
Building native extensions. This could take a while...
Building native extensions. This could take a while...
Successfully installed mongrel-1.1.3
Successfully installed gem_plugin-0.2.3
Successfully installed daemons-1.0.9
Successfully installed fastthread-1.0.1
Successfully installed cgi_multipart_eof_fix-2.5.0
Installing ri documentation for mongrel-1.1.3...
Installing ri documentation for gem_plugin-0.2.3...
Installing ri documentation for daemons-1.0.9...
Installing ri documentation for fastthread-1.0.1...
No definition for dummy_dump
No definition for dummy_dump
No definition for rb_queue_marshal_load
No definition for rb_queue_marshal_dump
Installing ri documentation for cgi_multipart_eof_fix-2.5.0...
Installing RDoc documentation for mongrel-1.1.3...
Installing RDoc documentation for gem_plugin-0.2.3...
Installing RDoc documentation for daemons-1.0.9...
Installing RDoc documentation for fastthread-1.0.1...
No definition for dummy_dump
No definition for dummy_dump
No definition for rb_queue_marshal_load
No definition for rb_queue_marshal_dump
Installing RDoc documentation for cgi_multipart_eof_fix-2.5.0...
Successfully installed mongrel_cluster-1.0.5
Great, you have almost all of the essentials.. except a database.
Phase Three
In this phase, we’re going to get our database server, PostgreSQL, installed and the libraries that Ruby needs to communicate with it.
Installing PosgreSQL with MacPorts
At Planet Argon, we design and develop our applications on top of PostgreSQL. I’ve been advocating the adoption of this awesome open source database in the Rails community for quite some time now.
The current version available of PostgreSQL via MacPorts is 8.3, which is what we’ll now install with the port
command.
$ sudo port install postgresql83 postgresql83-server
This will download and install the necessary libraries to run PostgreSQL server and the client utilities.
Configuring PostgreSQL
When PostgreSQL is finished installing, it’ll tell you to run the following commands to create a new database instance.
sudo mkdir -p /opt/local/var/db/postgresql83/defaultdb
sudo chown postgres:postgres /opt/local/var/db/postgresql83/defaultdb
sudo su postgres -c '/opt/local/lib/postgresql83/bin/initdb -D /opt/local/var/db/postgresql83/defaultdb'
Adding PostgreSQL to launchd
If you’d like to have PostgreSQL automatically startup after a system restart, you can load it into launchd, which comes with OS X. By running the following command, PostgreSQL will startup automatically on the next system restart.
sudo launchctl load -w /Library/LaunchDaemons/org.macports.postgresql83-server.plist
Adding PostgreSQL to your $PATH
For some reason, the MacPort for PostgreSQL doesn’t get the programs in your path automatically, so we’ll it now.
mate /etc/profile
Modify the PATH
that we changed earlier to include /opt/local/lib/postgresql83/bin@.
export PATH=/opt/local/bin:/opt/local/sbin:/opt/local/lib/postgresql83/bin:$PATH
Save the file and then open a new terminal. To test this, you should get the following output when you run which psql
.
$ which psql
/opt/local/lib/postgresql83/bin/psql
Creating a new PostgreSQL user
When I’m working on Rails applications in my development environment, I really don’t want to have to specify a username and password in every config/database.yml
file for each of our ongoing client projects. When PostgreSQL was installed, it created a superuser named postgres, which is great, but I’d like one that matches my system username, so that I’m not prompted at all for a username or password to connect to PostgreSQL.
To do this, we’ll use the createuser
command, which comes with PostgreSQL. As you can see, I’m creating a new user with superuser
privileges (and will hopefully be the last time I have to do a -U postgres
).
$ createuser --superuser robbyrussell -U postgres
CREATE ROLE
Let’s take a quick moment to test this out.
# create a new database
$ createdb my_test_db
CREATE DATABASE
# drop the database
$ dropdb my_test_db
DROP DATABASE
Great, everything looks good here.
We now have a running installation of PostgreSQL with a new user account. All we need to do now is install the appropriate RubyGem to allow our Ruby applications to connect to it.
Installing PostgreSQL Libraries for Ruby
You can install postgres gem by running the following command.
$ sudo gem install --include-dependencies postgres
Great. We’ve now built a professional development environment for working with Ruby on Rails. Can you feel the excitement? :-)
Closing Thoughts
Like the previous versions, I hope that a few people find this useful. I didn’t have to make a lot of changes from the second edition, but there were enough to warrant a new post. I’ve been setting up my workstation like this for about three years now and I’m looking forward to seeing how a fresh install on Leopard works out for me.
If you have any problems, feel free to ask a question in the comments below.
Installing Ruby on Rails and PostgreSQL on OS X, Second Edition
It’s been just over a year since I posted the article, Install Ruby, Rails, and PostgreSQL on OSX and it still gets quite a bit of traffic. Unfortunately, there have been a few changes in the install process that have caught people.
Today, I am leaving my PowerBook G4. It’s being replaced with a MacBook because the logic board is on the fritz. So, guess what that means? I get to install Ruby, Ruby on Rails, PostgreSQL on OS X again! I figured that I would post a revised version of my previous article for those who may go through this same process in the near future.
Step Zero: Install iTerm (optional)
You’ll spend a lot of time in your terminal as a Rails developer. I’m not a big fan of Terminal.app as it lacks tabbed windows1 and you’ll often find me with around ten tabs open. I’ve been using iTerm for a few years and it’s definitely improved in the past year and doesn’t seem to crash nearly as often as it used to.
Once installed, I always change the default color scheme as I prefer the white on black schema. The menus in iTerm are lacking some thoughtful interaction design, but I’ve figured out the right way to do it (after a long time of stumbling on it by accident). In iTerm, you’ll want to edit the Default bookmark, which you can access by going to Manage Bookmarks under the Bookmarks Menu.
Set the Display value to classic iTerm and you’re golden.
Now… let’s get to business…
Step 1: Install Xcode Tools
Without installing Xcode tools from Apple, we’re not going to get very far. First, you’ll need to grab a copy of Xcode, which you can download on Apple’s Developer Connection site. It’s almost a 1GB download, so you’ll want to start your download and use your multi-tasking skills and grab a Viso, read some blog posts.
- Download Xcode (dmg)
I’m going to make the assumption here that you know how to install a dmg on osx. Once this is installed, you can move on to the next step!
Step 2: All Your MacPorts are Belong to Us
MacPorts (formerly known as DarwinPorts) is a package management system for OS X. This is what we’ll use to install most of the necessary programs to develop and run your Ruby on Rails applications. If you’re from the Linux or BSD world, you are likely familiar with similar tools… such as: apt-get
, port
, and yum
.
You’ll want to download MacPorts and install the dmg file.
Now that this is installed, we should test it.
With a new terminal, run the following:
$ port version
Version: 1.442
Success! Let’s get going…
Step 3: Installing the Ruby on Rails development stack
We’re going to go through a series of small steps, which may take some time depending on how fast your internet connection and computer is.
Install Ruby and RubyGems
In order to install Ruby, we’re going to use MacPorts with the port
command, which is now available for installing various packages on our OS X machines.
$ sudo port install ruby rb-rubygems
It’ll probably take a while to download and install Ruby and all of it’s known dependencies. In the meantime, check out some funny code. KTHXBYE!
Still waiting for it to install, perhaps you could do something like… begin writing a comment on this post, writing your own blog post, watch a funny video, or recommend me. I walked to Backspace with Gary to get an Americano… and it’s still not done. :-p
(minutes/hours/weeks later)
Okay… I trust that it finished installing Ruby and RubyGems without any hiccups. Let’s test them from our terminal to make sure.
Let’s check the version…
$ ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.9.1]
Now, let’s make sure that Ruby is working properly…
$ irb
irb(main):001:0> x = 1
=> 1
irb(main):002:0> puts "wee!!!" if x == 1
wee!!!
Great, we’re on a roll. Let’s get the rest of the stack installed.
Install Ruby on Rails
We’re going to install Ruby on Rails with the gem
command that installing RubyGems provided.
$ sudo gem install -y rails
This command should produce an output similar to the following.
Successfully installed rails-1.2.3
Successfully installed rake-0.7.3
Successfully installed activesupport-1.4.2
Successfully installed activerecord-1.15.3
Successfully installed actionpack-1.13.3
Successfully installed actionmailer-1.3.3
Successfully installed actionwebservice-1.2.3
Installing ri documentation for rake-0.7.3...
Installing ri documentation for activesupport-1.4.2...
Installing ri documentation for activerecord-1.15.3...
Installing ri documentation for actionpack-1.13.3...
Installing ri documentation for actionmailer-1.3.3...
Installing ri documentation for actionwebservice-1.2.3...
Installing RDoc documentation for rake-0.7.3...
Installing RDoc documentation for activesupport-1.4.2...
Installing RDoc documentation for activerecord-1.15.3...
Installing RDoc documentation for actionpack-1.13.3...
Installing RDoc documentation for actionmailer-1.3.3...
Installing RDoc documentation for actionwebservice-1.2.3...
Install Rails-friendly gems
Mongrel
If you’re developing with Rails, it’s highly recommended that you use install and use Mongrel for your development and production environments. The following command will install the mongrel and mongrel_cluster gems (including their dependencies).
$ sudo gem install -y mongrel mongrel_cluster
* Note: Be sure to select the proper platform for mongrel. (hint: OS X is NOT mswin32)
My terminal output:
$ sudo gem install -y mongrel mongrel_cluster
Password:
Bulk updating Gem source index for: http://gems.rubyforge.org
Select which gem to install for your platform (i686-darwin8.9.1)
1. mongrel 1.0.1 (mswin32)
2. mongrel 1.0.1 (ruby)
3. mongrel 1.0 (mswin32)
4. mongrel 1.0 (ruby)
5. Skip this gem
6. Cancel installation
> 2
Select which gem to install for your platform (i686-darwin8.9.1)
1. fastthread 1.0 (ruby)
2. fastthread 1.0 (mswin32)
3. fastthread 0.6.4.1 (mswin32)
4. fastthread 0.6.4.1 (ruby)
5. Skip this gem
6. Cancel installation
> 1
Building native extensions. This could take a while...
Building native extensions. This could take a while...
Successfully installed mongrel-1.0.1
Successfully installed daemons-1.0.6
Successfully installed fastthread-1.0
Successfully installed gem_plugin-0.2.2
Successfully installed cgi_multipart_eof_fix-2.1
Installing ri documentation for mongrel-1.0.1...
Installing ri documentation for daemons-1.0.6...
Installing ri documentation for gem_plugin-0.2.2...
Installing ri documentation for cgi_multipart_eof_fix-2.1...
Installing RDoc documentation for mongrel-1.0.1...
Installing RDoc documentation for daemons-1.0.6...
Installing RDoc documentation for gem_plugin-0.2.2...
Installing RDoc documentation for cgi_multipart_eof_fix-2.1...
Successfully installed mongrel_cluster-0.2.1
Step 4: Installing the World’s Most Advanced Database Server… PostgreSQL!
At PLANET ARGON, we develop our applications on top of PostgreSQL. I’ve long been advocating the adoption of this awesome open source database in the Rails community. Just over a year ago, Jeremy Voorhis (PLANET ARGON alumnus) and I were interviewed on the Ruby on Rails podcast and had the opportunity to discuss our preference of PostgreSQL over the alternatives (mysql, sqlite, firebird, etc.).
We’re going to install PostgreSQL 8.2 from MacPorts by running the following command.
$ sudo port install postgresql82 postgresql82-server
While this is installing, you might take a moment to check out some space shuttles.
Setting up PostgreSQL
You may have noticed the output of the previous port installation of PostgreSQL 8.2, suggested that you do the following. Let’s do that now…
$ sudo mkdir -p /opt/local/var/db/postgresql82/defaultdb
$ sudo chown postgres:postgres /opt/local/var/db/postgresql82/defaultdb
$ sudo su postgres -c '/opt/local/lib/postgresql82/bin/initdb -D /opt/local/var/db/postgresql82/defaultdb'
Have PostgreSQL start automatically on system start-ups
Unless you’re concerned about extra applications running in the background, I’d encourage you to add PostgreSQL to launchd, which will start it automatically after system reboots.
$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.postgresql82-server.plist
Adding PostgreSQL commands to your $PATH
For some reason, MacPorts doesn’t add the PostgreSQL programs to the default bash PATH, which means that you can’t run psql
, pg_dump
, or createdb
/dropdb
without specifying the full path to where they were installed. What we’ll do is add them to our default terminal profile.
sudo vi /etc/profile
(you can use mate
, emacs
, joe
or any other preferred editor to do this)
This file gets loaded every time a new terminal session is started.
Let’s add /opt/local/lib/postgresql82/bin
to the end of the value for PATH.
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/opt/local/lib/postgresql82/bin"
Save the file and then open a new terminal. To test this, you should get the following output when you run which psql
.
$ which psql
/opt/local/lib/postgresql82/bin/psql
Creating a new PostgreSQL user
When I’m working on Rails applications in my development environment, I really don’t want to have to specify a username and password in every config/database.yml
file for each of our ongoing client projects. When PostgreSQL was installed, it created a superuser named postgres
, which is great, but I’d like one that matches my system username, so that I’m not prompted at all for a username or password to connect to PostgreSQL.
To do this, we’ll use the createuser
command, which comes with PostgreSQL. As you can see, I’m creating a new user with superuser privileges (and will hopefully be the last time I have to do a -U postgres).
$ createuser --superuser robbyrussell -U postgres
CREATE ROLE
Let’s take a quick moment to test this out.
# create a new database
$ createdb my_test_db
CREATE DATABASE
# drop the database
$ dropdb my_test_db
DROP DATABASE
Great, everything looks good here.
We now have a running installation of PostgreSQL with a new user account. All we need to do now is install the appropriate RubyGem to allow our Ruby applications to connect to it.
Installing the Ruby Postgres gem
UPDATE: Hydro posted a commented, which lead me to the ruby-postgres gem.
You can install ruby-postgres gem by running the following command.
$ sudo gem install -y ruby-postgres
Let’s take a moment to test that this installed properly.
$ irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'postgres'
=> true
If this returns true, than we should be good to go. We’ve now built a professional development environment for working with Ruby on Rails. Doesn’t that feel great?
Test your install
You can look back at my older post to walk through the process of testing out your setup with a new Rails application.
Closing thoughts
I hope that this post has been useful for you. It took me a few hours to walk through this process and it’s how all of our designers and developers at PLANET ARGON installs and configures their development environment.
We also install the following programs on new machines.
- TextMate
- Subversion:
sudo port install subversion
- RSpec:
sudo gem install -y rspec
- ...amongst other gems that are needed on specific projects
Until next time… have fun!
1 Rumor: Mac OS X Leopard will give Terminal.app tabs! (see screenshot)
Older posts: 1 2