This is the first in a series called “Bootstrap Your App” where I’ll detail how I’ve set up my local development environment to get new rails sites up and running quickly using sake and generators.
I’m a ruby on rails developer who focuses primarily on websites for small-businesses and non-profits, so I build lots of small rails apps from the ground up. Every time I start a site, I go through a few common tasks:
- Install all of my favorite plugins
- Run the generators from those plugins (like rspec and acts_as_authenticated)
- Install the hacks, patches and recipes I’ve gathered along the way, but never bothered to put into plugins
- Set the app’s name as the prefix for the exception notifier
For some apps, I’ll install and configure ssl, for others I might install and configure account_location. Some of my clients deploy to RailsMachine, and some to Slicehost. Some require email notification for user registrations, others don’t. Some use SVN, others git.
When I started to think about all of the different configurations that a client might need, I quickly realized that approaches like Courtenay’s sample rails apps didn’t quite fit for me. I needed a way to quickly add features to a site, and have complete customization.
Grok Sake
After going through several different setups, I’ve settled into using rake tasks (through sake) and building generators. Sake and generators are both simple and portable and when used correctly can give you massive productivity gains.
If you haven’t checked out sake do so immediately. Do not pass go. Do not collect $200. Sake allows you share rake tasks across different sites (and even different machines). Sake makes bootstrapping your apps very easy.
Plug-In
So the first task on my list above was to install common plugins. I went through my apps and identified the ones I used most commonly, and split them up into roughly four categories: common, speccing, ecommerce and cms (content management system) – your mileage will vary. Then I wrote a quick rake task that would install the plugins using the standard rails script/plugin install, or using piston import. The rake file looks like this:
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 50 51 52 53 54 55 56 57 58 |
namespace :zilkey do # A set of tasks that add plugins to the app either using script/plugin or piston namespace :plugins do desc "installs common plugins via piston or rails. Options:\n * set=[common|ecommerce|speccing|cms] (required)\n * method=[piston|rails]\n * pretend=[true|false]" task :install do sets = { :speccing => { :rspec => "svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec", :rspec_on_rails => "svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails", :spider_test => "svn://caboo.se/plugins/court3nay/spider_test", }, :common => { :attachment_fu => "http://svn.techno-weenie.net/projects/plugins/attachment_fu/", :will_paginate => "svn://errtheblog.com/svn/plugins/will_paginate", :geokit =>"svn://rubyforge.org/var/svn/geokit/trunk", :annotate_models => "http://repo.pragprog.com/svn/Public/plugins/annotate_models/", :exception_notification => "http://dev.rubyonrails.org/svn/rails/plugins/exception_notification/", :tztime => "http://dev.rubyonrails.com/svn/rails/plugins/tztime/", :tzinfo_timezone => "http://dev.rubyonrails.org/svn/rails/plugins/tzinfo_timezone/", :atom_feed_helper => "http://svn.rubyonrails.org/rails/plugins/atom_feed_helper/", :action_mailer_tls => "http://svn.nanorails.com/plugins/action_mailer_tls/", :acts_as_authenticated => "http://svn.techno-weenie.net/projects/plugins/acts_as_authenticated/", }, :ecommerce => { :active_merchant => "http://activemerchant.googlecode.com/svn/trunk/active_merchant", :ssl_requirement => "http://svn.rubyonrails.org/rails/plugins/ssl_requirement/", }, :cms => { :fckeditor => "svn://rubyforge.org/var/svn/fckeditorp/trunk/fckeditor", :better_nested_set =>"svn://rubyforge.org/var/svn/betternestedset/trunk", :acts_as_ferret =>"svn://projects.jkraemer.net/acts_as_ferret/tags/stable/acts_as_ferret", :us_states =>"http://svn.techno-weenie.net/projects/plugins/us_states/", }, } raise "You must specify set=[#{sets.keys.join(",")}]" unless sets.keys.map{|k|k.to_s}.include?(ENV['set']) set = sets[ENV['set'].to_sym] plugins = set.to_a.map text = set.to_a.map{|a|a.first}.join("\n * ") run_method = ENV['pretend'] == "true" ? :p : :system install_method = (ENV['method'] || "rails").to_sym set.each do |name,url| if install_method == :piston send run_method, "svn up" send run_method, "piston import #{url} vendor/plugins/#{name}" elsif install_method == :external send run_method, "script/plugin install #{url} -x" else send run_method, "script/plugin install #{url} --force" end end p "don't forget to run svn commit -m \"Added #{text} plugins to vendor/plugins via piston\"" if run_method == :piston end end end |
It’s not the most beautiful code I’ve ever written, but it works. The bulk of the task is just the hash that stores all of the names and urls of the plugins, grouped into the categories that I find most useful right now. To run it, I use commands like:
1 2 3 4 |
$rake zilkey:plugins:install set=common #=> installs using script/plugin install $rake zilkey:plugins:install set=common method=external # => installas using script/plugin install -x $rake zilkey:plugins:install set=common method=piston # => installs via piston import $rake zilkey:plugins:install set=common pretend=true # => echos the commands to the screen, but doesn't execute them |
Great – so the task is written and working. To get it up and running with sake I just typed:
$sake -i lib/tasks/zilkey.rake zilkey:plugins:install |
Now I can install these plugins to any app on my machine (or any other machine, for that matter: Google sake for more info on how to share tasks). Let’s try it out:
1 2 3 4 |
$rails newsite && cd newsite $sake zilkey:plugins:install set=speccing $ls vendor/plugins rspec rspec_on_rails spider_test |
There you have it – one common task down. In the next article I’ll discuss how to take advantage of custom generators to productize your apps.