Pat Shaughnessy

Ribadesella, Spain

Getting started with Ruby metaprogramming

February 20, 2010 · 1 comment

The Rails auto_complete plugin was my first exposure to Ruby metaprogramming. It’s code was simple enough for a Rails beginner like me to understand, but also just complex enough for me to learn something new. Specifically, I ran into metaprogramming when I took a close look at the “auto_complete_for” method and tried to figure out how it worked. I won’t spend any time here explaining what the auto_complete plugin does or what it’s used for, beyond to say that if you add this line to one of your controllers:

class CategoriesController < ApplicationController

  auto_complete_for :category, :name

  etc…


… a method called “auto_complete_for_category_name” will be automatically generated in that controller that will return a list of category records that have a name matching a given search query. This is very cool, and is a typical example of Ruby on Rails magic: you add one line to a class in your application and suddenly an entire feature or behavior is added, customized to the data and objects in your app!

This sort of thing is really what makes Ruby on Rails so amazing… but how does it work? Let’s take a look at the implementation of auto_complete_for method:

def auto_complete_for(object, method, options = {})
  define_method("auto_complete_for_#{object}_#{method}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{method}) LIKE ?",
                       '%' + params[object][method].downcase + '%'
                     ],
      :order => "#{method} ASC",
      :limit => 10 }.merge!(options)

    @items = object.to_s.camelize.constantize.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  end
end

You can find this code in the lib/auto_complete.rb file inside the rails/auto_complete repository on github. So what the heck does all of this mean? Let’s take a step-by-step look at this code, and see if we can figure it out.

To get started, let’s use the category/name example I used in my last article, and also that Ryan Bates used in his Auto-Complete Association screencast on the auto_complete plugin:

auto_complete_for :category, :name


Here we are passing “:category” into auto_complete_for as the value for “object,” and “:name” as the value for “method.” Now let’s repeat the auto_complete_for code, but substitute object with :category:

def auto_complete_for(:category, method, options = {})
  define_method("auto_complete_for_#{:category}_#{method}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{method}) LIKE ?",
                       '%' + params[:category][method].downcase + '%'
                     ],
      :order => "#{method} ASC",
      :limit => 10 }.merge!(options)

    @items = :category.to_s.camelize.constantize.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  end
end

In the code snippet above I’ve highlighted the places where the symbol :category appears. You can see that it’s used in a few different places, but the most important line is near the bottom: @items = :category.to_s.camelize.constantize… etc. Let’s evaluate each of the method calls on this one line, one at a time. The first call is “:category.to_s”. The “to_s” method name means “to string” and will convert the target object (the object you call to_s on) to a string. This means that the :category symbol will be converted to a string. So now let’s display the string ‘category’ in place and see what we are left with:

def auto_complete_for(:category, method, options = {})
  define_method("auto_complete_for_#{:category}_#{method}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{method}) LIKE ?",
                       '%' + params[:category][method].downcase + '%'
                     ],
      :order => "#{method} ASC",
      :limit => 10 }.merge!(options)

    @items = 'category'.camelize.constantize.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  end
end

You can see ‘category’ highlighted above where I’ve evaluated the to_s method. Now the next method call is “camelize” – what does this mean? The camelize method is one of a series of functions that Rails provides in the “ActiveSupport” gem, one of the components of the Rails framework. It converts the given string to camel case, for example “office_code” to “OfficeCode.” Since Ruby class names are written using camel case, this is often very useful for obtaining a class name from a string. In our example, the string “category” is converted into “Category” with an upper case “C” …

def auto_complete_for(:category, method, options = {})
  define_method("auto_complete_for_#{:category}_#{method}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{method}) LIKE ?",
                       '%' + params[:category][method].downcase + '%'
                     ],
      :order => "#{method} ASC",
      :limit => 10 }.merge!(options)

    @items = 'Category'.constantize.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  end
end

Now let’s take a look at the next method call on that same line of code: “constantize.” This converts a string into an actual Ruby constant, and returns an error if that constant doesn’t exist. In our example, the string “Category” is converted into the Ruby class Category:

def auto_complete_for(:category, method, options = {})
  define_method("auto_complete_for_#{:category}_#{method}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{method}) LIKE ?",
                       '%' + params[:category][method].downcase + '%'
                     ],
      :order => "#{method} ASC",
      :limit => 10 }.merge!(options)

    @items = Category.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  end
end

Now we can see that the complex line above evaluates to a simple ActiveRecord find call. The power of Ruby metaprogramming has enabled us to write a single helper function “auto_complete_for” that takes any symbol or string as an argument (e.g. :category), and performs a SQL query on the corresponding ActiveRecord class. The amazing part of this for me is how flexible and easy to use Ruby is: helper methods like camelize and constantize make it very easy to extract common bits of code you might write over and over again in your app, and generalize them into a single method that will apply to any target class. This would be possible in any programming language but it’s amazing just how easy it is to do with Ruby.

Let’s continue to simplify the auto_complete_for code by substituting a value for the “method” parameter – in our example method will become the symbol “:name” :

def auto_complete_for(:category, :name, options = {})
  define_method("auto_complete_for_#{:category}_#{:name}") do
    find_options = { 
      :conditions => [
                       "LOWER(#{:name}) LIKE ?",
                       '%' + params[:category][:name].downcase + '%'
                     ],
      :order => "#{:name} ASC",
      :limit => 10 }.merge!(options)

    @items = Category.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, '#{:name}' %>"
  end
end

Again you can see that the :name symbol is used in a few different places. Most commonly here the symbol is inserted in a string using this syntax: “#{:name}”. This is the standard Ruby #{} string interpolation operator, which is also implicitly converting the symbol into a string before inserting it into the surrounding string value, just as if we called to_s as above. So let’s replace “#{:name}” and “#{:category}” with the strings “name” and “category,” and then also insert them into the surrounding string values:

def auto_complete_for(:category, :name, options = {})
  define_method("auto_complete_for_category_name") do
    find_options = { 
      :conditions => [
                       "LOWER(name) LIKE ?",
                       '%' + params[:category][:name].downcase + '%'
                     ],
      :order => "name ASC",
      :limit => 10 }.merge!(options)

    @items = Category.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, 'name' %>"
  end
end

And now let’s replace the last parameter to auto_complete_for “options” with it’s default value since we aren’t providing that in our auto_complete_for :category, :name call:

def auto_complete_for(:category, :name, {})
  define_me thod("auto_complete_for_category_name") do
    find_options = { 
      :conditions => [
                       "LOWER(name) LIKE ?",
                       '%' + params[:category][:name].downcase + '%'
                     ],
      :order => "name ASC",
      :limit => 10 }.merge!({})

    @items = Category.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, 'name' %>"
  end
end

You can see that options was used in only one place: …merge!(options). Using merge with hashes is another extremely common technique in Ruby coding; it adds the key/value pairs from the hash you provide as a parameter to the hash you call merge on. Merge! is a variation on this which directly modifies the target object, as opposed to only returning the merged hash. Merge is very useful for metaprogramming because, as in this example, it makes it easy to allow the user/client code of a method to customize a hash of options that is passed into some other function. Here merge was used to allow the caller of auto_complete_for to pass in additional find options to the Category.find call. In our case since we didn’t pass in a value for options, it is set to {} and then has no effect on the find_options hash. So let’s just remove the call to merge!({}), which is a NOP anyway:

def auto_complete_for(:category, :name, {})
  define_method("auto_complete_for_category_name") do
    find_options = { 
      :conditions => [
                       "LOWER(name) LIKE ?",
                       '%' + params[:category][:name].downcase + '%'
                     ],
      :order => "name ASC",
      :limit => 10 }

    @items = Category.find(:all, find_options)

    render :inline => "<%= auto_complete_result @items, 'name' %>"
  end
end

Now the code is looking simpler and simpler. Let’s finish this example by evaluating the “define_method” call at the top. As you might guess, define_method is how you can dynamically create a new method in a class in Ruby. In auto_complete_for it’s used to add a new method to the controller in which you included the call to auto_complete_for, called “auto_complete_for_category_name.” In other words, adding the auto_complete_for :category, :name line to the CategoriesController was equivalent to adding this method definition to the class:

def auto_complete_for_category_name
  find_options = { 
    :conditions => [
                     "LOWER(name) LIKE ?",
                     '%' + params[:category][:name].downcase + '%'
                   ],
    :order => "name ASC",
    :limit => 10 }

  @items = Category.find(:all, find_options)

  render :inline => "<%= auto_complete_result @items, 'name' %>"
end

Now we’ve seen the real value of metaprogramming: the author of the auto_complete plugin was able to provide a simple helper method, auto_complete_for, which dynamically added this fairly complex method to your controller. The beauty and power of Ruby and Rails is just how easy this was to do: the generated function is tailored to use the Category model just as if you had written it yourself. When I first came across this code I was impressed not only by the power that this sort of metaprogramming provided, but also by how easy it was to do this using Ruby on Rails. Helper modules and methods, such as constantize in ActiveSupport, make it very, very easy to convert from symbols to strings to constants and back again… which is exactly what you need to do to write a general method that can be dynamically generated in this way.

Last week I asserted that calling auto_complete_for in your controller like this was equivalent to this code from Ryan Bates’ Auto-Complete Association screencast:

class CategoriesController < ApplicationController

  def index
    @categories =
      Category.find(:all, :conditions => ['name LIKE ?', "%#{params[:search]}%"])
  end

etc...


Now you can see how this is true: the code Ryan wrote was a simple call to Category.find, with conditions that search for a name like the name provided in the “search” parameter. Looking at the simplified auto_complete_for_category_name method above, you can see that it calls Category.find in the same way. However, the auto_complete_for version is a bit different in that it:

  • Looks for a parameter called “category[name]” instead of “search,” and
  • Converts it to lower case, and
  • Passes a couple of other SQL options into Category.find: order by name ascending, and limit 10, and
  • Makes a call to render :inline to return the result to the browser without the need for a normal view code file.

Auto_complete_for does essentially the same thing that Ryan explained in his screen cast, but the elegance of Ruby metaprogramming allows the users of the auto_complete plugin to implement this search feature without writing any code at all.

1 comment Tags:·

Creating associations to existing data part 2: belongs_to with auto_complete

February 13, 2010 · 3 comments

In my last post I started a series on how to write Rails forms that associate a new record with existing data. This sort of requirement comes up for me over and over again at my day job, and so I decided to support scaffolding for these forms in View Mapper.

Today I’ll continue by showing how to use the auto_complete plugin to select an existing record – exactly what Ryan Bates discussed in his screen cast Auto-Complete Association. Using the same Category/Product example, this form would allow the user to create a new product record, and associate it with an existing category tag:

To create scaffolding like this in your app with View Mapper, just run this command:

script/generate scaffold_for_view product name:string bar_code:integer
                --view belongs_to_auto_complete:category

View Mapper will validate you have a line “has_many :products” in your category model, that you have a category model to begin with, and also that the auto_complete plugin is installed before proceeding to generate this form. Also, View Mapper assumes the parent model, “Category” in this example, has a “name” column and will use that value to identify each category in the auto complete list. You can indicate to use a different parent model field instead with this syntax:

script/generate scaffold_for_view product name:string bar_code:integer
                --view belongs_to_auto_complete:category[display_name]

For more details on View Mapper, see the example below where I create a sample app from scratch.

Code review: model

Since Ryan explains auto complete association so well in his screen cast, I won’t repeat all of that information here. Instead, let’s take a look at the code View Mapper generates and compare it to what Ryan showed. First, in the product model Ryan has a “category_name” virtual attribute:

def category_name
  category.name if category
end
def category_name=(name)
  self.category = Category.find_or_create_by_name(name) unless name.blank?
end

This allows the view to display the category for each product easily, and also supports creating new categories on the fly when you submit a new product. The View Mapper scaffolding is a bit simpler and uses “find_by_name” instead of “find_or_create_by_name” since it assumes the category records already exist. Also, Ryan’s code uses “unless name.blank?” to avoid creating empty categories, while the View Mapper scaffolding assumes a blank category name indicates a product without a category, and allows the user to clear the category when editing an existing product. Either approach can make sense depending on the business requirements of your application. Here’s the View Mapper model code:

class Product < ActiveRecord::Base
  belongs_to :category
  def category_name
    category.name if category
  end
  def category_name=(name)
    self.category = Category.find_by_name(name)
  end
end

Code review: controller

In the controller code, the View Mapper scaffolding differs from Ryan’s solution more dramatically. To return the list of matching categories to the auto_complete plugin, Ryan adds this code to the categories controller to query the category records that have a name field that match a given search parameter:

def index
  @categories =
    Category.find(:all, :conditions => ['name LIKE ?', "%#{params[:search]}%"])
end

This makes a lot of sense: the categories controller should be used to generate a list of categories. However, for View Mapper I chose to use the products controller instead since the scaffolding generator already generates that code file, and to avoid the need for creating or modifying the categories controller also. View Mapper just adds this one line to the products controller to return the list of category names:

auto_complete_for :category, :name

This simple call actually achieves exactly the same thing as the Category.find call above. In my next post, I’ll take a look at the Ruby metaprogramming used by auto_complete_for and show how it automatically generates a method that executes the same SQL query.

Code review: view

Finally in the view we see a call to “text_field_with_auto_complete” to use the Prototype javascript library’s auto_complete feature. Here’s Ryan’s view code from the screen cast:

<%= text_field_with_auto_complete :product,
                                  :category_name,
                                  { :size => 15 },
                                  { :url => formatted_categories_path(:js),
                                    :method => :get,
                                    :param_name => 'search' }
%>

“:url => formatted_categories_path(:js)” calls the categories controller code above when the user starts to type in the text field, and the “:param_name =>‘search’” option passes the user’s text in as the search parameter. Ryan’s solution also uses a view file called “index.js.erb” to return the list of completion options in the proper format – this is called by the index action when the categories controller receives the “/categories.js” request:

<%= auto_complete_result @categories, :name %>

By contrast, View Mapper’s call to text_field_with_auto_complete looks like this:

<%= text_field_with_auto_complete :product,
                                  :category_name,
                                  {},
                                  { :method => :get,
                                    :url => '/auto_complete_for_category_name',
                                    :param_name => 'category[name]' }
%>

This is very similar, but uses “category[name]” as the search parameter and sets the AJAX url to “auto_complete_for_office_code”, since this is what “auto_complete_for” expects.

Ryan’s approach is more elegant since it follows the REST model for the Ajax URL and controller code: the categories controller is used to handle category related requests, and its index action is used to return the list of category values. The scaffolding code View Mapper generates uses the auto_complete plugin the way it was originally intended with the “auto_complete_for” function, but is a bit ugly in that the products controller returns the category values, and uses a custom action method name instead of the normal “index” action. There’s no need for the index.js.erb file since auto_complete_for renders the response inline.

The advantage of the View Mapper approach is that there’s no need for the categories controller at all, and also you don’t need to code the “index” action or the index.js.erb view file yourself. If you plan to have a categories controller in your application anyway, you might want to change the text_field_with_auto_complete call to use that controller instead.

Detailed example

To make sure you can get a working example on your computer, let’s run through a step by step example:

$ rails sample_app
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
etc...

First, let’s create a model to represent our existing data called “Office” that will have two attributes: “display_name” and “code:”

$ cd sample_app/
$ ./script/generate model office code:string display_name:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/office.rb
      create  test/unit/office_test.rb
      create  test/fixtures/offices.yml
      create  db/migrate
      create  db/migrate/20100212193446_create_offices.rb
$ rake db:migrate
(in /Users/pat/rails-apps/sample_app)
==  CreateOffices: migrating ==================================================
-- create_table(:offices)
   -> 0.0031s
==  CreateOffices: migrated (0.0034s) =========================================

And now let’s create a few sample office records:

$ ./script/console 
Loading development environment (Rails 2.3.5)
>> Office.create :display_name => 'Boston', :code => 'BOS'
>> Office.create :display_name => 'Boise', :code => 'BOI'
>> Office.create :display_name => 'Barcelona', :code => 'BAR'
>> exit

Now you can install View Mapper – you’ll need version 0.3.3 for the “belongs_to_auto_complete” view:

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install view_mapper
Successfully installed view_mapper-0.3.3
1 gem installed
Installing ri documentation for view_mapper-0.3.3...
Installing RDoc documentation for view_mapper-0.3.3...

And now we can just run View Mapper’s “scaffold_for_view” generator to create the scaffolding code. Let’s try creating a new model called “Employee” that will belong to one of the existing offices:

$ ./script/generate scaffold_for_view employee name:string
                    --view belongs_to_auto_complete:office
       error  The auto_complete plugin does not appear to be installed.
$ ./script/plugin install git://github.com/rails/auto_complete.git
Initialized empty Git repository in /Users/pat/rails-apps/sample_app/vendor/plugins/auto_complete/.git/
remote: Counting objects: 13, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 13 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (13/13), done.
From git://github.com/rails/auto_complete
 * branch            HEAD       -> FETCH_HEAD

Trying again:

$ ./script/generate scaffold_for_view employee name:string
                    --view belongs_to_auto_complete:office
     warning  Model Office does not contain a has_many association for Employee.

Editing app/models/office.rb:

1 class Office < ActiveRecord::Base
2   has_many :employees
3 end

Trying a third time:

$ ./script/generate scaffold_for_view employee name:string
                    --view belongs_to_auto_complete:office
     warning  Model Office does not have a name attribute.

This time we get a warning that our existing model doesn’t have a “name” attribute (we chose “display_name” instead). To make this a bit more interesting, let’s use the “code” attribute for the auto_complete options. You can specify this to View Mapper with this syntax:

$ ./script/generate scaffold_for_view employee name:string
                    --view belongs_to_auto_complete:office[code]
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/employees
      exists  app/views/layouts/
      exists  test/functional
etc...

Now we just run rake db:migrate again, start our server and we’re done!

$ rake db:migrate
(in /Users/pat/rails-apps/sample_app)
==  CreateEmployees: migrating ================================================
-- create_table(:employees)
   -> 0.0034s
==  CreateEmployees: migrated (0.0036s) =======================================

3 comments Tags:··

Creating associations to existing data part 1: belongs_to scaffolding

January 24, 2010 · 8 comments

I decided it would be fun to look into various different types of Rails forms that allow you to create a new object that is associated with existing data. In my next few posts, I’ll explore different ways to select existing records, and also how to work with has_and_belongs_to_many and has_many, through relationships in a Rails form. It seems to me that these use cases are more common than the nested models form in the complex-form-examples sample app that creates new records but doesn't associate with existing ones.

To start with today, here’s the simplest such form I could think of – I call this a “belongs_to” form:

Here we can see a form for a new “shirt” record; along with the color and size the user can also select the person who owns the shirt. In this example, the shirt and person models have a has_many/belongs_to relationship. This form uses simple HTML <select> and <option> tags to display the list of people, and is generated by Rails ERB code that uses “collection_select” like this:

 1 <p>
 2   Person:<br />
 3   <%= f.collection_select(:person_id,
 4                           Person.all,
 5                           :id,
 6                           :name,
 7                           { :prompt => true })
 8   %>
 9 </p>

The parameters passed to collection_select here indicate that we want to display all of the people that exist, and set the value and label for each <option> tag to the id and name of each person respectively. When the form is submitted, the “person_id” field for this shirt is set to the “id” value of the selected person.

Belongs_to scaffolding with View Mapper

If you need a form like this in your app, you can use my View Mapper gem to generate it for your models as follows… first install it from gemcutter; you’ll need version 0.3.2 at least for this form:

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources 
$ sudo gem install view_mapper
Successfully installed view_mapper-0.3.2

Then you can generate the belongs_to view scaffolding you see above like this:

$ ./script/generate view_for shirt --view belongs_to

View Mapper will open the specified model (“Shirt”), detect the associated model(s) that Shirt belongs to, and then generate the form using collection_select along with all of the other standard scaffolding files. You can also have View Mapper generate the new Shirt model and the view scaffolding code at the same time like this:

./script/generate scaffold_for_view shirt color:string size:integer
                                    --view belongs_to:person

Here you’ve specified the desired attributes for the new shirt model, along with the fact that you want it to belong to a person.

To make the generated view code simple and concise, View Mapper makes a couple of assumptions about your models:

  • It assumes the parent model (“Person” in this example) has an attribute or method called “name.” This is used to display the list of people.
  • It also assumes the child model (“Shirt”) has a method to display the name of the parent model it belongs to (“person_name” in this example).

I decided not to make the View Mapper command line more complex than it already is by providing a way to pass the “name” attribute as another parameter. Instead you can just edit the scaffolding code it produces as desired.

Detailed example and code review

Let’s create a sample app now together using View Mapper, and then review how this “belongs_to view” works. First, create a new Rails app:

$ rails belongs_to
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      etc...

And now let’s create the Person model with first and last name attributes; I’ll also use the console to create a few Person records so we have some existing data to work with:

$ cd belongs_to
$ ./script/generate model person first_name:string last_name:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/person.rb
      create  test/unit/person_test.rb
      create  test/fixtures/people.yml
      create  db/migrate
      create  db/migrate/20100124120247_create_people.rb
$ rake db:migrate
(in /Users/pat/rails-apps/belongs_to)
==  CreatePeople: migrating ===================================================
-- create_table(:people)
   -> 0.0030s
==  CreatePeople: migrated (0.0032s) ==========================================
 $ ./script/console 
Loading development environment (Rails 2.3.5)
>> Person.create :first_name => 'Barack', :last_name => 'Obama'
>> Person.create :first_name => 'George', :last_name => 'Bush'
>> Person.create :first_name => 'Bill',   :last_name => 'Clinton'

Now let’s go ahead and run View Mapper to create the form…

$ ./script/generate scaffold_for_view shirt color:string size:integer
                                      --view belongs_to:person
     warning  Model Person does not contain a has_many association for Shirt.

Here View Mapper is warning us that we haven’t called “has_many :shirts” in the Person model yet; let’s edit person.rb and enter that code:

1 class Person < ActiveRecord::Base
2   has_many :shirts
3 end

Now we can try again:

$ ./script/generate scaffold_for_view shirt color:string size:integer
                                      --view belongs_to:person
     warning  Model Person does not have a name attribute.

Above I mentioned that View Mapper assumes the parent model has a “name” attribute or method. This is required to know how to display each person record in the <select> drop down box on the form. Remember in the call to collection_select we passed in the symbol “:name” – this tells Rails to call the name method for the label of each <option> tag. So let’s create a name method in the Person model that displays the first and last name attributes together:

1 class Person &lt; ActiveRecord::Base
2   has_many :shirts
3 def name 4 "#{first_name} #{last_name}" 5 end
6 end

The highlighted code returns the first and last names concatenated together as a single name. Now View Mapper won’t complain and we can go ahead and create our new form:

$ ./script/generate scaffold_for_view shirt color:string size:integer
                                      --view belongs_to:person
      exists  app/models/
      exists  app/controllers/
… etc …

create app/views/shirts/_form.html.erb
… etc … exists test/fixtures/ create app/models/shirt.rb create db/migrate/20100124121032_create_shirts.rb

Note the output here looks just like what you get from the standard Rails scaffold generator, except for the one additional line I highlighted above. If you run your app now, you’ll be able to see scaffolding for the Shirts model at http://localhost:3000/shirts, and you’ll see the person select box on the new and edit forms.

Let me take a few more minutes to point out a couple of interesting details about the belongs_to scaffolding… first I’ve moved the form fields into a partial shared among the new and edit views; this is the new _form.html.erb file highlighted above in the generator output. If you look at new.html.erb, you’ll see a call to render :partial:

 1 <h1>New shirt</h1>
 2 
 3 <% form_for(@shirt) do |f| %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
5 <p> 6 <%= f.submit 'Create' %> 7 </p> 8 <% end %> 9 10 <%= link_to 'Back', shirts_path %>

The edit.html.erb file looks similar. The actual call to collection_select is in _form.html.erb; that way if you need to display the list of people differently, for example to use a method other than “name” for each person or possibly to use a filtered list of people instead of Person.all, then you just need to make your changes in one place.

And one more interesting detail: if you open up the new Shirts model that View Mapper generated you’ll see this:

1 class Shirt < ActiveRecord::Base
2   belongs_to :person
3 def person_name 4 person.name if person 5 end
6 end

The person_name method I highlighted above returns the person each shirt belongs to; it also checks if person is nil for that shirt. This simplifies the code in index.html.erb and show.html.erb; take a look at show.html.erb for example:

 1 <p>
 2   <b>Color:</b>
 3   <%=h @shirt.color %>
 4 </p>
 5 
 6 <p>
 7   <b>Size:</b>
 8   <%=h @shirt.size %>
 9 </p>
10 
11 <p> 12 <b>Person:</b> 13 <%=h @shirt.person_name %> 14 </p>
15 16 <%= link_to 'Edit', edit_shirt_path(@shirt) %> | 17 <%= link_to 'Back', shirts_path %>

Here the Person field looks just like any other field from the Shirt model. There’s no need to repeat the check for person == nil in the view, and if you ever need to use a Person attribute other than name or if you needed to find the associated person in some more complex way, you’ll only need to make the change to the model and not in each view file.

Again, if you run View Mapper on two has_many/belongs_to models that you’ve already written in your app, you will first need to provide:

  • A “name” method in the parent model, if it’s not already an attribute, and
  • A “[parent_model]_name” method in the child model

If these two methods don’t exist, View Mapper will display warning messages and not proceed; this avoids the confusion you would run into when the scaffolding view code didn’t work.

Next time, I’ll repeat this example but use type ahead/auto_complete to select the person instead...

8 comments Tags:·

How does the complex-form-examples sample app work?

December 30, 2009 · 1 comment

The complex-form-examples sample application written by Ryan Bates and then updated by Eloy Duran and many others is the standard example of how to implement a complex form in Rails. It shows how you can create and update more than one model using the same form. Last month, I wrote about how to create scaffolding for a complex form; using my View Mapper gem you can create a simplified version of the sample app right inside your application for your models.

Today I’d like to take some time to explain how my simplified version of the complex form actually works – the key to using scaffolding in your Rails application is understanding how it works so you can eventually modify and adapt it for you needs, and discard the code you don’t need. However, since this sample application is fairly complex I decided it would be more interesting and fun to follow a single code path through the app in a series of small steps, seeing how each small piece works in detail. To do this, I wrote a series of short blog pages or slides; to see it, click the “Follow Code Path” link… once you’re on the first page you’ll see links to move forward and backward through the slides.

Follow Code Path

Let me know here if you have any feedback on either the content or the style of the code path pages since they are just hard coded HTML for now and not part of my blog. Thanks!

1 comment Tags:···

How to convert a Rails plugin into a gem

December 12, 2009 · 1 comment

Recently I decided to convert my fork of the auto_complete plugin into a gem; I called it “repeated_auto_complete.” In the end it was very easy to convert a plugin into a gem; all I had to do was:

  • Make sure there was a code file in the lib folder with the same name as the gem, and
  • Move or copy the init.rb into a subfolder called “rails.”

This is simple enough, but why do I need to do this? These changes seem rather odd, and also it took me about 3-4 hours of debugging to figure out what I needed to do. The answer has to do with the way the Rails framework loads gems… this is more confusing and complicated than you might think! The rest of this article will show exactly how this works in detail, comparing how gems and plugins are loaded.

The load path works the same way for plugins and gems

Rails treats the load path in the same way for gems as it does for plugins. This is a relief, and also not a surprise since gems and plugins are very similar to each other. The best way to get a sense of how the load path works with plugins and gems is just to inspect it directly in the console. To do this, let’s start by creating a new sample app:

$ rails sample
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
etc…

And now let’s install the auto_complete plugin:

$ cd sample
$ ./script/plugin install git://github.com/rails/auto_complete.git
Initialized empty Git repository in .git/
warning: no common commits
remote: Counting objects: 13, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 13remote:  (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (13/13), done.

If we start a Rails console we can use the command in bold to just look at the load path:

$ ./script/console 
Loading development environment (Rails 2.3.5)
>> $LOAD_PATH.each { |path| puts path }; nil
.../gems/activesupport-2.3.5/lib/active_support/vendor/i18n-0.1.3/lib
.../gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12
.../gems/activesupport-2.3.5/lib/active_support/vendor/memcache-client-1.7.4
/Users/pat/rails-apps/sample/app/controllers/
/Users/pat/rails-apps/sample/app
/Users/pat/rails-apps/sample/app/models
/Users/pat/rails-apps/sample/app/controllers
/Users/pat/rails-apps/sample/app/helpers
/Users/pat/rails-apps/sample/lib
/Users/pat/rails-apps/sample/vendor/plugins/auto_complete/lib
/Users/pat/rails-apps/sample/vendor
.../gems/rails-2.3.5/lib/../builtin/rails_info/
.../gems/rails-2.3.5/lib
etc…

Here we can see the various application paths for my new sample app, as well as the paths of a few of the gems found on my laptop. For clarity, I've shortened the path to my gems folder, and there are many more gems that I’m not showing here. The line in bold indicates that the lib folder for the auto_complete plugin is included in the load paths array, allowing Rails to look inside the auto_complete plugin in order to find missing constants.

Now if I remove the auto_complete plugin…

$ rm -rf vendor/plugins/auto_complete
… and install the repeated_auto_complete gem (from gemcutter):
$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install repeated_auto_complete
Password:
Successfully installed repeated_auto_complete-0.1.0
1 gem installed
Installing ri documentation for repeated_auto_complete-0.1.0...
Installing RDoc documentation for repeated_auto_complete-0.1.0...

… and add a call to config.gem in config/environment.rb:

Rails::Initializer.run do |config|
…
  config.gem "repeated_auto_complete"
…
end

… and view the load path again:

$ ./script/console 
Loading development environment (Rails 2.3.5)
>> $LOAD_PATH.each { |path| puts path }; nil
.../gems/activesupport-2.3.5/lib/active_support/vendor/i18n-0.1.3/lib
.../gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12
.../gems/activesupport-2.3.5/lib/active_support/vendor/memcache-client-1.7.4
/Users/pat/rails-apps/sample/app/controllers/
/Users/pat/rails-apps/sample/app
/Users/pat/rails-apps/sample/app/models
/Users/pat/rails-apps/sample/app/controllers
/Users/pat/rails-apps/sample/app/helpers
/Users/pat/rails-apps/sample/lib
.../gems/repeated_auto_complete-0.1.0/lib
/Users/pat/rails-apps/sample/vendor
.../gems/rails-2.3.5/lib/../builtin/rails_info/
.../gems/rails-2.3.5/lib
etc…

In bold I can see the gem’s lib folder appear just as the plugin’s lib folder did earlier. In fact, it even appears at the same position in the array so classes should be loaded in exactly the same way for a gem as they were for a plugin.

For a gem, you need to have the expected code file in your lib folder

This next issue caused me some serious headaches… hopefully this explanation will save you some time. To explore how gems are loaded by Rails, let’s unpack my “repeated_auto_complete” gem that I just installed above:

$ rake gems:unpack
(in /Users/pat/rails-apps/sample)
Unpacked gem: '/Users/pat/rails-apps/sample/vendor/gems/repeated_auto_complete-0.1.0'

Now I have a local copy of the gem’s code in my vendor/gems directory. Next, let’s see what happens when I delete the “repeated_auto_complete.rb” file from the lib folder – in other words, the code file with the same name as the gem:

$ rm vendor/gems/repeated_auto_complete-0.1.0/lib/repeated_auto_complete.rb 
$ ./script/console 
Loading development environment (Rails 2.3.5)
no such file to load -- repeated_auto_complete
/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require'
/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
.../gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
.../gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
.../gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
.../gems/rails-2.3.5/lib/rails/gem_dependency.rb:208:in `load'
.../gems/rails-2.3.5/lib/initializer.rb:307:in `load_gems'
.../gems/rails-2.3.5/lib/initializer.rb:307:in `each'
.../gems/rails-2.3.5/lib/initializer.rb:307:in `load_gems'
.../gems/rails-2.3.5/lib/initializer.rb:164:in `process'
.../gems/rails-2.3.5/lib/initializer.rb:113:in `send'
.../gems/rails-2.3.5/lib/initializer.rb:113:in `run'
/Users/pat/rails-apps/sample/config/environment.rb:9
etc…

Now we get an error trying to load the Rails environment! The reason why is simple: when Rails loads each gem specified in the environment.rb file with a call to config.gem, it tries to load a code file with exactly the same name. Let’s take a look at line 307 of initializer.rb which appears in the stack trace above:

def load_gems
  unless $gems_rake_task
    @configuration.gems.each { |gem| gem.load }
  end
end

What’s going on here is:

  • load_gems is a method of the Rails::Initializer object. This is the class that we refer to in environment.rb.
  • @configuration is the configuration object that was yielded to the initializer block in environment.rb… in order words the value of the “config” variable that we used in our call to “config.gem”
  • @configuration.gems is an array of GemDependency objects; each one created by a config.gem call. If you’re interested, you can see these are created at line 811 in initializer.rb.
  • For each GemDependency object Rails calls “load.”

Let’s take a look at the GemDependency.load method: line 208 in gem_dependency.rb, also in the stack trace above:

def load
  return if @loaded || @load_paths_added == false
  require(@lib || name) unless @lib == false
  @loaded = true
rescue LoadError
  puts $!.to_s
  $!.backtrace.each { |b| puts b }
end

When the GemDependency object was created earlier, two of its attributes were loaded with values as follows:

  • name: this is set to the name of the gem – “repeated_auto_complete” in my example
  • @lib: this is set to the value of the “:lib” option provided to the config.gem call.

So if you read the code above, you’ll see that Rails allows for three possible cases when loading a gem:

  1. config.gem ‘repeated_auto_complete’ – in this case Rails will call require “repeated_auto_complete” and fail if a code file with that name is not present in the load path. This is what just happened to us above.
  2. config.gem ‘repeated_auto_complete’, :lib => ‘something_else’ – in this case Rails will call require “something_else” and fail if a code file with that name is not present in the load path.
  3. config.gem ‘repeated_auto_complete’, :lib => false – in this case Rails will not call require at all for this gem.

Note that for plugins none of this is an issue: Rails simply adds the plugin's lib folder to the load path array and that's it. But when you convert a plugin into a gem, you need to decide which variation of config.gem your users will have to put in environment.rb.

Init.rb has to move

The next thing Rails does after loading each plugin or gem is to execute a file called init.rb. If you’re the author of a gem or plugin this gives you a chance to initialize your code… for example to add certain modules you’ve written to classes in the application, etc. But as I mentioned at the beginning, if you’re writing a gem or converting a plugin into a gem, you need to be sure the init.rb file is located inside a folder called “rails.” Let’s see if we can find out how Rails does this; first let’s restore the original gem’s code:

$ rm -rf vendor/gems/repeated_auto_complete-0.1.0
$ rake gems:unpack
(in /Users/pat/rails-apps/sample)
Unpacked gem: '/Users/pat/rails-apps/sample/vendor/gems/repeated_auto_complete-0.1.0'

And now let’s edit the init.rb file, located at vendor/gems/repeated_auto_complete-0.1.0/rails/init.rb:

puts caller
ActionController::Base.send :include, AutoComplete
ActionController::Base.helper AutoCompleteMacrosHelper
ActionView::Helpers::FormBuilder.send :include, AutoCompleteFormBuilderHelper

I added the first line in bold: “puts caller.” This will display a stack trace leading to this file when we startup the sample application:

$ ./script/console 
Loading development environment (Rails 2.3.5)
.../gems/rails-2.3.5/lib/rails/plugin.rb:158:in `evaluate_init_rb'
.../gems/activesupport-2.3.5/lib/active_support/core_ext/kernel/reporting.rb:11:in `silence_warnings'
.../gems/rails-2.3.5/lib/rails/plugin.rb:154:in `evaluate_init_rb'
.../gems/rails-2.3.5/lib/rails/plugin.rb:48:in `load'
.../gems/rails-2.3.5/lib/rails/plugin/loader.rb:38:in `load_plugins'
.../gems/rails-2.3.5/lib/rails/plugin/loader.rb:37:in `each'
.../gems/rails-2.3.5/lib/rails/plugin/loader.rb:37:in `load_plugins'
.../gems/rails-2.3.5/lib/initializer.rb:369:in `load_plugins'
.../gems/rails-2.3.5/lib/initializer.rb:165:in `process'
.../gems/rails-2.3.5/lib/initializer.rb:113:in `send'
.../gems/rails-2.3.5/lib/initializer.rb:113:in `run'
/Users/pat/rails-apps/sample/config/environment.rb:9

This time I’ve bolded the “plugin.rb” file; if you look at line 152 in plugin.rb you’ll see this:

def evaluate_init_rb(initializer)
  if has_init_file?
    silence_warnings do
      # Allow plugins to reference the current configuration object
      config = initializer.configuration
      eval(IO.read(init_path), binding, init_path)
    end
  end
end

So this just calls “eval()” on the init.rb file, assuming that “init_path” indicates the path of this file, executing the plugin’s or gem’s initialization code. If you poke around a bit inside of plugin.rb, you’ll see this code for the Rails:Plugin class, which represents each plugin that Rails finds in your application:

def classic_init_path
  File.join(directory, 'init.rb')
end

def gem_init_path
  File.join(directory, 'rails', 'init.rb')
end

def init_path
  File.file?(gem_init_path) ? gem_init_path : classic_init_path
end

If we read the definition of init_path, we see that it uses either rails/init.rb or init.rb, whichever it finds first. This seems to indicate that for a Rails plugin, you can place init.rb either in the “rails” subfolder, or in the main plugin folder, and that it will find and use the copy in the “rails” folder if you happen to have both.

However, for a gem things don’t work this way. You can see why if you look down towards the bottom of the plugin.rb file:

class GemPlugin < Plugin
  # Initialize this plugin from a Gem::Specification.
  def initialize(spec, gem)
    directory = spec.full_gem_path
    super(directory)
    @name = spec.name
  end
  def init_path
    File.join(directory, 'rails', 'init.rb')
  end
end

It turns out that Rails uses a different class to represent gems, called “GemPlugin” (what a confusing name!). In this case we can see that init_path is defined to be the path rails/init.rb and nothing else. This means that gems intended to be used in a Rails application must put their init.rb file in the rails folder.

To summarize this logic:

  • Rails plugins can place init.rb either in the root plugin folder, or in a subfolder called “rails.” If they have both, the rails folder copy will be used.
  • Rails gems must place their init.rb file in a “rails” subfolder.

The actual reason why Rails was implemented this way was that possibly a gem might be used by more than one Ruby framework (e.g. Merb, Sinatra, etc.) and might have different init.rb code for each framework. But a Rails plugin can only be used in a Rails application. Finally, Rails has allowed for plugins to work in the original manner with init.rb in the root folder, or for a plugin to be a gem at the same time, with init.rb in the rails folder.

1 comment Tags:

Scaffolding for auto complete on a complex, nested form

November 25, 2009 · 23 comments

I just updated View Mapper to work with my fork of the Rails auto_complete plugin that allows for repeated text fields on the same complex form. This means that View Mapper can now generate scaffolding code that uses both nested attributes and the auto_complete plugin at the same time, to display a form like this:

To generate this sort of complex form for two of your models you’ll first need to install my “repeated_auto_complete” gem from gemcutter.org:

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install repeated_auto_complete
Successfully installed repeated_auto_complete-0.1.0
1 gem installed
Installing ri documentation for repeated_auto_complete-0.1.0...
Installing RDoc documentation for repeated_auto_complete-0.1.0...

To learn more about repeated_auto_complete and what it does, see: http://patshaughnessy.net/repeated_auto_complete. Now you can generate a complex form like the one shown above for two of your models in a has_many/belongs_to, has_and_belongs_to_many or has_many, :through association by installing View Mapper (version 0.3.1 or later):

$ sudo gem install view_mapper
Successfully installed view_mapper-0.3.1
1 gem installed
Installing ri documentation for view_mapper-0.3.1...
Installing RDoc documentation for view_mapper-0.3.1...

… and then running the “view_for” generator with a view option called “has_many_auto_complete,” like this:

./script/generate view_for group --view has_many_auto_complete:people

 

Detailed Example

To see how easy it is to create a complex form using View Mapper, let’s create one from scratch in a brand new Rails app. You should be able to follow along using the commands below on your machine. First, let’s create a new Rails application:

$ rails complex_auto_complete
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  config/initializers
      create  config/locales
    … etc..
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log

The first thing I’ll do is install the auto_complete plugin. However, since I’m planning to use auto_complete on a complex form, I’ll need to get my fork of auto_complete which I’ve deployed as a gem on gemcutter.org:

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install repeated_auto_complete
Successfully installed repeated_auto_complete-0.1.0
1 gem installed
Installing ri documentation for repeated_auto_complete-0.1.0...
Installing RDoc documentation for repeated_auto_complete-0.1.0...

And let’s update my new app to use the repeated_auto_complete gem by editing the config/environment.rb file:

Rails::Initializer.run do |config|
…etc…
  config.gem "repeated_auto_complete"
…etc…

If you prefer, you can also install this the old fashioned way, using “script/plugin install git://github.com/patshaughnessy/auto_complete.git”. Next, let’s generate a new model called “person” with a couple of fields for name and age, like the ones shown above in the screen shot:

$ cd complex_auto_complete/
$ ./script/generate model person name:string age:integer group_id:integer
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/person.rb
      create  test/unit/person_test.rb
      create  test/fixtures/people.yml
      create  db/migrate
      create  db/migrate/20091125195040_create_people.rb

Note that I’ve also included an integer field for the group id, since in a minute I’ll be adding a belongs_to association for people to groups.

Now I’m ready to use View Mapper… if you haven’t installed that yet, get it from gemcutter.org like this:

$ sudo gem install view_mapper
Successfully installed view_mapper-0.3.1
1 gem installed
Installing ri documentation for view_mapper-0.3.1...
Installing RDoc documentation for view_mapper-0.3.1...

You’ll need at least version 0.3.1 to use auto_complete on a complex form. Now I can use View Mapper to create scaffolding for a new “group” model that has many people with auto_complete like this:

$ ./script/generate scaffold_for_view group name:string
                    --view has_many_auto_complete:people
       error  Table for model 'person' does not exist
              - run rake db:migrate first.

Yes… I forgot to create the people table in my database; if we do that:

$ rake db:migrate
(in /Users/pat/rails-apps/complex_auto_complete)
==  CreatePeople: migrating ===================================================
-- create_table(:people)
   -> 0.0014s
==  CreatePeople: migrated (0.0015s) ==========================================

… and then re-run View Mapper:

$ ./script/generate scaffold_for_view group name:string
                    --view has_many_auto_complete:people
     warning  Model Person does not contain a belongs_to
              association for Group.

… we get a second error message! This time View Mapper is reminding me that I still need to add “belongs_to :group” to the person model in order to get the complex form to work. Let’s do that now:

class Person < ActiveRecord::Base
  belongs_to :group
end

And now I can run View Mapper once more:

$ ./script/generate scaffold_for_view group name:string
                    --view has_many_auto_complete:people
      exists  app/models/
…etc…
      create  app/models/group.rb
      create  test/unit/group_test.rb
      create  test/fixtures/groups.yml
      exists  db/migrate
      create  db/migrate/20091125195715_create_groups.rb
      create  app/views/groups/show.html.erb
      create  app/views/groups/_form.html.erb
      create  app/views/groups/_person.html.erb
      create  public/javascripts/nested_attributes.js
       route  map.connect 'auto_complete_for_group_name',
                          :controller => 'groups',
                          :action => 'auto_complete_for_group_name'
       route  map.connect 'auto_complete_for_person_name',
                          :controller => 'groups',
                          :action => 'auto_complete_for_person_name'
       route  map.connect 'auto_complete_for_person_age',
                          :controller => 'groups',
                          :action => 'auto_complete_for_person_age'

Now you can see the new scaffolding files View Mapper created, including some new scaffolding files peculiar to complex forms, like “nested_attributes.js,” “_form.html.erb,” and “_person.html.erb.” You may also have noticed View Mapper added three new routes related to the auto_complete plugin; these will handle the AJAX requests used to return the auto_complete options to the form.

Now to get it all to work, I just need to create the group table:

$ rake db:migrate
(in /Users/pat/rails-apps/complex_auto_complete)
==  CreateGroups: migrating ===================================================
-- create_table(:groups)
   -> 0.0013s
==  CreateGroups: migrated (0.0014s) ==========================================


Now running my server and creating a new group I see:

If you click “Add a Person” you’ll see nested fields for new Person records appear. This all works exactly the same way as the standard nested attributes scaffolding that I described in my last post. The only difference is that in this form, each of the text fields present in both the parent (“Group”) and child (“Person”) models are displayed using the “text_field_with_auto_complete” method.

I’ll try to write up a detailed walk through of how this scaffolding actually works as soon as I can… there are a lot of interesting details in the code that will be fun to look at. In the meantime, hopefully this scaffolding will make it easier for you to learn how to use auto_complete and nested attributes together in your app.

23 comments Tags:··

Scaffolding for complex forms using nested attributes

November 09, 2009 · 4 comments

While the new nested attributes feature in Rails 2.3 greatly simplifies writing forms that operate on two or more models at the same time, writing a complex form is still a confusing and daunting task even for experienced Rails developers. To make this easier, I just added nested attribute support to my View Mapper gem. This means you can generate complex form scaffolding for two or more models in a has_many/belongs_to, has_and_belongs_to_many or has_many, through relationship.

Example:

If I have a group model that has many people and accepts nested attributes for them like this:

class Group < ActiveRecord::Base
  has_many :people
  accepts_nested_attributes_for :people, :allow_destroy => true
end

… and a person model that belongs to a group:

class Person < ActiveRecord::Base
  belongs_to :group
end

… then View Mapper will allow you to generate scaffolding that displays groups of people all at once, like this:

$ ./script/generate view_for group --view has_many:people
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/groups
…etc…
      create  app/views/groups/_form.html.erb
      create  app/views/groups/_person.html.erb
      create  public/javascripts/nested_attributes.js

Now if I open my Rails app and create a new group, I will see:

This looks just like the standard Rails scaffolding, but with one additional “Add a Person” link. If you click on it, you’ll see the attributes of the person model appear along with a “remove” link, indented to the right:

If I enter some values and submit, ActiveRecord will insert a new record into both the groups table and the people table, and set the group_id value in the new person record correctly:

View Mapper has:

  • inspected your group and person models to find their attributes (columns).
  • validated that they are in a has_many / belongs_to relationship, or in a has_and_belongs_to_many or a has_many, through relationship.
  • checked that you have a foreign key column (“group_id” by default for this example) in the people table if necessary. (The foreign key isn’t in the people table for has_and_belongs_to_many or has_many, through.)
  • generated scaffolding using your attribute and model names, and that uses Javascript to support the “Add a person” and “remove” links.

To get the add/remove links to work, I used a simplified version of the “complex-form-examples” sample application from Ryan Bates and Eloy Duran. Ryan has a few screen casts on this topic as well. In my next post I’ll explain how that works in detail, since understanding the details about how scaffolding works is the first step towards using it successfully in your app.

But for now, you can try this on your machine using the precise commands below…

Creating a new complex form from scratch

Let’s get started by creating a new Rails application; you will need to have Rails 2.3 or later in order to make this work:

$ rails complex-form
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
… etc …
      create  log/production.log
      create  log/development.log
      create  log/test.log

Using the same group has many people example from above, let’s generate a new person model:

$ cd complex-form
$ ./script/generate model person first_name:string last_name:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/person.rb
      create  test/unit/person_test.rb
      create  test/fixtures/people.yml
      create  db/migrate
      create  db/migrate/20091109204744_create_people.rb

And let’s run that migration to create the people table:

$ rake db:migrate
(in /Users/pat/rails-apps/complex-form)
==  CreatePeople: migrating ===================================================
-- create_table(:people)
   -> 0.0013s
==  CreatePeople: migrated (0.0014s) ==========================================

Now we’re ready to run View Mapper. View Mapper contains two generators; one is for creating scaffolding for an existing model, called “view_for,” which is what I used above. There’s also another generator called “scaffold_for_view” which will create a new model along with the scaffolding, using the same syntax as the standard Rails scaffold generator. Let’s use that here, since we have a new app and haven’t created the group model yet:

$ ./script/generate scaffold_for_view group name:string --view has_many:people
     warning  Model Person does not contain a belongs_to association for Group.

Here View Mapper is reminding me that I didn’t specify “belongs_to” in the person model. This saves me the trouble later of figuring out what’s wrong when my complex form doesn’t work. Let’s add that line to app/models/person.rb and try again:

class Person < ActiveRecord::Base
  belongs_to :group
end

$ ./script/generate scaffold_for_view group name:string --view has_many:people
     warning  Model Person does not contain a foreign key for Group.

Duh… I also forgot to include the “group_id” column when I generated the person model. I could have done that by including “group_id:integer” on the script/generate model command line above. Since I already have the person model now, let’s just continue by creating a new migration for the missing column:

$ ./script/generate migration add_group_id_column_to_people
      exists  db/migrate
      create  db/migrate/20091109205711_add_group_id_column_to_people.rb

Editing the migration file:

class AddGroupIdColumnToPeople < ActiveRecord::Migration
  def self.up
    add_column :people, :group_id, :integer
  end
etc…

And running the migration:

$ rake db:migrate
(in /Users/pat/rails-apps/complex-form)
==  AddGroupIdColumnToPeople: migrating =======================================
-- add_column(:people, :group_id, :integer)
   -> 0.0010s
==  AddGroupIdColumnToPeople: migrated (0.0012s) ==============================

Now let’s run View Mapper once more to see whether we have any other problems, or whether we’re ready to generate the complex form scaffolding:

$ ./script/generate scaffold_for_view group name:string --view has_many:people
      exists  app/models/
      exists  app/controllers/
…etc…
      create  app/models/group.rb
      create  test/unit/group_test.rb
      create  test/fixtures/groups.yml
      exists  db/migrate
      create  db/migrate/20091109210312_create_groups.rb
      create  app/views/groups/show.html.erb
      create  app/views/groups/_form.html.erb
      create  app/views/groups/_person.html.erb
      create  public/javascripts/nested_attributes.js

It worked! Just looking at the list of files that View Mapper created, you can get a sense of how it has customized the standard Rails scaffolding to implement the complex form: _form.html.erb, _person.html.erb, nested_attributes.js. More on these details in my next article.

One detail I will point out now is that in order to get you started in the right direction and to allow the complex form to work immediately, the scaffold_for_view generator included the has_many and accepts_nested_attributes_for calls in the new model:

class Group < ActiveRecord::Base
  has_many :people
  accepts_nested_attributes_for :people,
                                :allow_destroy => true,
                                :reject_if => proc { |attrs|
                                  attrs['first_name'].blank? &&
                                  attrs['last_name'].blank?
                                }
end

You don’t need to type in all of this code yourself and know the precise syntax of the accepts_nested_attributes_for method… it’s all generated for you. Later when you start to customize the scaffolding to work for your specific requirements, you’ll have a working example to look at right inside your app.

Finally, we’re need to run the migrations once more since the scaffold_for_view generator created a new group model and corresponding migration for the groups table:

$ rake db:migrate
(in /Users/pat/rails-apps/complex-form)
==  CreateGroups: migrating ===================================================
-- create_table(:groups)
   -> 0.0013s
==  CreateGroups: migrated (0.0014s) ==========================================

Now if you start up Rails and hit http://localhost:3000/groups/new, you’ll see the complex form!

4 comments Tags:·

Paperclip scaffolding

October 16, 2009 · 2 comments

I just updated the View Mapper gem to support Paperclip. You can use it to generate scaffolding code that supports uploading and downloading Paperclip file attachments.

Creating a view for an existing model

If you have a model like this:

class Song < ActiveRecord::Base
  has_attached_file :mp3
end

… you can generate a “Paperclip view” for this model like this:

script/generate view_for song --view paperclip

This will generate a controller, view and other code files that support uploading and downloading files. If you run your app you’ll see the typical scaffolding user interface but with a file field for the “mp3” Paperclip attachment:

View Mapper has:

  • inspected your model (“Song” in this example) and found its Paperclip attachments and other standard ActiveRecord attributes
  • called the Rails scaffold generator and passed in the ActiveRecord columns it found
  • added additional view code to support Paperclip (e.g. set the form to use multipart/form-data encoding)
  • created a file field in the form for each Paperclip attachment (“mp3” in this example), as well as a link to each attachment in the show view code file.

If you’re not very familiar with Paperclip and how to use it or if you just want to get a Rails upload form working very quickly, then View Mapper can help you.

Creating an entirely new Paperclip model and view

View Mapper also provides a generator called “scaffold_for_view” that is identical to the standard Rails scaffold generator, except it will create the specified view. As an example, let’s create a new Rails app from scratch that uses Paperclip; you should be able to type in these precise commands on your machine and get this example to work.

First, let’s install View Mapper and create a new Rails app to display my MP3 library online (ignoring copyright issues for now):

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install view_mapper
Successfully installed view_mapper-0.2.0
1 gem installed
Installing ri documentation for view_mapper-0.2.0...
Installing RDoc documentation for view_mapper-0.2.0...
$ rails music
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
etc…

And now we can generate a new “Song” model that has a Paperclip attachment called “MP3” using View Mapper like this:

$ cd music
$ ./script/generate scaffold_for_view song name:string artist:string
                    album:string play_count:integer --view paperclip:mp3
       error  The Paperclip plugin does not appear to be installed.

Wait… I forgot to install Paperclip; let’s do that and then try again:

$ ./script/plugin install git://github.com/thoughtbot/paperclip.git
Initialized empty Git repository in /Users/pat/rails-apps/music/vendor/plugins/paperclip/.git/
remote: Counting objects: 71, done.
remote: Compressing objects: 100% (59/59), done.
remote: Total 71 (delta 7), reused 29 (delta 3)
Unpacking objects: 100% (71/71), done.
From git://github.com/thoughtbot/paperclip
 * branch            HEAD       -> FETCH_HEAD
$ ./script/generate scaffold_for_view song name:string artist:string
                    album:string play_count:integer --view paperclip:mp3
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/songs
      exists  app/views/layouts/
      exists  test/functional/
etc…

Finally, we just need to run db:migrate – one minor detail here is that the scaffold_for_view generator included the Paperclip columns (“mp3_file_name,” “mp3_content_type,” etc…) in the migration file to create the songs table:

$ rake db:migrate
(in /Users/pat/rails-apps/music)
==  CreateSongs: migrating ====================================================
-- create_table(:songs)
   -> 0.0022s
==  CreateSongs: migrated (0.0024s) ===========================================

Now you can run your app and see the scaffolding UI I showed above, and will be able to upload and download MP3 files using Paperclip.

Let’s take a quick look at exactly what is different about the scaffolding code View Mapper generated vs. the standard Rails scaffolding code:

<h1>New song</h1>

<% form_for(@song, :html => { :multipart => true }) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>

… etc …

  <p>
    <%= f.label :mp3 %><br />
    <%= f.file_field :mp3 %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

<%= link_to 'Back', songs_path %>

The code in bold was generated by View Mapper specifically to support Paperclip since we used the “--view paperclip” command line option. You can see that “:html => { :multipart => :true }” was added to form_for to allow for file uploads, and also a file_field was added for the mp3 Paperclip attachment.

If you take a look at the show view, you’ll see:

<p>
  <b>Name:</b>
  <%=h @song.name %>
</p>

… etc …

<p>
  <b>Mp3:</b>
  <%= link_to @song.mp3_file_name, @song.mp3.url %><br>
</p>

<%= link_to 'Edit', edit_song_path(@song) %> |
<%= link_to 'Back', songs_path %>

Here a link to the file attachment was added, using Paperclip to provide the name and URL of the attachment.

Next I’ll be adding support for nested attributes and complex forms to View Mapper.

2 comments Tags:··

View Mapper: Scaffolding for your models and plugins

October 01, 2009 · 3 comments

View Mapper will generate scaffolding illustrating how to write view code using a specified plugin or feature with your existing models. It can also generate new models.

A couple simple examples:

script/generate view_for office --view auto_complete:address

… will generate Rails scaffolding code that displays a form for an existing “office” model, with auto complete on the “address” field.

script/generate scaffold_for_view office address:string code:string
                                  --view auto_complete:address

… will generate the same form, but also create the “office” model class file, a migration file containing the “address” and “code” columns, and other standard scaffolding files as well.

The idea behind View Mapper is that it’s easy to write simple, concise model classes representing your domain objects using ActiveRecord, but very hard to implement the corresponding views using a combination of HTML, Javascript Rails helper functions, routes, controllers, etc. If you’re not very familiar with a certain plugin you want to use in your app, View Mapper can help you get started in the right direction by generating a working example with scaffolding code.

If you’re developing a Rails plugin or gem it’s easy to write your own View Mapper module for your plugin’s users to call with View Mapper.

Code:   http://github.com/patshaughnessy/view_mapper

Install:

Right now I’ve only deployed View Mapper on gemcutter.org; if you haven’t already you first need to install gemcutter as a gem source:

gem sources -a http://gemcutter.org

Then just install it like any other gem:

sudo gem install view_mapper

Usage:

Two generators are provided, called view_for and scaffold_for_view:

script/generate view_for model [ --view view_name:param ]

This will generate the specified view code for an existing model. The view_for generator will look for your model, inspect all of its columns and then generate standard Rails scaffolding containing a form field for each existing column.

If you also specify a view, then a custom view will be created using the specified Rails feature or plugin, using the specified parameter.

script/generate scaffold_for_view model attributes [ --view view_name:param ]

If you don’t specify a view, then this command is identical to the standard Rails scaffold generator.

If you do specify a view, then the entire working set of a model, views and controller will be generated to implement the specified Rails feature or plugin, using the specified parameter.

Views:

Right now, I’ve implemented seven views:

  • auto_complete: Uses the standard Rails auto_complete plugin to implement type ahead behavior for the specified field.
  • paperclip: Uses the Paperclip plugin to upload and download file attachments.
  • has_many: Displays a complex form to edit two or more associated models.
  • has_many_auto_complete: This is the same as has_many but also uses the auto_complete plugin to implement type ahead behavior for each text field. This view requires you to install my fork of the Rails auto_complete plugin.
  • belongs_to: Generates scaffolding that allows you to select an existing, associated model.
  • belongs_to_auto_complete: Generates scaffolding that allows you to select an existing, associated model using auto_complete.
  • (Default) If no view is specified, then standard Rails scaffold code will be generated.

I’ll be implementing more views in the coming weeks and months. There is also an API for implementing your own View Mapper module, for example to generate code illustrating how to use a plugin or gem you are working on. In the future I’ll document this as well.

3 comments Tags:·

Auto_complete scaffolding

October 01, 2009 · 0 comments

I’ve written a lot here about the Rails auto_complete plugin; I’ve also refactored the auto_complete plugin to support repeated fields and named scopes. Today I’d like to show how you can automatically generate Rails view and controller code with auto_complete behavior for one of your models using a new gem I’ve written called View Mapper. If you’ve never used the auto_complete plugin before this is a great way to learn quickly how to use it in your app; even if you are familiar with the plugin using scaffolding like this can help to get a working auto_complete form up and running quickly and let you concentrate on more important parts of your app.

Let’s say you have an existing model in your app called “Person:”

Class Person < ActiveRecord::Base
end

And suppose the Person model has two string attributes for the person’s name and the name of the office they work in:

class CreatePeople < ActiveRecord::Migration
  def self.up
    create_table :people do |t|
      t.string :name
      t.string :office
      etc…

Now let’s install View Mapper so we can generate an auto_complete view for our person model. Since I’ve only deployed view_mapper on gemcutter.org for now, you’ll also need to add gemcutter as a gem source if you haven’t already.

$ gem sources -a http://gemcutter.org
http://gemcutter.org added to sources
$ sudo gem install view_mapper
Successfully installed view_mapper-0.1.0
1 gem installed
Installing ri documentation for view_mapper-0.1.0...
Installing RDoc documentation for view_mapper-0.1.0...

Now with view_mapper you can run a single command to generate scaffolding that displays the your existing person model’s fields in a form with auto_complete type ahead behavior for the office field:

$ ./script/generate view_for person --view auto_complete:office
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/people
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/people/index.html.erb
      create  app/views/people/show.html.erb
      create  app/views/people/new.html.erb
      create  app/views/people/edit.html.erb
      create  app/views/layouts/people.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/people_controller.rb
      create  test/functional/people_controller_test.rb
      create  app/helpers/people_helper.rb
      create  test/unit/helpers/people_helper_test.rb
       route  map.resources :people
       route  map.connect 'auto_complete_for_person_office',
                          :controller => 'people',
                          :action => 'auto_complete_for_person_office'

This works just like the Rails scaffold generator, except that the view_for generator has also:

  • inspected your person model and found the name and office columns.
  • added a route for “auto_complete_for_person_office” to routes.rb.
  • added a call to “auto_complete_for :person, :office” to PersonController.
  • used text_field_for_auto_complete on the office field in your new and edit forms.
  • inserted “javascript_include_tag :defaults” into views/layouts/people.html.erb to load the prototype javascript library.

If you start up your application and create a few person records with names and addresses, then you will see the auto_complete plugin working!

With this working example right inside your application, you can easily review exactly how the view, route and controller files use auto_complete. After that you can adapt the view to fit into your application’s design and delete the scaffolding you don’t really need or want.

As another example, let’s create an entirely new Rails application completely from scratch, and use View Mapper to setup auto_complete inside it:

$ rails auto_complete_example
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      etc…
$ cd auto_complete_example

First, let’s install the auto_complete plugin. (In a future post I’ll show how to use View Mapper with my fork of auto_complete in a complex form.)

$ ./script/plugin install git://github.com/rails/auto_complete.git
Initialized empty Git repository in /Users/pat/rails-apps/auto_complete_example/vendor/plugins/auto_complete/.git/
remote: Counting objects: 13, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 13 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (13/13), done.
From git://github.com/rails/auto_complete
 * branch            HEAD       -> FETCH_HEAD

Now that we have the plugin installed, let’s create our scaffolding. Along with the “view_for” generator I used above, View Mapper also provides a generator called “scaffold_for_view.” This works the same way, except it just creates a new model the same way the Rails scaffold generator does, instead of inspecting an existing model.

Let’s create the same person model we used above, and an auto_complete view:

$ ./script/generate scaffold_for_view person name:string office:string
                                      --view auto_complete:office
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/people
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/people/index.html.erb
      create  app/views/people/show.html.erb
      create  app/views/people/new.html.erb
      create  app/views/people/edit.html.erb
      create  app/views/layouts/people.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/people_controller.rb
      create  test/functional/people_controller_test.rb
      create  app/helpers/people_helper.rb
      create  test/unit/helpers/people_helper_test.rb
       route  map.resources :people
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/person.rb
      create    test/unit/person_test.rb
      create    test/fixtures/people.yml
      create    db/migrate
      create    db/migrate/20091001161349_create_people.rb
       route  map.connect 'auto_complete_for_person_office',
                          :controller => 'people',
                          :action =>'auto_complete_for_person_office'

Note the syntax is the same as the standard Rails scaffold generator, except I’ve added the “view” parameter to specify we want the auto_complete plugin to be used in the view.

Now we just need to migrate the database schema and run our app:

$ rake db:migrate
(in /Users/pat/rails-apps/auto_complete_example)
==  CreatePeople: migrating ===================================================
-- create_table(:people)
   -> 0.0012s
==  CreatePeople: migrated (0.0015s) ==========================================

And if you add a few records, you’ll see auto_complete working!

I’ll be adding more views to View Mapper soon, and in future posts here I’ll write about how to generate scaffolding for Paperclip file attachments, my version of auto_complete used on a complex form, and also how to write your own views for View Mapper.

0 comments Tags:··

Rails generator tutorial part 2: writing a custom manifest action

September 02, 2009 · 1 comment

In part one of this tutorial I wrote a simple rails generator that creates a controller code file in app/controllers. This time I want to finish up my simple “VersionGenerator” example by enabling it to add a line to the routes.rb file. In other words, I want to be able to use a manifest action called “route” like this:

def manifest
    record do |m|
      m.template('controller.rb', 'app/controllers/version_controller.rb')
      m.route :name => 'version',
              :controller => 'version',
              :action => 'display_version'
    end
  end

The problem for today is how to implement the “route” action, which is not supported by Rails by default. The Rails generator base class code does provide a few other actions you can use in your manifest, like “file” or “directory,” which create a file or directory. Rails even provides a method called “route_resources” to add a “map.resources” line to routes.rb, but not a simple named route line like what we need for this example.

This specific action might be useful for you, but my real goal is to explore how Rails generators work in general and discuss some of the issues you might run into if you need a custom manifest action like this for any purpose. Let’s get started by just adding the new action method we need right inside our generator class…

def route(route_options)
  puts "This will create a new route!"
end

…and see what happens when we run script/generate with manifest method above:

$ ./script/generate version ../build-info/version.txt 
   identical  app/controllers/version_controller.rb
This will create a new route!

Very promising! All we need to do is add the necessary code here and we’ll be all set. But first let’s take a closer look at what’s happening by adding a call to “caller” inside this new method and see how it’s being called by the Rails generator system:

def route(route_options)
  puts "This will create a new route!"
  puts caller
end

Here’s what is displayed when we run this (paths shortened for readability):

$ ./script/generate version ../build-info/version.txt
   identical  app/controllers/version_controller.rb
This will create a new route!
/usr/local/lib/ruby/1.8/delegate.rb:270:in `__send__'
/usr/local/lib/ruby/1.8/delegate.rb:270:in `method_missing'
/path/to/rails-2.3.2/lib/rails_generator/manifest.rb:47:in `send'
/path/to/rails-2.3.2/lib/rails_generator/manifest.rb:47:in `send_actions'
/path/to/rails-2.3.2/lib/rails_generator/manifest.rb:46:in `each'
/path/to/rails-2.3.2/lib/rails_generator/manifest.rb:46:in `send_actions'
/path/to/rails-2.3.2/lib/rails_generator/manifest.rb:31:in `replay'
/path/to/rails-2.3.2/lib/rails_generator/commands.rb:42:in `invoke!'
/path/to/rails-2.3.2/lib/rails_generator/scripts/../scripts.rb:31:in `run'
/path/to/rails-2.3.2/lib/commands/generate.rb:6
/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
./script/generate:3

There’s a lot going on here; in fact, when I started to learn more about Rails generators and how they work I was shocked at how complicated their object model and design are. I won’t try to explain all of it here, but let’s take a closer look at the one line I bolded above: line 31 of lib/rails_generator/scripts.rb

Rails::Generator::Base.instance(options[:generator], args, options)
                      .command(options[:command]).invoke!

This is called when you run script/generate, by the “Rails::Generator::Scripts::Generate” class. Translating this line of Ruby into English, we get: “Create an instance of the specified generator, passing in the arguments and options provided on the command line; then create an instance of the specified command that uses that generator; then invoke the command.”

What this means is that actually Rails generator classes do not do the real work of generating code – Rails generator “commands” do! The lib/rails_generator/commands.rb file defines a series of command objects inside the Rails::Generator::Commands module. The two most important command classes are Create and Destroy. The “Create” command is called when you execute script/generate, like what happened in the stack trace above. This class contains the implementation of the “template” method we used before, plus various other methods like “file” and “directory” that create files and directories, etc.

The “Destroy” command is called when you execute script/destroy from the command line (I removed “puts caller” first before running this):

$ ./script/destroy version ../build-info/version.txt 
This will create a new route!
          rm  app/controllers/version_controller.rb

This class has all of the same methods that the Create class does, except that they perform the opposite action: instead of creating a file or directory, they delete them. The Destroy class also executes the actions in the opposite order, by looping backwards through the manifest that we recorded in our generator. Here the destroy command has deleted our version_controller.rb file we generated earlier. Note that it still displays “This will create a new route!”

If you’re interesting in diving into the real details about how Rails generators work, then read the code in commands.rb very carefully. In particular, look at how the “self.included” and “self.instance” methods at the top work; these methods are used in the line I showed above to create the specified command, supplying the specified generator as an argument to the command constructor. The “invoke!” method on commands actually plays back the recorded manifest. Also all of the actions that are available to your generator's manifest method are defined in this file. One other interesting detail I don’t have space to explain carefully here is that the command objects contain the corresponding generator class as a delegate object; in other words they contain an instance of the generator as a member variable:

# module Rails::Generator::Commands...
class Base < DelegateClass(Rails::Generator::Base)

This explains the top two lines in the stack trace above:

/usr/local/lib/ruby/1.8/delegate.rb:270:in `__send__'
/usr/local/lib/ruby/1.8/delegate.rb:270:in `method_missing'

“DelegateClass” is defined by delegate.rb, which is a Ruby library that implements the delegate pattern. This is why we were able to add the new “route” method right in our generator; when Ruby found the “route” method missing in the Create command, it delegated the call to our generator object.

Obviously we have a problem here: our new “route” method needs to be able to remove a route from routes.rb if the Destroy command is called, as well as create a new one for the Create command. One way to implement our new “route” method would be check the “options[:command]” value that we saw above on line 31 of scripts.rb, like this:

def route(route_options)
  if options[:command] == :create
    puts "This will add a new route to routes.rb."
  elsif options[:command] == :destroy
    puts "This will remove the new route from routes.rb."
  end
end

Now if we run script/generate again, our new method will create a route:

$ ./script/generate version ../build-info/version.txt
      create  app/controllers/version_controller.rb
This will add a new route to routes.rb.

And if we run the destroy command, we’ll remove the route:

$ ./script/destroy version ../build-info/version.txt 
This will remove the new route from routes.rb.
          rm  app/controllers/version_controller.rb

It turns out a cleaner way to implement the “route” method is to directly add the method to both the Create and Destroy command classes. This allows me to call a utility method called “gsub_file” in the Rails::Generator::Commands::Base class which I wouldn’t have direct access to from my generator class. It also avoids the somewhat ugly if statement on the options[:command] value, and finally it might make it easier for me someday to refactor the new route methods into a separate module that I could use with various different generators that might need to add and remove routes.

Anyway, here’s the finished code for the entire generator:

class VersionGenerator < Rails::Generator::NamedBase
  attr_reader :version_path
  def initialize(runtime_args, runtime_options = {})
    super
    @version_path = File.join(RAILS_ROOT, name)
  end
  def manifest
    record do |m|
      m.template('controller.rb', 'app/controllers/version_controller.rb')
      m.route :name => 'version',
              :controller => 'version',
              :action => 'display_version'
    end
  end
end

module Rails
  module Generator
    module Commands

      class Base
        def route_code(route_options)
          "map.#{route_options[:name]} '#{route_options[:name]}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
        end
      end

# Here's a readable version of the long string used above in route_code;
# but it should be kept on one line to avoid inserting extra whitespace
# into routes.rb when the generator is run:
# "map.#{route_options[:name]} '#{route_options[:name]}',
#     :controller => '#{route_options[:controller]}',
#     :action => '#{route_options[:action]}'"

      class Create
        def route(route_options)
          sentinel = 'ActionController::Routing::Routes.draw do |map|'
          logger.route route_code(route_options)
          gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |m|
              "#{m}\n  #{route_code(route_options)}\n"
          end
        end
      end

      class Destroy
        def route(route_options)
          logger.remove_route route_code(route_options)
          to_remove = "\n  #{route_code(route_options)}"
          gsub_file 'config/routes.rb', /(#{to_remove})/mi, ''
        end
      end

    end
  end
end

More or less copied from the existing route_resources action found in commands.rb, this works as follows: If we call script/generate then the route action method in the Create command class is called. This uses gsub_file to look for the “sentinel” or target area of the file and replaces it with the sentinel + the new route code. I also use the “logger” method to display log messages using the Rails::Generator::SimpleLogger class. Here’s what it looks like on the command line:

$ ./script/generate version ../build-info/version.txt
      create  app/controllers/version_controller.rb
       route  map.version 'version',
                 :controller => 'version',
                 :action => 'display_version'

If script/destroy is called, then the second route implementation in the Destroy class is called. This uses gsub_file to remove the route code. Finally, the “route_code” method returns the route code that we want to generate or remove from routes.rb. This time on the command line we get:

$ ./script/destroy version ../build-info/version.txt 
remove_route  map.version 'version',
                 :controller => 'version',
                 :action => 'display_version'
          rm  app/controllers/version_controller.rb

1 comment Tags:·

Tutorial: How to write a Rails generator

August 23, 2009 · 1 comment

I’ve been working on a generator called ViewMapper recently that allows you to create view scaffolding from an existing model. I found writing a Rails generator to be somewhat confusing and hard to do: Where do I need to put my generator class? What does it need to be called? How does it work? This article will show step by step how to write your own Rails generator from scratch – hopefully it will save you some time if you ever need to write your own.

First let's think of some sample Rails code that I might want to generate as an example. This is admittedly contrived, but it’s short and simple enough to show here while still interesting enough to illustrate how a generator would work. Let’s suppose my Capistrano deployment scripts wrote the last commit information from Git into a file called “version.txt” in a folder RAILS_ROOT/../build-info:

commit 217d9bd1d1d508a0f7f1e7afa2b489b130196e33
Author: Pat Shaughnessy <pat@patshaughnessy.net>
Date:   Wed Aug 5 07:45:01 2009 -0400

Now with a controller like this I could display the info, helping me keep track of what version is running on a given development development, QA or production server:

class VersionController < ApplicationController
  VERSION_FILE = '../build-info/version.txt'
  def display_version
    path = File.join(RAILS_ROOT, VERSION_FILE)
    render path
  end
end

If I add a route to this action like this in config/routes.rb:

map.version 'version', :controller => 'version', :action => 'display_version'

…then I’ll get the Git last commit info by just opening http://servername/version in my browser. Obviously it would be simpler to just put the version.txt file under the public folder… but let’s continue with this contrived example for now. Now let’s say I have 5 or 10 different Rails apps, and that I’d like to add the same controller and route to each one. Let’s write a simple Rails generator that would make this easy to do, called the “version” generator. Here’s how to do it…

Step 1: A skeleton Rails generator

All Rails generators are derived from a class called “Rails::Generator::Base.” You can find the source code for this file in vendor/rails/railties/lib/rails_generator/base.rb (link is to the Rails 2.3.3 version). Definately read the source code; there is helpful documentation in the comments and understanding the base class code is indispensible if you plan to use it for your generator.

Here’s an empty, skeleton Rails generator that we can use as a starting point:

class VersionGenerator < Rails::Generator::Base
  def manifest
    record do |m|
      # Do something
    end
  end
end

The name of the class is important: since we want a “version” generator we need to create a class called “VersionGenerator.” The other important detail here is where this class is located: we need to put it in lib/generators/version/version_generator.rb. The reason for all of this is that the code called by script/generate takes the generator name and looks for the corresponding generator class in this location. We can check that our class is being found by just running script/generate with no parameters, like this:

$ ./script/generate 
Usage: ./script/generate generator [options] [args]
…etc…
Installed Generators
  Lib: version
  Rubygems: cucumber, feature, install_rubigen_scripts, integration_spec, rspec, rspec_controller, rspec_model, rspec_scaffold, session
  Builtin: controller, helper, integration_test, mailer, metal, migration, model, observer, performance_test, plugin, resource, scaffold, session_migration

Note that our new “version” generator is listed as “Lib: version,” in bold above. The other generators found on my system were either part of Rails or else part of a gem or plugin. If you don’t see your generator in this list, or if it has the wrong name then double check your folder name: it should be lib/generators/XYZ or lib/generators/version in this example. After you finish developing your generator you'll want to move it into a plugin or gem so it can be reused in different apps on your machine; then "version" would appear in the "Rubygems" list for example.

If we try running our empty generator, we’ll get no output or changes; not a surprise:

$ ./script/generate version

If you see an error like this:

$ ./script/generate version
/usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:
in `gem_original_require': no such file to load --
/path/to/myapp/lib/generators/version/version_generator.rb (MissingSourceFile)

Then you have the wrong file name… it needs to be XYZ_generator.rb, or version_generator.rb in this example. Or if you see:

Missing VersionGenerator class in
  /path/to/myapp/lib/generators/version/version_generator.rb

… then you have the wrong class name inside this file. It needs to be XYZGenerator, or VersionGenerator in this example.

Step 2: Creating code using an ERB template

The way Rails generators work is by creating code using ERB, in just the same way that view code generates HTML for a Rails web site. To see how this works and to take the next step towards writing our version generator, let’s create a new template file for our version controller. Create a new text file called “controller.rb” and put it into this folder: lib/generators/version/templates/controller.rb; in other words, into a new subfolder under version called “templates.” Next, paste in the controller code from above:

class VersionController < ApplicationController
  VERSION_FILE = '../build-info/version.txt'
  def display_version
    path = File.join(RAILS_ROOT, VERSION_FILE)
    render path
  end
end

Now if we add this line to our manifest method back in the VersionGenerator class:

def manifest
  record do |m|
    m.template('controller.rb', 'app/controllers/version_controller.rb')
  end
end

… a new code file called version_controller.rb will be generated inside of the app/controllers folder in our app when we run the generator:

$ ./script/generate version
    create  app/controllers/version_controller.rb

If you take a look at this new version_controller.rb file, you’ll just see the code we pasted into the controller.rb template file. Here’s what happened when we ran script/generate version:

  • The Rails code called by script/generate looked for the “version” generator, by looking for a class called “VersionGenerator” in the lib/generators/version folder, among other places.
  • It called the “manifest” method in VersionGenerator. Using the “record” utility method, a series of actions are recorded that the Rails generator base classes will execute later. In our case, there’s only one action: “template.”
  • The template action indicates that the Rails generator code should run an ERB transformation to generate new code, using the “controller.rb” file. The generated code will then be copied to the app/controllers/version_controller.rb file.

To make our generator interesting, let’s provide a parameter to it that indicates the path of the file containing the version information, instead of hard coding “version.txt.” To do that we can take advantage of another Rails built in class called “Rails::Generator::NamedBase”. If we derive our VersionGenerator class from NamedBase instead of Base, then we’ll get the ability to take a name parameter from the script/generate command line for free. Let start by changing our base class in version_generator.rb:

class VersionGenerator < Rails::Generator::NamedBase

Now if you run the generator, you’ll get some helpful usage information:

$ ./script/generate version
Usage: ./script/generate version VersionName [options]
Etc…

Here the NamedBase class has written the usage info, explaining that an additional parameter is now expected. If we re-run the generator and add the version file name as a parameter, here’s what we’ll get:

$ ./script/generate version ../build-info/version.txt
   identical  app/controllers/version_controller.rb

This shows a helpful feature of Rails generators: they check if you’re about to overwrite some existing code with the new, generated code. In this case, since we already had a controller called “version_controller.rb” the Rails generator code displayed the “identical” message. Our new generated controller is identical to the previous one since we haven’t used the version name parameter yet. To take advantage of the version file name parameter and our new NamedBase base class, we need to add some new code to VersionGenerator, in bold:

class VersionGenerator < Rails::Generator::NamedBase
  attr_reader :version_path
  def initialize(runtime_args, runtime_options = {})
    super
    @version_path = File.join(RAILS_ROOT, name)
  end
  def manifest
    record do |m|
      m.template('controller.rb', 'app/controllers/version_controller.rb')
    end
  end
end

The way this works is:

  • The NamedBase base class parses the script/generate command line, expecting an additional parameter which indicates the name of some object.
  • This name is provided in "name," an instance variable/attribute of the NamedBase class.
  • The VersionGenerator class assumes "name" indicates the relative path of a version file, determines the full path of the file and then saves the full path in the @version_path attribute.

I mentioned above that Rails generators work by running an ERB transformation; to try using ERB in this example, we just need to refer to the new “version_path” attribute inside the controller.rb template file, like this:

class VersionController < ApplicationController
  VERSION_PATH = '<%= version_path %>'
  def display_version
    render VERSION_PATH
   end
end

The “version_path” variable in this template refers to the version_path attribute of the VersionGenerator class above. I’ve also removed the File.join line from here since we now handle that in the generator itself. If we run the generate command and provide the name of the version file, we’ll get this result:

$ ./script/generate version ../build-info/version.txt
overwrite app/controllers/version_controller.rb? (enter "h" for help) [Ynaqdh] Y
       force  app/controllers/version_controller.rb

As I mentioned above, Rails checks whether you’re about to overwrite some existing code file. Since in this case our controller code file is now different that what we had before (VERSION_PATH and not VERSION_FILE; no File.join, etc.) we get an overwrite warning. I just entered “Y” to overwrite the old file.

If we look at our new controller, app/controllers/version_controller.rb, we now have:

class VersionController < ApplicationController
  VERSION_PATH = '/path/to/myapp/../build-info/version.txt'
  def display_version
    render VERSION_PATH
   end
end

We’ve successfully generated a new VersionController class, based on a parameter passed to our “version” generator. While this is a good start, to be able to use this action in our app we still need to add a route to config/routes.rb. It turns out this is a lot harder to do with a generator, since we will need to insert a new line of code in an existing file, and not just generate a new routes.rb file. We will also need to worry about how to remove the route line when script/destroy is run. It’s possible to do all of this with a Rails generator, but will require a more thorough understanding of how Rails generators actually work. I’ll explain all of that in my next post…

1 comment Tags:·

Generating view scaffolding code for existing models

July 24, 2009 · 1 comment

(Updated October 2009)

I just rewrote and cleaned up the code I describe below here and published it as a new gem called View Mapper on gemcutter.org. See my usage article for more details. I also rethought and redesigned the commands I describe below… the part about nested attributes will not work for now. I’m planning to reimplement that soon.

I’ve been thinking for a while that a generator that creates view scaffolding for an existing model or models would be really useful. For example, I want to be able to write this by hand:

class Group < ActiveRecord::Base
  has_many :people
end
class Person < ActiveRecord::Base
  belongs_to :group
end

… and then run a generator and get a working view to edit and display groups of people. Recently I started developing a generator called “ViewMapper” that does this; see: http://github.com/patshaughnessy/view_mapper.

The idea is that it creates a view that maps to your existing models. Writing model classes is often very easy; using ActiveRecord allows you to write concise, short code files. However, writing a view that displays a form for these models is often very hard, requiring knowledge of a long list of Rails functions, macros, classes, as well as the usual HTML and Javascript. Right now ViewMapper it just a plugin; so you can install it into your app like this:

$ ./script/plugin install git://github.com/patshaughnessy/view_mapper.git

Probably it should be packaged as a gem instead… this is still work-in-progress. At the moment it supports:

  • Creating simple form based on the columns of an existing model
  • Creating a complex form based on the columns of two associated models in a many-one relationship, using nested attributes
If you’re interested and have time, try the examples below or better yet try the generator on some of your own models and let me know what you think.

Example 1: Create a view based on the columns of an existing model

Let’s say I have an existing model class that manages a series of person records:

class Person < ActiveRecord::Base
end

Now to create the corresponding view with ViewMapper, just run this command:

$ ./script/generate view_for person
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/people
      exists  app/views/layouts/
      exists  test/functional/
      create  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/people/index.html.erb
      create  app/views/people/show.html.erb
      create  app/views/people/new.html.erb
      create  app/views/people/edit.html.erb
      create  app/views/layouts/people.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/people_controller.rb
      create  test/functional/people_controller_test.rb
      create  app/helpers/people_helper.rb
      create  test/unit/helpers/people_helper_test.rb
       route  map.resources :people

If you run your Rails app you will see the usual scaffolding UI:

This looks just like the scaffolding page you would have gotten from the Rails scaffolding generator. In fact, I’ve based the “view_for” generator class (ViewForGenerator) on the existing Rails ScaffoldGenerator class, so it generates the same code.

The difference here is that the scaffolding code was based on the properties of the existing Person model. Here’s what the view_for generator did:

  • Find the specified model class
  • Inspect it and get a list of column names and data types
  • Create the scaffolding code as usual
If you also want to create your model class at the same time, then you don’t need ViewMapper; you would just run the standard Rails scaffold generator like this:

$ ./script/generate scaffold person name:string age:integer

ViewMapper provides you with the flexibility to create the view scaffolding after the fact for a model you or someone else has already created in your app.

Example 2: Create a view using a complex form based on two associated models in a many-one relationship, using Rails 2.3 nested attributes

Let’s suppose you have two related models that describe groups of people:

class Group < ActiveRecord::Base
  has_many :people
end
class Person < ActiveRecord::Base
  belongs_to :group
end

Again, writing model classes is really easy. With just a few lines of code I have told ActiveRecord to manage two separate tables and their relationship.

Now you can use ViewMapper to create a complex form for creating and editing groups and people at the same time like this:

$ ./script/generate nested_attributes_view_for group containing:people
       error  Model 'Group' does not accept nested attributes
              for child models 'people'

This error indicates that my Group model is missing the “accepts_nested_attributes_for” directive. Since the generator creates view code that relies on the nested attributes feature, it won’t let you create the view for a model that is missing this line. This sort of helpful error message is possible since ViewMapper is inspecting an existing model class, and not just creating new scaffold code.

Now if we add the missing line (suddenly my model is less concise... nested attributes aren't that easy to use yet!):

class Group < ActiveRecord::Base
  has_many :people
  accepts_nested_attributes_for :people,
    :allow_destroy => true,
    :reject_if => proc { |attrs| attrs['name'].blank? && attrs['age'].blank? }
end

…we can re-run the generator:

$ ./script/generate nested_attributes_view_for group containing:people
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/groups
      exists  app/views/layouts/
      exists  test/functional/
      create  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/groups/index.html.erb
      create  app/views/groups/show.html.erb
      create  app/views/groups/new.html.erb
      create  app/views/groups/edit.html.erb
      create  app/views/layouts/groups.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/groups_controller.rb
      create  test/functional/groups_controller_test.rb
      create  app/helpers/groups_helper.rb
      create  test/unit/helpers/groups_helper_test.rb
       route  map.resources :groups

And now if we run our app, we get view scaffolding illustrating how to create and edit people and groups all at the same time:

If I save the group…

And re-editing the same group:

More to come… as I said this is work-in-progress. As a next step I’m planning to improve the scaffolding code for nested attributes by adding javascript to dynamically add/delete the child objects. But after that I'm thinking about:

  • Adding a “paperclip_view_for” generator that creates a file upload form for a model that contains a “has_attached_file” directive.
  • Adding a “auto_complete_view_for” generator that adds “auto_complete_for” to the controller and uses “text_field_for_auto_complete” in the form.
  • Adding the ability for you to extend this with your own scaffolding code.

1 comment Tags:·

Taming the beast: Using JRuby and RSpec to test a J2EE application

June 25, 2009 · 0 comments

Working with J2EE applications is something like wandering in a jungle: you never quite know what wild animal you’ll find around the next corner… it might be an ORM tool like Hibernate; it could be an application framework like Spring or Struts with lots of confusing XML files, or it might just be a long list of obscure JAR files that you need to find and download… whatever it is, you’re guaranteed to spend countless hours wasting time learning things you really didn’t want to know. This article will show how you can get your J2EE application under control by using JRuby and RSpec… but first, let’s take a quick look at what RSpec is and how it’s normally used with Ruby.

Using RSpec with Ruby

If you are a Ruby developer, you would probably write code similar to this to add up an array of numbers:

class Calculator
  def self.sum numbers
    numbers.inject do |sum, x|
      sum+x
    end
  end
end

This is simple enough to run:

$ irb
irb(main):001:0> require 'calculator.rb'
=> true
irb(main):002:0> Calculator.sum [2, 3]
=> 5

One of the best things about Ruby are all of the different testing tools available to you – for example, you could write a test for this method using RSpec like this:

require 'calculator.rb'
describe "Calculator" do
  it "should add numbers correctly" do
    Calculator.sum([1, 2]).should == 3
    Calculator.sum([2, 2]).should == 4
    Calculator.sum([2, 3, 4]).should == 9
  end
end

And then run the spec as follows:

$ spec calculator_spec.rb 
.
Finished in 0.001703 seconds
1 example, 0 failures

RSpec allows you to write test code that is readable, and also behavior-oriented; that is, the tests reflect the way an end user might actually behave. In fact, RSpec is really just the first step towards behavior-driven-development. The Ruby community also benefits from other tools such as WebRat, Cucumber, etc., that can make testing very easy and effective.

A J2EE sample app

Let’s rewrite the “sum” Ruby method above using Java. To make this feel more like an actual J2EE application, we’ll use a service class that will perform the actual sum operation for us, and set it up with the Spring framework. We can start by writing an XML file called “ApplicationContext.xml” for Spring to use, and declare a bean called “calculatorService:”

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="calculatorService"
    class="calculator.CalculatorServiceImpl">
  </bean>
</beans>

In a real J2EE app, we would probably have an interface called “CalculatorService” like this:

package calculator;
public interface CalculatorService {
  public int sum(int[] array);
}

And then we’d implement it using a concrete class, like this:

package calculator;
public class CalculatorServiceImpl implements CalculatorService {
  public int sum(int[] array)
  {
    int sum = 0;
    for (int number: array)
    {
      sum += number;
    }
    return sum;
  }
}

And finally, let’s write a simple Java command line client for this so we can test running it from the command line:

package calculator;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CalculatorApp {
  public static void main(String[] args) throws Exception {
    int[] array = { 2, 3 };
    ClassPathXmlApplicationContext application_context =
      new ClassPathXmlApplicationContext("ApplicationContext.xml");
    CalculatorService calculator =
      (CalculatorService)application_context.getBean("calculatorService");
    System.out.println("2 + 3 is: " + Integer.toString(calculator.sum(array)));
  }
}

This will also help us figure out how to write the ruby spec later. If you run this from Eclipse or your favorite Java IDE, you’ll get:

Jun 25, 2009 12:21:58 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3479e304: display name [org.springframework.context.support.ClassPathXmlApplicationContext@3479e304]; startup date [Thu Jun 25 12:21:58 EDT 2009]; root of context hierarchy
Jun 25, 2009 12:21:58 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [ApplicationContext.xml]
Jun 25, 2009 12:21:58 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@3479e304]: org.springframework.beans.factory.support.DefaultListableBeanFactory@604788d5
Jun 25, 2009 12:21:58 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@604788d5: defining beans [calculatorService]; root of factory hierarchy
2 + 3 is: 5

This is starting to feel like an actual “Enterprise” J2EE application now that we have an XML config file and lots of confusing information being logged!

Running RSpec with JRuby

Now that we have a J2EE application to test, let’s get started with JRuby. First, you will need to download and install JRuby. This is really just a matter of downloading the TAR file and then placing the JRuby bin folder on your path. Test that you have it setup properly by running this command:

$ jruby --version
jruby 1.1.5 (ruby 1.8.6 patchlevel 114) (2008-11-03 rev 7996) [x86_64-java]

Now let’s update our Ruby spec from above and get it to work with Java. Here’s what we had for Ruby:

require 'calculator.rb'
describe "Calculator" do
  it "should add numbers correctly" do
    Calculator.add([1, 2]).should == 3
    Calculator.add([2, 2]).should == 4
    Calculator.add([2, 3, 4]).should == 9
  end
end

The key to using JRuby to test Java is to add this line at the top of the spec:

require 'java'

This tells JRuby that we want to allow the Ruby code to call Java directly. But what code should we try to call? The nice thing about the Spring framework is that is makes a series of “beans,” i.e. Java objects, available to us. The reason I went to the trouble of adding the Spring framework to this sample app is that for a real J2EE application using Spring to create and load a Java object will be the simplest path towards testing your target application. What I did while testing a real J2EE application was to:

  • Identify what business logic I wanted to test
  • Look for the Java object that was the top-level, simplest interface to that business logic under the user interface layer
  • Find a bean in the ApplicationContext.xml file that corresponded to that Java object. In my case, there actually was no such bean, and I had to slightly modify the application I was testing by adding a new <bean> tag to one of the Spring XML files.

If your application is not using Spring, you might simply be able to create a Java object directly from your Ruby code, or in the worst case scenario you might have to make Java code changes to the target J2EE app to break dependencies that the object you’d like to test has on other objects, interfaces, services, etc., allowing you to create it in isolation. Michael Feathers has written an entire book on dependency breaking techniques.

Back to this sample app: here in our Ruby spec we can follow the same pattern that I used above in the command line Java client: calling “ClassPathXmlApplicationContext” to get the application context, and then creating a bean with getBean. The other thing we need to add are “include_class” directives that indicate to JRuby which Java classes should be loaded from the classpath and made available to the Ruby script. Here’s the spec code with the new JRuby code changes in bold:

require 'java'
include_class 'org.springframework.context.ApplicationContext'
include_class
  'org.springframework.context.support.ClassPathXmlApplicationContext'
describe "Calculator" do
  it "should add numbers correctly" do
    application_context =
      ClassPathXmlApplicationContext.new "ApplicationContext.xml"
    calculator =  application_context.getBean "calculatorService"
    calculator.sum([1, 2]).should == 3
    calculator.sum([2, 2]).should == 4
    calculator.sum([2, 3, 4]).should == 9
  end
end

Now let’s try to run it using JRuby. Another trick with JRuby is knowing how to use the “-S” option – this allows you to run a Ruby command like “gem” or “spec,” but inside a JRuby session. So here’s how to run our new spec using JRuby:

$ jruby -S spec calculator_spec.rb 
(eval):1:in `include_class': cannot load Java class
  org.springframework.context.ApplicationContext (NameError)
  from /Users/pat/src/jruby-1.1.5/lib/ruby/site_ruby/1.8/builtin/javasupport/core_ext/object.rb:38:in `eval'
  from /Users/pat/src/jruby-1.1.5/lib/ruby/site_ruby/1.8/builtin/javasupport/core_ext/object.rb:67:in `include_class'
  from /Users/pat/src/jruby-1.1.5/lib/ruby/site_ruby/1.8/builtin/javasupport/core_ext/object.rb:38:in `each'
  from /Users/pat/src/jruby-1.1.5/lib/ruby/site_ruby/1.8/builtin/javasupport/core_ext/object.rb:38:in `include_class'
  from calculator_spec.rb:2

So what is the error message all about? This just means that JRuby wasn’t able to find the ApplicationContext class from Spring on the classpath. But what is the classpath anyway? It would be nice to be able to simply specify the classpath using a command line option, the way you do with a java command line using “-cp” for example. But for JRuby you need to specify the classpath as an environment setting. To make this easier, I wrote a simple shell script for running a JRuby spec:

BASE=`pwd`
CLASSPATH=$BASE/lib/spring-2.5.1.jar
export CLASSPATH
jruby -S spec $1

This just gets the current working directory and constructs the classpath setting, indicating where to find the Spring JAR file. Finally we export the classpath value and call JRuby. This will work on the Mac and Linux; for Windows you would need something slightly different. Anyway, now I can run my specs like this:

$ ./jruby-spec.sh calculator_spec.rb 
F
1)
NativeException in 'Calculator should add numbers correctly'
java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
org/springframework/util/ClassUtils.java:73:in `<clinit>'
org/springframework/core/io/DefaultResourceLoader.java:52:in `<init>'
org/springframework/context/support/AbstractApplicationContext.java:198:in `<init>'
org/springframework/context/support/AbstractRefreshableApplicationContext.java:80:in `<init>'
org/springframework/context/support/AbstractXmlApplicationContext.java:58:in `<init>'
org/springframework/context/support/ClassPathXmlApplicationContext.java:119:in `<init>'
org/springframework/context/support/ClassPathXmlApplicationContext.java:66:in `<init>'
sun/reflect/NativeConstructorAccessorImpl.java:-2:in `newInstance0'
sun/reflect/NativeConstructorAccessorImpl.java:39:in `newInstance'
sun/reflect/DelegatingConstructorAccessorImpl.java:27:in `newInstance'
java/lang/reflect/Constructor.java:513:in `newInstance'
org/jruby/javasupport/JavaConstructor.java:226:in `new_instance'
org/jruby/java/invokers/ConstructorInvoker.java:100:in `call'
org/jruby/java/invokers/ConstructorInvoker.java:180:in `call'
etc...

Oops – it turns out that Spring actually requires the Apache Commons logging JAR file to be on the classpath also. This sample app is definitely reminding me of a complex J2EE app running on WebSphere or WebLogic! Let’s add commons-logging-1.0.4.jar to the classpath in my shell script and try again:

$ ./jruby-spec.sh calculator_spec.rb 
Jun 25, 2009 1:58:15 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4c6c3e: display name [org.springframework.context.support.ClassPathXmlApplicationContext@4c6c3e]; startup date [Thu Jun 25 13:58:15 EDT 2009]; root of context hierarchy
Jun 25, 2009 1:58:15 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [ApplicationContext.xml]
F
1)
NativeException in 'Calculator should add numbers correctly'
org.springframework.beans.factory.BeanDefinitionStoreException:
  IOException parsing XML document from class path resource
  [ApplicationContext.xml]; nested exception is java.io.FileNotFoundException:
  class path resource [ApplicationContext.xml] cannot be opened because
  it does not exist
org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java:334:in `loadBeanDefinitions'
org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java:295:in `loadBeanDefinitions'

Once again, I’ve forgotten something in my class path: this time it’s the ApplicationContext.xml file which I passed into ClassPathXmlApplicationContext in the spec. As the name implies, I need to put the XML file on the classpath in order for Spring to be able to find it. Let’s cut to the chase and put everything I need onto the classpath… here’s the final version of jruby-spec.sh (the classpath line split into two for readability):

BASE=`pwd`
CLASSPATH=$BASE/bin:$BASE/resources:$BASE/lib/spring-2.5.1.jar:
  $BASE/lib/commons-logging-1.0.4.jar
export CLASSPATH
jruby -S spec $1

Now JRuby will be able to find everything that it needs: Spring, Apache Common Logging, the ApplicationContext.xml file, and also the application’s class files saved under “bin” by Eclipse. Now if I run the spec once more it should all work:

$ ./jruby-spec.sh calculator_spec.rb 
Jun 25, 2009 2:02:22 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1717d968: display name [org.springframework.context.support.ClassPathXmlApplicationContext@1717d968]; startup date [Thu Jun 25 14:02:22 EDT 2009]; root of context hierarchy
Jun 25, 2009 2:02:22 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [ApplicationContext.xml]
Jun 25, 2009 2:02:22 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@1717d968]: org.springframework.beans.factory.support.DefaultListableBeanFactory@5a21fdc8
Jun 25, 2009 2:02:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5a21fdc8: defining beans [calculatorService]; root of factory hierarchy
F
1)
TypeError in 'Calculator should add numbers correctly'
for method sum expected [[I]; got: [org.jruby.RubyArray];
  error: argument type mismatch calculator_spec.rb:8:
Finished in 0.70559 seconds
1 example, 1 failure

What’s this error all about? I do have the correct classpath now, but what does “RubyArray” mean? I thought I passed an array of integers into the calculator service:

calculator.sum([1, 2]).should == 3

Well it turns out that Ruby and JRuby aren’t quite the same thing. Since JRuby is actually a Java application itself, it implements each Ruby class with a corresponding Java class. For the Ruby Array class, JRuby has created a class called “org.jruby.RubyArray.” When you call Java code and pass in Ruby objects, JRuby actually provides the Java equivalent of these Ruby objects to the Java code. That’s why we get an error here; our calculator service doesn’t expect a RubyArray – it expects int[] instead.

To avoid this error, we need to convert the RubyArray into a normal Java array, using a JRuby method called “to_java()”, like this:

calculator.sum([1, 2].to_java(:int)).should == 3

To_java takes a symbol as a parameter, which indicates what type each element of the Ruby array should be converted into. Now our spec will pass!

$ ./jruby-spec.sh calculator_spec.rb 
Jun 25, 2009 2:15:37 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1717d968: display name [org.springframework.context.support.ClassPathXmlApplicationContext@1717d968]; startup date [Thu Jun 25 14:15:37 EDT 2009]; root of context hierarchy
Jun 25, 2009 2:15:37 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [ApplicationContext.xml]
Jun 25, 2009 2:15:37 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@1717d968]: org.springframework.beans.factory.support.DefaultListableBeanFactory@5a21fdc8
Jun 25, 2009 2:15:37 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5a21fdc8: defining beans [calculatorService]; root of factory hierarchy
.
Finished in 0.28237 seconds
1 example, 0 failures

Taking a step back, I think what we’ve done is quite interesting; we have:

  • Pulled a J2EE application out of Eclipse, IntelliJ or whatever IDE you project uses, and started to run it from the command line instead. For me, avoiding a confusing and complex IDE makes the application easier to work with… more like a Rails app.
  • Documented what the classpath needs to be and what JAR files the application requires in an understandable text file format, rather than having it hidden away in a confusing Eclipse dialog box.
  • Exposed the J2EE application’s business logic to testing below the user interface layer – nothing here involves a web browser or user interface testing tool like Selenium.
  • Started to apply behavior driven development to a J2EE application, using the best tools available, which happen to be written in Ruby.
In a future post, I’ll try to take the next step of using Cucumber features to test this same J2EE sample app. It will be interesting to find out if Cucumber works will with JRuby.

0 comments Tags:··

Auto complete for complex forms using nested attributes in Rails 2.3

June 14, 2009 · 8 comments

I just updated my fork of the auto_complete plugin to support Rails 2.3 nested attributes. Thanks to Anthony Frustagli for the code and ideas that I used as the basis for this fix.

Basic usage:

To use auto_complete on a complex form with nested attributes, just call “text_field_for_auto_complete” right on the form builder object yielded by form_for or fields_for, like in this example:

<% parent_form.fields_for :children do |child_form| %>
  <%= child_form.text_field_with_auto_complete :name, {},
        { :method => :get, :skip_style => true } %>
<% end %>

If you have Rails 2.3, this code will iterate over each child object and display a text field with auto complete support. My plugin will generate HTML and Javascript that works even when repeated in a loop like this. Also note that I’ve left off the object name parameter from text_field_with_auto_complete. It’s not needed now, since the object is indicated by the surrounding call to fields_for. The other parameters are optional and are taken unchanged from the original auto_complete plugin:

  • “:method => :get” indicates GET requests should be used by the AJAX calls to load the pick list values, avoiding problems with CSRF protection.
  • And “:skip => :true” indicates that the inline CSS stylesheet used by the auto complete drop down Prototype code should be skipped. Since we’re iterating over child objects we don’t want the same CSS code repeated once for each; instead include it once in a parent object’s call to text_field_for_auto_complete or else just include it manually somewhere.

That’s it – it should just work. If you’re interested in learning more about how to use nested attributes and what my plugin is actually doing, read on…

Details:

To learn more, let’s take a look at a simple nested attribute example, using the Projects/Tasks models from Ryan Bates' complex forms screen cast:

class Project < ActiveRecord::Base
  has_many :tasks
  accepts_nested_attributes_for :tasks, :allow_destroy => true
end
class Task < ActiveRecord::Base
  belongs_to :project
end

A project has many tasks, and each task belongs to a project. Here I’ve also declared that each project “accepts nested attributes for” tasks. This is a new method added to ActiveRecord in Rails 2.3… for lots of examples and explanation just take a look directly at the new nested_attributes.rb code file in Rails 2.3. In a nutshell, “accepts_nested_attributes_for” tells ActiveRecord that the project model should be able to save the attributes of the associated task model objects when a project is saved. This means that when I submit my project form, it can also contain a series of task fields as well. For example, my view code might look something like this:

<% form_for @project do |project_form| %>
  <p>
    <%= project_form.label :name, "Project:" %>
    <%= project_form.text_field :name %>
  </p>
  <% project_form.fields_for :tasks do |task_form| %>
    <%= task_form.label :name, "Task:" %>
    <%= task_form.text_field :name %>
  <% end %>
<% end %>

This displays a name text field for the project, and then calls “fields_for” again right on the form builder yielded by form_for. This is new for Rails 2.3. In earlier versions of Rails you had to explicitly iterate over the child objects and call fields_for for each one. Now in Rails 2.3, you can call fields_for as a method of the parent form and it will automatically iterate over all of the child objects and call fields_for. If we take a look at the HTML generated by this example form, we’ll find something like:

<input id="project_name" name="project[name]"
  size="30" type="text" value="Some project" />
<input id="project_tasks_attributes_0_id"
  name="project[tasks_attributes][0][id]" type="hidden" value="1" />
<input id="project_tasks_attributes_0_name"
  name="project[tasks_attributes][0][name]" type="text" value="Task one" />
<input id="project_tasks_attributes_1_id"
  name="project[tasks_attributes][1][id]" type="hidden" value="2" />
<input id="project_tasks_attributes_1_name"
  name="project[tasks_attributes][1][name]" type="text" value="Task two" />

I’ve simplified this to make it more readable. You can see the iteration by project_form.fields_for :tasks, and that for each task there’s an <input> tag for the “name” field, along with another hidden <input> tag containing the task’s “id” attribute. The most important detail here is the name given to each of these tags: “project[tasks_attributes][0][name]” for example. Since the tasks are nested attributes of the project, they are displayed using the PARENT_OBJECT[CHILD_OBJECTS_attributes][INDEX][FIELD] pattern, while for the project we get the simple OBJECT[FIELD] pattern. This is the key to making nested attributes work. In our project model, when we called “has_many :tasks”, Rails defined some new methods for us on the Project class to handle tasks: tasks, tasks=, task_ids, task_ids= and a couple of others as well. Now with Rails 2.3, when we call “accepts_nested_attributes_for :tasks” Rails defines another new method for Project called tasks_attributes= in order to process all of the new nested parameters for tasks when the complex project form is submitted. This is the reason for the “_attributes” in the naming pattern used in the form.

Now… how do we get auto complete to work for this form? The problem with auto complete on a complex form has always been that the Javascript and HMTL used by the Prototype library assumes that the <input> tag, <div> tag and related Javascript code would be unique on the HTML page. If you just call the text_field_with_auto_complete macro from the standard auto_complete plugin like this…

<% project_form.fields_for :tasks do |task_form| %>
  <%= text_field_with_auto_complete :task, :name, {},
        { :method => :get, :skip_style => true } %>
<% end %>

… it will not work. The first problem is that text_field_with_auto_complete does not know that fields_for is iterating over the child tasks, or which task is currently being processed in the iteration. But even if you were able to identify the current task object somehow, you would still get HTML like this:

<input id="task_name" name="task[name]" size="30" type="text" />
<div class="auto_complete" id="task_name_auto_complete"></div>
<script type="text/javascript">
//<![CDATA[
var task_name_auto_completer = new Ajax.Autocompleter('task_name',
  'task_name_auto_complete', '/projects/auto_complete_for_task_name',
  {method:'get'})
//]]>
</script>

…

<input id="task_name" name="task[name]" size="30" type="text" />
<div class="auto_complete" id="task_name_auto_complete"></div>
<script type="text/javascript">
//<![CDATA[
var task_name_auto_completer = new Ajax.Autocompleter('task_name',
  'task_name_auto_complete', '/projects/auto_complete_for_task_name',
  {method:'get'})
//]]>
</script>

Now the <input id=“task_name”> tag is repeated on the same page, and the Javascript call to Ajax.Autocompleter('task_name', … ) will not work since the browser will not be able to identify which <input> tag to use.

If you use my plugin instead of the original auto_complete plugin…

$ rm -rf vendor/plugins/auto_complete
$ ./script/plugin install git://github.com/patshaughnessy/auto_complete.git
Initialized empty Git repository in /Users/pat/rails-app/vendor/plugins/auto_complete/.git/
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 20 (delta 5), reused 0 (delta 0)
Unpacking objects: 100% (20/20), done.
From git://github.com/patshaughnessy/auto_complete
 * branch            HEAD       -> FETCH_HEAD

… and restart your Rails app, then you can change your view to call text_field_with_auto_complete as a method of the form builder, like this:

<% project_form.fields_for :tasks do |task_form| %>
  <%= task_form.text_field_with_auto_complete :name, {},
  { :method => :get, :skip_style => true } %>
<% end %>

Note that I’ve also dropped :task as a parameter since that’s implicit in the call to fields_for. In fact, since text_field_with_auto_complete is now a method of the FormBuilder object (“task_form”), it has access to the task object currently being processed in the iteration. Now if you refresh the same form you’ll instead get this HTML instead:

<input id="project_tasks_attributes_0_name"
  name="project[tasks_attributes][0][name]"
  size="30" type="text" value="Task one" />
<div class="auto_complete" id="project_tasks_attributes_0_name_auto_complete">
</div><script type="text/javascript">
//<![CDATA[
var project_tasks_attributes_0_name_auto_completer =
  new Ajax.Autocompleter('project_tasks_attributes_0_name',
  'project_tasks_attributes_0_name_auto_complete',
  '/projects/auto_complete_for_task_name',
  {method:'get', paramName:'task[name]'})
//]]>
</script>

…

<input id="project_tasks_attributes_1_name"
  name="project[tasks_attributes][1][name]"
  size="30" type="text" value="Task two" />
<div class="auto_complete" id="project_tasks_attributes_1_name_auto_complete">
</div><script type="text/javascript">
//<![CDATA[
var project_tasks_attributes_1_name_auto_completer =
  new Ajax.Autocompleter('project_tasks_attributes_1_name',
  'project_tasks_attributes_1_name_auto_complete',
  '/projects/auto_complete_for_task_name',
  {method:'get', paramName:'task[name]'})
//]]>
</script>

This looks much better, and will actually work for the following reasons:

  • The <input> tags have the correct names, using the PARENT_OBJECT[CHILD_OBJECTS_attributes][INDEX][FIELD] pattern from fields_for. This means that the field values will be processed properly by ActiveRecord when the form is submitted.
  • My changes to the auto_complete plugin have picked up the child object index, 0 and 1 in this example, and included it in the <input> tag’s id, the <div> tag id and well as the associated Javascript code that calls Ajax.Autocompleter. Since all of the tag id’s are unique, the auto complete behavior works properly again for each text field.
  • The original “task” class name and “name” field name are passed unchanged into the Ajax calls to the server. This means that in your controller you can continue to use “auto_complete_for :task, :name” as usual, without worrying about the complex form and the fact that the task fields are repeated multiple times, etc.:
    Ajax.Autocompleter('project_tasks_attributes_1_name',     
      'project_tasks_attributes_1_name_auto_complete',
      '/projects/auto_complete_for_task_name',
      {method:'get', paramName:'task[name]'})
    Here the third parameter to Ajax.Autocompleter, "/projects/auto_complete_for_task_name", is the AJAX URL which you need to account for in routes.rb, and paramName:'task[name]' tells the auto_complete_for handler in your controller to get the task names as usual, and protects the server side code from all of the complexity around the tag id, names, child object index, etc.

8 comments Tags: