Rails generator templates: optionality

I’ve been working on tailoring the rails generator templates – in particular the scaffold generator.  Tailoring these templates allows you to automatically generate objects that have much of the content you want for your application.  My base generator templates came through using the twitter bootstrap application builder – it included Devise, FactoryGirl and Rspec for me.  However, I found that when I was building objects I was often writing the same code over and over again, and more annoyingly, the same unit tests over and over again.

Tailoring the templates themselves is not hard, this guide provides much of what you need to do the work.  In my case, I’m using RVM, so the templates I wanted to tailor lived in places like:

  /usr/local/rvm/gems/ruby-1.9.3-p374@<app_name>/gems/railties-3.2.9/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
  /usr/local/rvm/gems/ruby-1.9.3-p374@<app_name>/gems/railties-3.2.9/lib/rails/generators/erb/scaffold/templates/index.erb.html    (and the other views)
  /usr/local/rvm/gems/ruby-1.9.3-p374@<app_name>/gems/activerecord-3.2.9/lib/rails/generators/active_record/model/templates/model.rb
  /usr/local/rvm/gems/ruby-1.9.3-p374@<app_name>/gems/rspec-rails-2.12.0/lib/generators/rspec/controller/templates/controller_spec.rb

To tailor these, you create a local copy within your project, and edit that local copy. The local copy overrides the standard version, and goes into:

  lib/templates/rails/scaffold_controller/controller.rb
  lib/templates/erb/scaffold/index.erb.html   (and the other views)
  lib/templates/active_record/model/model.rb
  lib/templates/rspec/scaffold/controller_spec.rb

Once I got to that point, I had some base templates for my application that were working fine.  But I then discovered that I had multiple types of object that I wanted to build.  Some of my objects were for codes tables or reference data, and they had a particular set of fields that were common, and a particular set of unit tests.  Other objects were for functional data, and still other objects for user configurable reference data.  Each of these tables have particular characteristics, and it would be nice to create a set of templates that deal with each individually.

I haven’t found a way to pass options into the generator. In theory I could subclass the generator, so I could have:

  rails generate scaffold_ref_data xxxxxxx
  rails generate scaffold_base_object xxxxxxx
  etc

In practice that looked kinda hard, as none of the generators looked to take information such as this as a parameter, so it felt like I’d have to subclass all the way down the call stack to the templates themselves. It also looked like I’d end up duplicating bunches of the scaffold generator into my project, which then would probably get annoying with upgrades.
Instead, I’ve gone with a script that I run prior to the generate, this script copies the right templates in. So, for example, I have:

  lib/templates/rails/scaffold_controller/ref_data_controller.rb
  lib/templates/rails/scaffold_controller/base_object_controller.rb

Any my script copies the right one across as controller.rb. Whilst I’m sure there are more elegant and ruby-like ways to do this, it seems to be working fine and was quick to implement. I’m interested to hear if other people have found a different way to do this.

The script itself looks as follows:

#!/usr/bin/env ruby
require 'optparse'

options = {}

opt_parser = OptionParser.new do |opt|
  opt.banner = "Usage: config_generators.rb OBJECT_TYPE"
  opt.separator  ""
  opt.separator  "Alters the templates for the rails generate command to generate different types of objects.  The types of objects supported are:"
  opt.separator  "     ref_data:          system wide reference data"
  opt.separator  "     base_object:       core user data objects"
  opt.separator  "     project_ref_data:  ref data that is user tailorable per project"
  opt.separator  "     project:           objects that hang off the project"
  opt.separator  "     versioned_project: versioned objects that hang off the project"
  opt.separator  ""
  opt.separator  "Options:"

  opt.on("-h","--help","help") do
    puts opt_parser
  end
end

def copy_files(object_type)
  [['lib/templates/rails/scaffold_controller/', 'controller.rb'],
   ['lib/templates/rspec/scaffold/', 'controller_spec.rb'],
   ['lib/templates/erb/scaffold/', 'edit.html.erb'],
   ['lib/templates/erb/scaffold/', '_form.html.erb'],
   ['lib/templates/erb/scaffold/', 'index.html.erb'],
   ['lib/templates/erb/scaffold/', 'new.html.erb'],
   ['lib/templates/erb/scaffold/', 'show.html.erb'],
   ['lib/templates/active_record/model/', 'model.rb']].each do |path, file|
    system "cp #{path}#{object_type}_#{file} #{path}#{file}" || abort("Failed to copy #{path}#{object_type}_#{file}, are you in the root directory of your application?")
  end
end

opt_parser.parse!

case ARGV[0]
when "ref_data"
  copy_files(ARGV[0])
when "base_object"
  copy_files(ARGV[0])
when "project_ref_data"
  puts "Not yet available"
when "project"
  puts "Not yet available"
when "versioned_project"
  puts "Not yet available"
else
  puts opt_parser
end
Advertisements

2 thoughts on “Rails generator templates: optionality

  1. Pingback: Creating a history table with Rails and ActiveModel::Dirty | technpol

  2. Pingback: AngularJS and Rails – shared directory or not | technpol

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s