Recently I’ve been working on a small spike intended to help jumpstart our Rails application development in the Pivotal Labs Boulder office.
Until Rails 3 was officially released, new projects used the Pivotal “rails-template” to help bootstrap development. The template is really just a GitHub repo that installs dependencies in our Rails 2 project.
There’s are few new interesting new features in Rails 3, including a “–builder=” option to the “rails new APP_PATH [options]” command. I thought this might be an interesting solution to bootstrapping an application, especially when your dependencies include Rails generators.
The spike makes a few choices regarding dependencies and testing preferences. For example, we tend to use things like RSpec and Cucumber as well as Cruise.rb, Engine Yard Cloud, and NewRelic. To make life a bit easier, I also used RVM and Gemsets to help set the initial set of dependencies. Even more interesting, I’m able to bootstrap from a GitHub Gist or any directly downloadable file URL.
For everything to work without a hitch, I have both a local .rvmrc file and Bundler Gemfile within my ~/workspace/mahan directory with the below Gems installed.
[Note: I'm making an assumption here that you know and understand Bundler and RVM]
Here’s the .rvmrc file I used within the ~/workspace/mahan directory.
rvm_archflags="-arch x86_64" rvm 1.8.7-p174@mahan
And here’s a few commands to get you started.
$ cd workspace/ $ mkdir mahan $ cd mahan/ $ vi .rvmrc rvm_archflags="-arch x86_64" nrvm 1.8.7-p174@mahan"
Also, here’s the Gemfile I used within the ~/workspace/mahan directory.
You’ll need to run “gem install bundler –version 1.0″ from this directory and then run “bundle install”.
source 'http://rubygems.org' gem 'rails', '3.0.0' gem 'mysql2' gem "rspec-rails", '2.0.0.beta.22' gem "rspec", '2.0.0.beta.22' gem 'capybara', '0.3.9' gem 'database_cleaner', '0.5.2' gem 'cucumber-rails', '0.3.2' gem 'cucumber', '0.9.0' gem 'rspec-rails', '2.0.0.beta.22' gem 'spork', '0.8.4' gem 'launchy', '0.3.7' gem 'jasmine', :submodules => true gem 'json_pure', '1.4.6'
Agin, a few commands to get you started.
$ cd ~/workspace/mahan/ $ rvm gemset create 'mahan' $ rvm gemset use mahan $ gem install bundler --version 1.0 $ vi Gemfile [Add the above Gemfile contents] $ bundle install
Next, I’ll dive right into the Rails command. From your ~/workspace/mahan directory run
rails new rails_example --builder=http://gist.github.com/611035.txt
The command creates a new Rails 3 application with our default set of dependencies.
Feel free to take a look at the actual Gist. You’ll notice that I extend “class AppBuilder < Rails::AppBuilder”. If you’re interested in the Rails guts, take a look around line 316 in railties-3.0.0/lib/rails/generators/rails/app/app_generator.rb. The Rails AppGenerator looks for custom builders and then performs an instance_eval on the file.
You’ll also notice that I “override” the test method within my custom AppBuilder to include RSpec, Cucumber, and Jasmine. The actual method then call the associated Rails install generator. And yes, you’ll notice we’re working on the Jasmine Rails 3 support.
def test append_test_gems rspec cucumber jasmine end
I also override the “leftovers” method to include things like Cruise.rb, Engine Yard Cloud, and NewRelic dependencies and configurations. The “leftovers” method gets call from the AppGenerator “finish_template” method. With a few of the “leftovers”, I simply pull the from the associated GitHub repo and copy the necessary files and/or directories.
I’ve also experiment with a Gem that includes a Thor wrapper around a custom Rails AppGenerator. The benefit for this approach is that now I’m now able include my own custom Rails options. For example
class PivotalAppGenerator < Rails::Generators::AppGenerator include Thor::Shell source_root File.expand_path('../templates', __FILE__) class_option :skip_rspec, :type => :boolean, :aliases => "-R", :default => false, :desc => "Skip rspec" class_option :skip_cucumber, :type => :boolean, :aliases => "-C", :default => false, :desc => "Skip cucumber" class_option :skip_jasmine, :type => :boolean, :aliases => "-J", :default => false, :desc => "Skip jasmine"
We’ll probably use a combination of the AppBuilder and AppGenerator approach, although at this time I thought some feedback would be interesting.