For a while I’ve been thinking that writing a Rails generator is a fairly difficult thing to do. First you need to learn about Thor and the Rails generator system: what sort of Ruby class you need to write, how to handle arguments, how to run commands like “copy_file”, etc. Then you need to write ERB files to produce the code that you’d like to generate, which is always a chore.
So last night I wrote a gem called generate_from_diff that let’s you create Rails 3 generators automatically using a code record/playback model. Here’s how it works:
- You make a code change in some Rails project, and commit it to your Git repo.
- You run a command from my gem to extract this code change from the Git repo into a new Rails 3 generator.
- You later run this new generator in any other Rails 3 app, and your recorded code changes are “played back,” or applied to your new project, using the Unix patch utility written by Larry Wall.
You’ve created a Rails 3 generator without ever writing a single line of generator code!
Disclaimer: I just got this working last night, so it’s still very rough; but if the idea seems worthwhile I’ll clean it up and try to make it more robust and useable. Another disclaimer: none of this will work on Windows since it relies on the Unix patch utility.
Example: recording code into a new generator
Let’s look at an example to try to make this a bit clearer. Support I create a new Rails 3 application:
$ rails new first_app
create
create README
create Rakefile
create config.ru
create .gitignore
create Gemfile
etc...
And let’s run bundle install to be sure I have all the required gems. This is usually not necessary for a new, empty Rails app, but I want to have my Gemfile.lock file created... more on that in a moment.
$ cd first_app
$ bundle install
Fetching source index for http://rubygems.org/
Using rake (0.8.7)
Using abstract (1.0.0)
Using activesupport (3.0.0.beta4)
Using builder (2.1.2)
etc...
And let’s create a new Git repo here and check the empty application into it:
$ git init
Initialized empty Git repository in /Users/pat/.../first_app/.git/
$ git add .
$ git commit -m"New sample app"
This first Git revision will serve as the baseline for recording my new generator, which I’ll do in a minute. The reason I ran bundle install was to insure that the Gemfile.lock file would be included in the baseline... and so not included in the recorded code change.
Now let’s write some code that I can record into a new generator. Suppose at my company I want to create a controller that returns the build number, diagnostics and some other information about each of my Rails apps. I might do this by creating a new controller as follows:
$ rails generate controller build_info
create app/controllers/build_info_controller.rb
invoke erb
create app/views/build_info
invoke test_unit
create test/functional/build_info_controller_test.rb
invoke helper
etc...
And in this new controller I’ll add a single index action:
class BuildInfoController < ApplicationController
def index
render :text => 'Some interesting build info about this app...'
end
end
Finally, I’ll add a route to send “build_info” requests to this action:
FirstApp::Application.routes.draw do |map|
match 'build_info' => 'build_info#index'
...etc...
This is somewhat silly, but it’s simple enough to use as an example here. Now if I run my app I’ll get this fascinating page:

Next let’s “record” this sample code by using generate_from_diff to create a new Rails generator for it. First, we need to install generate_from_diff:
$ gem install generate_from_diff
Successfully installed generate_from_diff-0.0.1
1 gem installed
Installing ri documentation for generate_from_diff-0.0.1...
Installing RDoc documentation for generate_from_diff-0.0.1...
Next, let’s commit my new controller and routes.rb code changes:
$ git add .
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: app/controllers/build_info_controller.rb
# new file: app/helpers/build_info_helper.rb
# modified: config/routes.rb
# new file: test/functional/build_info_controller_test.rb
# new file: test/unit/helpers/build_info_helper_test.rb
#
$ git commit -m"Build info"
Created commit 037ca3b: Build info
5 files changed, 22 insertions(+), 0 deletions(-)
create mode 100644 app/controllers/build_info_controller.rb
create mode 100644 app/helpers/build_info_helper.rb
create mode 100644 test/functional/build_info_controller_test.rb
create mode 100644 test/unit/helpers/build_info_helper_test.rb
One last detail: we need to edit the Gemfile to load generate_from_diff into this application:
source 'http://rubygems.org'
gem 'generate_from_diff'
gem 'rails', '3.0.0.beta4'
etc...
Finally we create our new generator by just running this command:
$ rails generate generator_from_diff build_info HEAD~1 HEAD
create lib/generators/build_info
create lib/generators/build_info/build_info_generator.rb
create lib/generators/build_info/USAGE
run git diff --no-prefix HEAD~1 HEAD from "."
Ok - what happened here was that I ran a generator called “generator_from_diff” that is located inside the generate_from_diff gem. I provided it with the name of the new generator I want to create: “build_info” in this example. This is similar to how the Rails 3 “generator generator” works: it generates a generator. But next I provide two Git revisions, in this example “HEAD~1” and “HEAD,” the first and second revisions in my Git repo. The first value is the baseline revision: what to compare to. In this example, this is my new, empty Rails application. The second revision is what code to record and save into the new generator, in this example this revision contains all of my controller and routes.rb changes.
Example: playing back code using a generator
Now let’s see if we can use this new Rails generator to copy the build_info controller and route into a different Rails app. First, let’s create a second, new Rails application:
$ cd ..
$ rails new second_app
create
create README
create Rakefile
create config.ru
...etc...
$ cd second_app
And next, let’s copy the new generator we just created in the first app, over to this new app:
$ mkdir lib/generators
$ cp -r ../first_app/lib/generators/build_info lib/generators
And now we can just run our new generator to playback the code changes that I recorded above:
$ rails generate build_info
gsub lib/generators/build_info/build_info.patch
run patch -p0 < /Users/pat/.../second_app/lib/generators/build_info/build_info.patch from "."
patching file app/controllers/build_info_controller.rb
patching file app/helpers/build_info_helper.rb
patching file config/routes.rb
patching file test/functional/build_info_controller_test.rb
patching file test/unit/helpers/build_info_helper_test.rb
That’s it! Now I can run the second app and see the same build status page that we had before:

How does this actually work?
Here’s what is going on under the hood. First, when you record your code changes into the new generator like this:
$ rails generate generator_from_diff build_info HEAD~1 HEAD
... the “generator_from_diff” code actually runs the “git diff” command like this:
$ git diff HEAD~1 HEAD
diff --git a/app/controllers/build_info_controller.rb
b/app/controllers/build_info_controller.rb
new file mode 100644
index 0000000..c44d83e
--- /dev/null
+++ b/app/controllers/build_info_controller.rb
@@ -0,0 +1,5 @@
+class BuildInfoController < ApplicationController
+ def index
...etc...
This produces a list of all the text changes that were made from one revision (HEAD~1) to another (HEAD). These are then saved into a file called “build_info.patch,” saved inside the new generator.
Later, the text differences, the “patch,” are applied to whatever new or existing files are found relative to the current directory when you run the generator. This copies the new controller file as well as the new route inside of routes.rb into the other application. The patch file is applied using this command:
patch -p0 < lib/generators/build_info/build_info.patch
I use patch instead of git apply to avoid the need to match revision id’s; these will be different from one repo to another.
Ok sounds interesting - so where are you going with this next?
I think it’s cool to be able to “record” Rails generators without writing any code. If this seems like a useful idea, then I’ll spend some more time to clean it up and make it more robust. For example, I’m thinking of adding some code to warn you before the patch is run if there are unexpected files present, or if some expected files are missing.
Next, I’m considering enhancing the gem to perform search/replace using arguments or options that you specify when recording the generator. For example, suppose you recorded a series of code changes that had to do with a model called “Person.” But imagine that you want to be able to playback those code changes in a target application that might have a different model name, “User” instead of “Person” for example. Then the gem could search/replace on the patch file, both when it’s recorded and again when it’s played back, to cause the generated code to use User instead of Person.
Tags:rails
LEFT OUTER JOIN queries are a great way to handle associations that may contain missing or null values. For example, suppose I have an application with a has_one/belongs_to association between books and authors.
class Book < ActiveRecord::Base
has_one :author
end
class Author < ActiveRecord::Base
belongs_to :book
end
If I want to identify the books that don’t have any authors, I can use this SQL statement:
SELECT * FROM books LEFT OUTER JOIN authors ON authors.book_id = books.id
WHERE authors.book_id IS NULL
I need to use LEFT OUTER JOIN here since a normal INNER JOIN query would only return books that have authors. Or if I want to sort the books by their author, I could use:
SELECT * FROM books LEFT OUTER JOIN authors ON authors.book_id = books.id
ORDER BY authors.name
This sort would work even if some of the books didn’t have an author assigned to them. If we had used a normal INNER JOIN query the books with no author would have been dropped from the sorted list.
Today I’m going to discuss how to use LEFT OUTER JOIN with ActiveRecord and SearchLogic, allowing you to handle associations that might have missing records easily and cleanly.
ActiveRecord :joins
Before we go any farther, let’s setup the book/author example so we can explore how ActiveRecord handles joins:
$ rails outer_join_example
$ cd outer_join_example
$ script/generate model book title:string
$ script/generate model author name:string book_id:integer
$ rake db:migrate
And don’t forget to add the has_one/belongs_to lines to the two new models:
class Book < ActiveRecord::Base
has_one :author
end
class Author < ActiveRecord::Base
belongs_to :book
end
And now let’s create some books and authors we can use to test with:
$ script/console
Loading development environment (Rails 2.3.5)
>> [ 'One', 'Two', 'Three' ].each do |title|
?> book = Book.create :title => title
>> book.author = Author.create :name => "Author of Book #{title}"
>> book.save
>> end
Let’s get started by looking at how you would sort the books by their author’s name, using a normal INNER JOIN query:
>> ActiveRecord::Base.logger = Logger.new(STDOUT)
>> Book.find(:all, :joins => :author, :order => 'authors.name')
.collect { |book| book.title }
Book Load (1.4ms) SELECT "books".* FROM "books" INNER JOIN "authors"
ON authors.book_id = books.id ORDER BY name
=> ["One", "Three", "Two"]
Great – here using find with the :joins option we’ve told ActiveRecord to load all the books, and to join with the authors table so we can sort on the authors.name column. But watch what happens if I add a couple of new books that have no author yet, and then repeat the same INNER JOIN sort query:
>> Book.create :title => 'Four'
>> Book.create :title => 'Five'
>> Book.find(:all, :joins => :author, :order => 'name')
.collect { |book| book.title }
Book Load (1.8ms) SELECT "books".* FROM "books" INNER JOIN "authors"
ON authors.book_id = books.id ORDER BY name
=> ["One", "Three", "Two"]
Books “Four” and “Five” are dropped entirely. This is because INNER JOIN only includes records in the result set that have values from both the books and authors tables. Since there is no author record for these books, they don’t appear at all.
ActiveRecord :include
The simplest way to get around this problem is to use ActiveRecord’s :include option, instead of the :joins option. Let’s rewrite that call to Book.find and use :include instead:
>> Book.find(:all, :include => :author, :order => 'authors.name')
.collect { |book| book.title }
Book Load Including Associations (2.8ms) SELECT "books"."id" AS t0_r0,
"books"."title" AS t0_r1, "books"."created_at" AS t0_r2,
"books"."updated_at" AS t0_r3, "authors"."id" AS t1_r0,
"authors"."name" AS t1_r1, "authors"."book_id" AS t1_r2,
"authors"."created_at" AS t1_r3, "authors"."updated_at" AS t1_r4 FROM "books"
LEFT OUTER JOIN "authors" ON authors.book_id = books.id ORDER BY authors.name
=> ["Four", "Five", "One", "Three", "Two"]
Now we get books “Four” and “Five” in the sorted list; this is because ActiveRecord uses a LEFT OUTER JOIN query when you specify the :include option. Note they appear first since their author name values are both null, which is sorted before any of the other authors. If you read the ActiveRecord log output, you’ll see it generated a very complex SQL statement that explicitly mentions each column of both the books and authors tables. It used a column naming pattern, “t0_r1” etc., to identify each column. The reason for all of this is that ActiveRecord is executing the “Load Including Associations” operation, which is loading each and every attribute for all of the associated objects. This guarantees that we get every possible value for all the books and their authors.
So this is perfect! Now I can write a named_scope to sort books by their author name, including null authors, like this:
class Book < ActiveRecord::Base
has_one :author
named_scope :sorted_by_author_with_nulls,
{ :include => :author, :order => 'authors.name' }
end
And trying it in the console:
>> Book.sorted_by_author_with_nulls.collect { |book| book.title }
Book Load Including Associations (3.9ms) SELECT "books"."id" AS t0_r0,
"books"."title" AS t0_r1, "books"."created_at" AS t0_r2,
"books"."updated_at" AS t0_r3, "authors"."id" AS t1_r0,
"authors"."name" AS t1_r1, "authors"."book_id" AS t1_r2,
"authors"."created_at" AS t1_r3, "authors"."updated_at" AS t1_r4 FROM "books"
LEFT OUTER JOIN "authors" ON authors.book_id = books.id ORDER BY authors.name
=> ["Four", "Five", "One", "Three", "Two"]
I can now also write this named_scope called “missing_author,” which returns just the books that have a missing author:
class Book < ActiveRecord::Base
has_one :author
named_scope :sorted_by_author_with_nulls,
{ :include => :author, :order => 'authors.name' }
named_scope :missing_author,
{ :include => :author, :conditions => 'authors.name IS NULL' }
end
And in the console:
>> Book.missing_author.collect { |book| book.title }
Book Load Including Associations (1.9ms) SELECT "books"."id" AS t0_r0,
"books"."title" AS t0_r1, "books"."created_at" AS t0_r2,
"books"."updated_at" AS t0_r3, "authors"."id" AS t1_r0,
"authors"."name" AS t1_r1, "authors"."book_id" AS t1_r2,
"authors"."created_at" AS t1_r3, "authors"."updated_at" AS t1_r4 FROM "books"
LEFT OUTER JOIN "authors" ON authors.book_id = books.id
WHERE (authors.name IS NULL)
=> ["Four", "Five"]
What’s wrong with :include?
It sounds like I’m done and that the :include option is the perfect way to handle associations that might contain null or missing values. But not so fast… it turns out that using :include is often a bad idea. A great resource on :joins and :include options is Ryan Bates’s screen cast from 2009. If you think you need to use one of these find options, invest a few minutes and listen to Ryan’s explanation. Here I’ll just mention a couple of potential problems with using :include with named scopes:
1. It‘s potentially slow and wasteful. :include causes ActiveRecord to load every attribute for every included associated object, which is often much more information than you really need. If your named scope only requires one or two columns from the associated table, then using :include might be overkill.
2. As Ryan mentions, you lose control over the “SELECT” portion of the query. This means that if you write a named scope that uses :include, like the missing_author example above, then you can’t chain it together with other named scopes that contain a select option. For example, you couldn’t write code like this:
Book.missing_author.scoped(:select => 'DISTINCT title')
This might appear to work, but if you look at the query ActiveRecord generates you’ll notice that it doesn’t contain the DISTINCT keyword. This is because the Load Including Associations query ignores the select scope.
A better way to write a named_scope that uses LEFT OUTER JOIN is actually to go back to the :joins option, like this:
class Book < ActiveRecord::Base
has_one :author
named_scope :sorted_by_author_with_nulls, {
:joins => 'LEFT OUTER JOIN authors ON authors.book_id = books.id',
:order => 'authors.name'
}
named_scope :missing_author, {
:joins => 'LEFT OUTER JOIN authors ON authors.book_id = books.id',
:conditions => 'authors.id IS NULL'
}
end
Here I’ve written the join clause of the query for each scope, typing in the LEFT OUTER JOIN syntax explicitly. Here’s the example from above using DISTINCT:
>> Book.missing_author.scoped(:select => 'DISTINCT title')
Book Load (1.1ms) SELECT DISTINCT title FROM "books"
LEFT OUTER JOIN authors ON authors.book_id = books.id
WHERE (authors.id IS NULL)
=> [#<Book title: "Four">, #<Book title: "Five">]
It works now since the :joins option simply adds LEFT OUTER JOIN to the query without rewriting the entire SELECT statement. Then ActiveRecord combines the join with the select scope, inserting the DISTINCT keyword into the query.
Customizing SearchLogic to use LEFT OUTER JOIN
Ok – now using the same techniques from my last post, let’s see if we can customize SearchLogic to support this sort of named scope. First let’s install SearchLogic into our sample app:
$ script/plugin install http://github.com/binarylogic/searchlogic.git
And next, let’s implement a new named scoped called “without_author” that will work the same way as the “missing_author” scope from above. We’ll use method_missing the same way that SearchLogic does; see my last article for a more complete explanation. Here we go:
class Book < ActiveRecord::Base
has_one :author
class << self
def method_missing(name, *args, &block)
if name == :without_author
named_scope :without_author,
{
:joins => 'LEFT OUTER JOIN authors
ON authors.book_id = books.id',
:conditions => 'authors.id IS NULL'
}
without_author
else
super
end
end
end
end
And let’s try it in the console:
>> Book.without_author.collect { |book| book.title }
Book Load (1.3ms) SELECT "books".* FROM "books" LEFT OUTER JOIN authors
ON authors.book_id = books.id WHERE (authors.id IS NULL)
=> ["Four", "Five"]
Works perfectly! All we need to do now is generalize this for any model and association. Following the pattern from the SearchLogic source code, to get a list of associated models I can call “reflect_on_all_associations.” This will return a list of AssociationReflection objects, each of which represents an association between this model (Book) and some other model (Author). See the ActiveRecord source code for more details.
Here’s what the code looks like with the call to reflect_on_all_associations:
class Book < ActiveRecord::Base
has_one :author
class << self
def method_missing(name, *args, &block)
association_names =
reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^without_(#{association_names.join("|")})$/
named_scope :without_author,
{
:joins => 'LEFT OUTER JOIN authors
ON authors.book_id = books.id',
:conditions => 'authors.id IS NULL'
}
without_author
else
super
end
end
end
end
You can see that we construct a regex pattern from a list of associated model names, joined with the | character. For example, if there were 3 associated models, then we would use /without_(assoc1|assoc2|assoc3)/… in this example since Book has only one associated model, we have a simple regex pattern: /without_(author)/.
Let’s make sure the code still works in the console:
>> Book.without_author.collect { |book| book.title }
Book Load (1.0ms) SELECT "books".* FROM "books" LEFT OUTER JOIN authors
ON authors.book_id = books.id WHERE (authors.id IS NULL)
=> ["Four", "Five"]
So far so good. The next thing we need to do is replace the hard coded values in the call to named_scope. To do this, we’ll need the AssociationReflection object that corresponds to the matching association name. Here’s some code that does that:
def matching_association(match)
@matching_association ||= reflect_on_all_associations.detect do |assoc|
assoc.name.to_s == match
end
end
If we pass in the matching name from the regex pattern above, e.g. matching_association($1), then we’ll get the corresponding association object. Once we have that, we can get the name of that association’s database table and primary key:
associated_table = matching_association($1).table_name
associated_key = matching_association($1).primary_key_name
One subtle point to note here: ActiveRecord's AssociationReflection.primary_key_name method actually returns the foreign key column for this association, the book_id column in the authors table, and not the primary key of the authors table, which would have just been id. It works properly, but possibly should have been named foreign_key_name. Anyway, now we’ll need these values to construct our LEFT OUTER JOIN and condition strings. These methods do that:
def join_clause(associated_table, associated_key)
outer_join = "LEFT OUTER JOIN #{associated_table}"
outer_join += " ON #{associated_table}.#{associated_key}"
outer_join += " = #{quoted_table_name}.#{primary_key}"
end
def where_clause(associated_table, associated_key)
"#{associated_table}.#{associated_key} IS NULL"
end
Finally, we can put it all together like this:
class Book < ActiveRecord::Base
has_one :author
class << self
def method_missing(name, *args, &block)
association_names =
reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^without_(#{association_names.join("|")})$/
associated_table = matching_association($1).table_name
associated_key = matching_association($1).primary_key_name
named_scope name,
{
:joins => join_clause(associated_table, associated_key),
:conditions => where_clause(associated_table, associated_key)
}
send(name)
else
super
end
end
def matching_association(match)
@matching_association ||= reflect_on_all_associations.detect do |assoc|
assoc.name.to_s == match
end
end
def join_clause(associated_table, associated_key)
outer_join = "LEFT OUTER JOIN #{associated_table}"
outer_join += " ON #{associated_table}.#{associated_key}"
outer_join += " = #{quoted_table_name}.#{primary_key}"
end
def where_clause(associated_table, associated_key)
"#{associated_table}.#{associated_key} IS NULL"
end
end
end
Note that I used “send(name)” to call the named scope we just created with named_scope. Let’s make sure it still all works properly in the console:
>> Book.without_author.collect { |book| book.title }
Book Load (1.0ms) SELECT "books".* FROM "books"
LEFT OUTER JOIN authors ON authors.book_id = "books".id
WHERE (authors.book_id IS NULL)
=> ["Four", "Five"]
Phew – it does! Ideally I would have some RSpec examples setup to test this, instead of using the console.
Just like in my last article, the last thing I’ll do today is move these class methods out of the Book model and into a new module called SearchLogicExtensions, which in my application I saved into a file called config/initializers/search_logic_extensions.rb. This causes the method missing code to be loaded when my app starts up. At the bottom I extend ActiveRecord::Base with the new module, so the named scope can be used by every model in my application:
module SearchLogicExtensions
def method_missing(name, *args, &block)
association_names =
reflect_on_all_associations.collect { |assoc| assoc.name }
if name.to_s =~ /^without_(#{association_names.join("|")})$/
associated_table = matching_association($1).table_name
associated_key = matching_association($1).primary_key_name
named_scope name,
{
:joins => join_clause(associated_table, associated_key),
:conditions => where_clause(associated_table, associated_key)
}
send(name)
else
super
end
end
def matching_association(match)
@matching_association ||= reflect_on_all_associations.detect do |assoc|
assoc.name.to_s == match
end
end
def join_clause(associated_table, associated_key)
outer_join = "LEFT OUTER JOIN #{associated_table}"
outer_join += " ON #{associated_table}.#{associated_key}"
outer_join += " = #{quoted_table_name}.#{primary_key}"
end
def where_clause(associated_table, associated_key)
"#{associated_table}.#{associated_key} IS NULL"
end
end
ActiveRecord::Base.extend(SearchLogicExtensions)
Tags:rails
The Ruby interpreter calls method_missing on a Ruby object whenever it receives a message (method call) that it cannot handle. One of the best examples of using method_missing that I’ve come across is in the SearchLogic plugin, which allows you to dynamically create named scopes. Today I’m going to take some time to explain how method_missing works, show how it’s used by SearchLogic, and finally show how you can use method_missing yourself to customize SearchLogic’s behavior.
Simple sorting with SearchLogic
Suppose I have an ActiveRecord model called “book” with a “title” attribute:
$ rails books
$ cd books
$ script/generate model book title:string
$ rake db:migrate
$ script/console
>> ["one", "two", "three"].each { |title| Book.create :title => title }
The best way in Rails to display the books sorted by title would be to use a named scope like this in my model:
class Book < ActiveRecord::Base
named_scope :sorted_by_title, { :order => 'title' }
end
If I use a trick my colleague Niranjan Sarade showed me, we can see the SQL produced by ActiveRecord for the named scope in the console, like this:
$ script/console
>> ActiveRecord::Base.logger = Logger.new(STDOUT)
>> Book.sorted_by_title.collect { |book| book.title }
Book Load (1.6ms) SELECT * FROM "books" ORDER BY title
=> ["one", "three", "two"]
This is a good example of why I’m a Rails developer: with just a single line of code in my model I can sort the values in a database column! But it gets even easier if I install SearchLogic:
$ script/plugin install git://github.com/binarylogic/searchlogic.git
Now I get a whole series of named scopes created for me automatically! For example, I can now just call the “ascend_by_title” and “descend_by_title” named scopes as if I had written them myself:
$ script/console
>> Book.ascend_by_title.collect { |book| book.title }
Book Load (1.3ms) SELECT * FROM "books" ORDER BY books.title ASC
=> ["one", "three", "two"]
>> Book.descend_by_title.collect { |book| book.title }
Book Load (2.0ms) SELECT * FROM "books" ORDER BY books.title DESC
=> ["two", "three", "one"]
Brilliant! Using SearchLogic I can filter/sort on any attribute of any model in my application without writing a single line of code. I can even sort and filter on attributes of associated models, e.g. if I had “Book has_many :authors,” I could sort books by their author’s names, or sort the authors for each book, etc., all without writing any SQL or even Ruby code.
Sorting with NULL values last
Recently at my day job I came across a business requirement to sort a list of values, always displaying the NULL or empty values at the end of the list. In our example, this would mean that there might be some books with missing titles:
$ script/console
>> 2.times { Book.create :title => nil }
Here’s the behavior I get from the ascend_by_title and descend_by_title named scopes with NULL values:
>> Book.ascend_by_title.collect { |book| book.title }
Book Load (2.5ms) SELECT * FROM "books" ORDER BY books.title ASC
=> [nil, nil, "one", "three", "two"]
>> Book.descend_by_title.collect { |book| book.title }
Book Load (2.7ms) SELECT * FROM "books" ORDER BY books.title DESC
=> ["two", "three", "one", nil, nil]
In other words, the NULL values are considered to be less than the other values by the database server, and are sorted accordingly. To get the behavior I want, I need to use a slightly more complex sorting pattern in a named scope, like this:
class Book < ActiveRecord::Base
named_scope :sorted_by_title_nulls_last,
{ :order => 'title IS NULL, title' }
named_scope :sorted_by_title_nulls_last_desc,
{ :order => 'title IS NULL, title DESC' }
end
Trying it out in the console:
$ script/console
>> Book.sorted_by_title_nulls_last.collect { |book| book.title }
Book Load (2.6ms) SELECT * FROM "books" ORDER BY title IS NULL, title
=> ["one", "three", "two", nil, nil]
>> Book.sorted_by_title_nulls_last_desc.collect { |book| book.title }
Book Load (2.5ms) SELECT * FROM "books" ORDER BY title IS NULL, title DESC
=> ["two", "three", "one", nil, nil]
These named scopes first sort on “title IS NULL” and then on the actual title value, causing the NULL values to appear at the end. This code is fairly clean and would work fine – the problem I had at my day job, however, was that I needed this sorting behavior for about six different columns in various database tables. To make this work, I would need to repeat these two named scopes in each model for each attribute that I wanted to sort on. If only SearchLogic had supported this sorting behavior, I wouldn’t need to copy and paste all of the named scopes.
Using method_missing
Specifically, here’s the method that I wished SearchLogic had implemented for me:
>> Book.ascend_by_title_nulls_last
undefined method `ascend_by_title_nulls_last' for #<Class:0x2234978>
As you can see it doesn’t. But I mentioned above that SearchLogic works by using method_missing; let’s see if I can use method_missing myself and implement the NULLs last behavior… in other words, let’s see if I can use metaprogramming to implement the “nulls last” named scopes on all of my model classes all at once!
I’ll start by using the simplest possible implementation of method_missing:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
puts "This method is missing: #{name}"
end
end
end
Here “class << self” indicates that method_missing will be a class method on my Book class; Ruby calls method_missing on the class that is missing the method. The code here simply writes out a message when an unknown method is called:
>> Book.ascend_by_title_nulls_last
This method is missing: ascend_by_title_nulls_last
=> nil
Now I’m ready to think about how to implement the nulls last sorting. But not so fast: it turns out that I have just broken my model class! Aside from SearchLogic, ActiveRecord itself also uses method_missing extensively. The simplest examples of this are the “find_by_...” methods. For example, calling find_by_title should return the book record with the given title:
>> Book.find_by_title 'one'
This method is missing: find_by_title
But now instead of Book “one” I just get the debug message from method_missing. The correct solution here is to pass along the method_missing call to the super class, like this:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name == :ascend_by_title_nulls_last
puts "This method is missing: #{name}"
else
super
end
end
end
end
Let’s try find_by_title again in the console:
>> Book.find_by_title 'one'
Book Load (0.5ms)
SELECT * FROM "books" WHERE ("books"."title" = 'one') LIMIT 1
=> #<Book id: 1, title: "one", created_at: "2010-06-11 18:39:26", etc...
>> Book.ascend_by_title_nulls_last
This method is missing: ascend_by_title_nulls_last
=> nil
Sigh of relief – it works again! Looking at the if statement above, you can see that I check if the missing method is called “ascend_by_title_nulls_last,” in which case I write the debug message; if any other missing method is called I pass the call along to the super class. In this case, the super class is actually the SearchLogic module; it uses method_missing with super in exactly the same way that I do here. If the missing method is not recognized by SearchLogic, super is called again and finally ActiveRecord receives the method_missing call, which eventually evaluates find_by_title.
How does SearchLogic work?
SearchLogic uses method_missing as follows, the first time a missing method is called on an ActiveRecord model:
- Check if the unknown method matches a certain pattern, e.g. “ascend_by_XYZ.”
- Call “named_scope” if so, to create the corresponding named scope.
- Run the new named scope, to return the desired query results.
If the same missing method is called again, it will no longer be missing since the corresponding named scope will now exist. ActiveRecord caches a list of scopes that are created by calls to named_scope for each model class.
Ok – let’s try this idea on my Book model:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name == :ascend_by_title_nulls_last
named_scope :ascend_by_title_nulls_last,
{ :order => 'title IS NULL, title' }
ascend_by_title_nulls_last
else
super
end
end
end
end
In the console again:
>> Book.ascend_by_title_nulls_last.collect { |book| book.title }
Book Load (1.9ms) SELECT * FROM "books" ORDER BY title IS NULL, title
=> ["one", "three", "two", nil, nil]
It works! The code above implements SearchLogic’s algorithm: if someone tries to use a named scope called “ascend_by_title_nulls_last” then actually create the scope at that moment with the proper sorting behavior.
Adding custom sorting to SearchLogic
Now I’m ready to generalize this for any model and attribute. First, I’ll look for any missing method name that matches a certain regex pattern (“ascend_by_XYZ_nulls_last”):
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name.to_s =~ /^ascend_by_(\w+)_nulls_last$/
named_scope :ascend_by_title_nulls_last,
{ :order => 'title IS NULL, title' }
ascend_by_title_nulls_last
else
super
end
end
end
end
The line highlighted above first converts the method name from a symbol to a string, and then matches it against the “nulls_last” syntax I’m looking for. Next, I’m still hard coding “title” in the named_scope call; let’s replace that with the proper value, and also use the name passed into method_missing instead of the hard coded symbol for the scope name:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name.to_s =~ /^ascend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1}" }
ascend_by_title_nulls_last
else
super
end
end
end
end
“$1” returns the string that matched the first expression contained in parentheses in the regex pattern above, “(\w+)” in this case. This will be the name of the attribute between ascend_by… and …nulls_last, taken from the missing method’s name. Now the proper named scope is created using this attribute name in the SQL fragment. So for example, if I call “Book.ascend_by_author_name_nulls_last” a named scope called “ascend_by_author_name_nulls_last” will be created, using :order => “author_name IS NULL, author_name.”
One last hard coded value to remove: the call to “ascend_by_title_nulls_last” still refers to title directly. To fix this, I just need to use “send(name)” – this calls the method whose name is in the “name” string, which is the named scope we just created. Here’s how that looks:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name.to_s =~ /^ascend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1}" }
send(name)
else
super
end
end
end
end
Now I can add in the case for the descending sort as well:
class Book < ActiveRecord::Base
class << self
def method_missing(name, *args, &block)
if name.to_s =~ /^ascend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1}" }
send(name)
elsif name.to_s =~ /^descend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1} DESC" }
send(name)
else
super
end
end
end
end
The last thing I’ll do today is generalize this for any model in my application by moving the method_missing code into a module that I’ll call “SearchLogicExtensions,” and then extending ActiveRecord::Base with that:
module SearchLogicExtensions
def method_missing(name, *args, &block)
if name.to_s =~ /^ascend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1}" }
send(name)
elsif name.to_s =~ /^descend_by_(\w+)_nulls_last$/
named_scope name, { :order => "#{$1} IS NULL, #{$1} DESC" }
send(name)
else
super
end
end
end
ActiveRecord::Base.extend(SearchLogicExtensions)
Note that I need to use ActiveRecord::Base.extend and not ActiveRecord::Base.include here, since my method_missing code calls “super” if the missing method does not match the pattern. “Extend” means that the methods of ActiveRecord::Base, including method_missing, will be overridden by the methods of the SearchLogicExtensions module, but will still be present and available via a call to “super.” Another important detail here is that I removed the “class << self” syntax. Since this is a module and not a class like Book was, I just define method_missing directly. My method_missing will be added as a class method to Book and all of my other models by the last line, when we extend ActiveRecord::Base. In my application I put this code into a file called “config/initializers/search_logic_extensions.rb,” which caused it to be loaded during the Rails initialization process. I could have also packaged the code up as a separate plugin.
That’s it for today; next time I’ll continue this discussion of metaprogramming with SearchLogic by showing how to sort with NULL values in an associated database table, using a LEFT OUTER JOIN query.
Tags:rails
Update June 2010: I just heard from Jon Yurek in the comments below that he has, in fact, finished up the Rails 3 changes for Paperclip. This means that you can now just install Paperclip as usual in a Rails 3 app as a plugin:
$ rails plugin install git://github.com/thoughtbot/paperclip.git
... or as a gem by adding it to your Gemfile if you’ve already installed it with “gem install paperclip:”
source 'http://rubygems.org'
gem 'rails', '3.0.0.beta3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'paperclip'
I’ll leave my original article here as a reference – it was a fun learning experience trying out Paperclip with Rails 3, and the same ideas around Bundler, generators, etc., might still be helpful while using other gems or plugins with Rails 3.
To get Paperclip to work in a Rails 3 application, use this in your Gemfile:
gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git',
:branch => 'rails3'
… and this in application.rb:
module YourPaperclipApp
class Application < Rails::Application
Paperclip::Railtie.insert
etc...
end
end
Right now it looks like Thoughtbot is finishing Rails 3 related changes in a “rails3” branch in their Paperclip github repository. The best thing to do if you have a Paperclip app you want to migrate to Rails 3 is simply to wait a bit longer for them to finish that work, test it and merge it back into the master branch.
The rest of this article is really not about Paperclip at all, but about Rails 3. Here’s what I learned about Rails 3 while troubleshooting Paperclip:
- The command line has changed
- Plugin generators have moved
- Rails 2.x generators don’t work at all
- You use Bundler and a “Gemfile” to declare gems
- You can install a gem from a specific git repository branch
- Rails 3 frameworks are now based on Rails::Railtie
- Bundler does not call rails/init.rb in each gem
Let’s go ahead and troubleshoot Paperclip together in a new Rails 3 app. First I’ll begin by verifying the versions of Ruby and Rails I’m using now:
$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin9.8.0]
$ rails -v
Rails 3.0.0.beta3
And next I’ll create a sample app to use with Paperclip:
$ rails paperclip-sample-app
create
create README
create .gitignore
create Rakefile
create config.ru
create Gemfile
create app
create app/controllers/application_controller.rb
etc…
Now we’re ready to install Paperclip into my new app. But what should I do exactly? Should I use Paperclip as a plugin or a gem? I wasn’t sure what to do, so I simply tried both.
Fact 1: the command line has changed
First let’s install it as a plugin, since that’s the most straightforward. In Rails 3 the plugin install command has changed a bit vs. Rails 2.x:
$ cd paperclip-sample-app
$ rails plugin install git://github.com/thoughtbot/paperclip.git
Initialized empty Git repository in .../vendor/plugins/paperclip/.git/
remote: Counting objects: 77, done.
remote: Compressing objects: 100% (68/68), done.
remote: Total 77 (delta 12), reused 20 (delta 0)
Unpacking objects: 100% (77/77), done.
From git://github.com/thoughtbot/paperclip
* branch HEAD -> FETCH_HEAD
Next let’s use scaffolding to create a “User” model with a couple of attributes:
$ rails generate scaffold user name:string email:string
invoke active_record
create db/migrate/20100521034815_create_users.rb
create app/models/user.rb
invoke test_unit
create test/unit/user_test.rb
create test/fixtures/users.yml
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
etc…
$ rake db:migrate
Fact 2: plugin generators have moved
The next step is to create a second migration for the additional database columns required by Paperclip. To make this easy, Paperclip provides a “paperclip” generator; let’s try that and specify that we want an “avatar” file attachment saved on the user model:
$ rails generate paperclip user avatar
Could not find generator paperclip.
Uhh… not what I expected. It looks like something has changed about Rails 3 generators that has broken the Paperclip generator. For now, let’s take a look at the Paperclip code to see if we can find the generator:
$ find vendor/plugins/paperclip -name *generator*
vendor/plugins/paperclip/generators
vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb
There it is… After some research, I found out that for Rails 3, plugin/gem generators need to be located inside a folder called “BASE_DIR/lib/generators” – we can see here that the Paperclip generator needs to be moved in order to comply with this new standard.
Fact 3: Rails 2.x generators don’t work at all
So let’s try just moving it and see what happens:
mv vendor/plugins/paperclip/generators vendor/plugins/paperclip/lib/.
$ rails generate paperclip user avatar
[WARNING] Could not load generator "generators/paperclip/paperclip_generator"
because it's a Rails 2.x generator, which is not supported anymore.
Error: uninitialized constant Rails::Generator.
Things are looking worse and worse! It turns out that the generators architecture for Rails 3 has been completely rewritten, and that generators written for Rails 2.x will simply not work at all in Rails 3. What to do now? Of course, I could simply hand code the migration for adding the avatar columns to the users table, and continue to work on my sample application. Instead, I decided to give up on the plugin entirely and to try using Paperclip as a gem.
Fact 4: You use Bundler and a “Gemfile” to declare gems
Let’s take a look at how gems are installed for a Rails 3 app. Rails 3 uses a new file called the “Gemfile,” which specifies which gems should be included in your application. This file is read and used by Bundler, which manages gems and their dependencies. We can specify that our application uses the Paperclip gem by adding a single line to the Gemfile like this:
source 'http://rubygems.org'
gem 'rails', '3.0.0.beta3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'paperclip'
etc…
This simply tells Bundler to install Paperclip from your default gem source: probably rubygems.org. Now I’ll delete the plugin I installed earlier and install the gem, using the “bundle install” command to install all of the gems in my Gemfile:
$ rm -rf vendor/plugins/paperclip/
$ bundle install
Fetching source index from http://rubygems.org/
Using rake (0.8.7) from system gems
Using abstract (1.0.0) from bundler gems
etc…
Installing paperclip (2.3.1.1) from rubygems repository at http://rubygems.org/
etc…
Bundler indicated that it found the official version of Paperclip on rubygems.org, downloaded and installed it. It also told us we have version 2.3.1.1. Let’s use the bundle show command and take a look at where Paperclip was installed to:
$ bundle show paperclip
/Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1
Bundler simply installed the gem in the standard location where all my other gems are located for my RVM version of Ruby 1.8.7, just as if I had run a gem install command manually. Now let’s try that generate command again and see if it works any better:
$ rails generate paperclip user avatar
DEPRECATION WARNING: RAILS_ROOT is deprecated! Use Rails.root instead. (called from expand_path at /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39)
/Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39:in `expand_path': can't convert # into String (TypeError)
from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39
from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:46:in `require'
from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:46:in `require'
from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:41:in `each'
Still broken! It looks like I’m just not running code that was intended to be used with Rails 3.
Fact 5: You can install a gem from a specific git repository branch
After more investigation, I noticed that there had been a lot of recent changes to the Paperclip github repository. At the time I wrote this, Thoughtbot was actively developing on a branch called “rails3.” I decided the best thing to do would be to try the code from the rails3 branch, hoping it might work better for me. Bundler makes this easy, since you can just specify git as a source for downloading a gem using a “git” option, as well as optionally a specific branch using a “branch” option, like this:
source 'http://rubygems.org'
gem 'rails', '3.0.0.beta3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git',
:branch => 'rails3'
etc …
After saving this change to Gemfile, let’s re-run the bundle install command:
$ bundle install
Updating git://github.com/thoughtbot/paperclip.git
Fetching source index from http://rubygems.org/
Updating git://github.com/thoughtbot/paperclip.git
Using rake (0.8.7) from system gems
Using abstract (1.0.0) from bundler gems
…etc…
Using paperclip (2.3.2.beta1)
from git://github.com/thoughtbot/paperclip.git (at rails3)
Hmm… interesting. Bundler is showing that it’s downloaded Paperclip from the github repository, and that it got the code at the head of the rails3 branch. Another interesting detail here is that I apparently now have version “2.3.2 beta1” of Paperclip. This is a good sign, since I have a more recent version than 2.3.1.1 (the rubygems.org version) and also it seems that Thoughtbot is actively working on it since it’s labeled “beta1.”
If we run bundle show again, we can see that Bundler has saved a special copy of Paperclip downloaded from github, along with the git commit id and branch of the version I have:
$ bundle show paperclip
/Users/pat/.rvm/gems/ruby-1.8.7-p249/bundler/gems/paperclip-61f74de14812cabc026967a2b2c3ca8cbd2eed69-rails3
Now let’s try that generator once more:
$ rails generate paperclip user avatar
create db/migrate/20100521003113_add_attachment_avatar_to_user.rb
Yes! It’s working now!
Fact 6: Rails 3 frameworks are now based on Rails::Railtie
Let’s continue to put together my sample application by running the migration:
… and by editing my User model to call “has_attached_file:”
class User < ActiveRecord::Base
has_attached_file :avatar
end
Before I start editing my views and adding the code to upload and display the avatar attachment, let’s start the server and see if Paperclip is working. Opening the users index page I get…

… more trouble! I’m definitely having a bad day… what now? Well it seems that Paperclip is just not being loaded at all, or is being initialized improperly for some reason. At this point I started to poke around the Paperclip source code a bit, and found that the code that includes the Paperclip module into ActiveRecord::Base was moved and is no longer being called. Since Paperclip is not included in my User/ActiveRecord class I get the error has_attached_file not defined, since that’s defined by Paperclip.
I found the include code in a file called “lib/paperclip/railtie.rb:”
require 'paperclip'
module Paperclip
if defined? Rails::Railtie
require 'rails'
class Railtie < Rails::Railtie
config.after_initialize do
Paperclip::Railtie.insert
end
end
end
class Railtie
def self.insert
ActiveRecord::Base.send(:include, Paperclip)
File.send(:include, Paperclip::Upfile)
end
end
end
I’m not quite sure what Thoughtbot’s plans are for Paperclip, but if you take some time to read through Yehuda Katz’s write up Rails and Merb Merge: Rails Core (Part 4 of 6), you’ll learn about how Rails frameworks like ActiveRecord and ActiveController have been recast as instances of this “Rails::Railtie” class. Possibly Paperclip will become one of these. Rails 3 has a new API for declaring how Railties are loaded and initialized, but it looks like this version of Paperclip and this version of Rails aren’t quite working correctly now.
Fact 7: Bundler does not call rails/init.rb in each gem
For now, the problem I’m having in my sample application is that the Paperclip::Railtie.insert method is not being called – the two lines I highlighted above need to be executed in order to enable “has_attached_file” to be present as a class method for ActiveRecord models. To make things more interesting, Thoughtbot did include a call to insert inside rails/init.rb, like this:
require 'paperclip/railtie'
Paperclip::Railtie.insert
… but for Rails 3, it turns out that Bundler no longer calls rails/init.rb.
Moving this line instead to config/application.rb will solve the problem:
module PaperclipSampleApp
class Application < Rails::Application
Paperclip::Railtie.insert
etc…
end
end
Alternatively, you could just create a file called “config/initializers/paperclip.rb” and put the call to insert there.
Now reloading the users index page we finally get Paperclip to work:

Instead of proceeding with my sample app now, I’m going to wait a few weeks while Thoughtbot finishes off the Rails 3 changes for Paperclip.
I don’t think troubleshooting these problems was a waste of time at all; in fact, it was a good excuse to get my hands dirty with Rails 3 and Bundler. Once Thoughtbot has finished their changes in the rails3 branch and merged them into the master I’ll update my tutorial from last year, and also update my Paperclip fork to support database BLOB storage for Rails 3.
Tags:rails
Today I want to demonstrate a JQuery slideshow tool that my friend Daniel Higginbotham wrote: Electric Slide. It’s tremendously simple to use, while still providing ways to customize and adapt its behavior as you need to.
Suppose you have a long series of slides containing text and/or images that you want to display as a slideshow on a web page; let’s use these Mickey images as an example:

You might represent these slides using a Rails ActiveRecord model called “Slide.” If you had one image per slide you could attach it to the slide model with Paperclip:
class Slide < ActiveRecord::Base
has_attached_file :image
end
|
The simplest way to display all of these would be to draw a single vertical column on a web page and let the user scroll down to view all of the slides:
<% @slides.each do |slide| %>
<%= image_tag slide.image.url %>
<br/>
<% end %>
However, scrolling can be annoying especially if there are many images or a lot of text. Also, this isn’t a slideshow. I don’t see each image in the same location as the previous one. It’s harder to notice changes between the slides and also harder for me to surprise the user with something funny or unexpected in the following slide since they are all immediately visible. |
 |
Another simple solution would be to display each slide on a separate page and provide next/previous links, using code similar to this:
 |
<%= image_tag @slide.image.url %>
<br>
<%= link_to 'Prev', @slide.higher_item unless @slide.first? %>
<%= link_to 'Next', @slide.lower_item unless @slide.last? %>
|
But this is also annoying since each click takes the user to a separate page, changing the URL and also adding to the browser’s history list. Since all of these images are part of a single presentation I’d like to display them on one page. Additionally I have to write all of the navigation code: I might use acts_as_list in my model class like in the ERB snippet above, and then write the HTML to display the next/prev links in the proper location, to hide them when necessary, to give them the proper styling, etc. In other words, it’s a fair amount of work.
Electric Slide
Electric Slide to the rescue! If you install Daniel’s Electric Slide JQuery code file in your app, all you have to do is use a <div class=“slide”> tag around each image or whatever slide content you have, call a JQuery function when your page is loaded and Electric Slide will take care of the rest:
<script type="text/javascript" charset="utf-8">
$(function(){
$("#slides").electricSlide();
})
</script>
<div id="slides">
<% @slides.each do |slide| %>
<div class="slide">
<%= image_tag slide.image.url %>
</div>
<% end %>
</div>
|
|
 |
With just a little bit of CSS love, the slideshow can look like this (click through the screen shot to see a working example):
body {
background:#EEEEDD;
}
.slide {
display:none;
}
#slides .slide-header {
display: none
}
#slides .slide-footer {
margin:0;
width: 200px;
}
.slide-footer .previous {
width:75px;
float:left;
}
.slide-footer .next {
width:75px;
float:right;
text-align:right;
}
|
|
 |
Now all the slides are displayed in a working slideshow inside my single web page! When I click on the previous or next links, I can watch Mickey move around without leaving the page. Let’s take a look at how my ERB and CSS code works and what Electric Slide is doing:
- First I’ve identified each of my slide’s content using <div class=“slide”>. I just included that in my iteration over the slide array. For me, each of these <div> tags contains a Mickey image, but they could contain any HTML content you would like.
- Note I set “display: none” for the “.slide” style so the slides are initially hidden. This is required by Electric Slide, or else all of the slides will appear initially visible.
- Next I added an on-load handler using JQuery syntax: $(function(){ …. }). JQuery will call this code when the page finishes loading.
- In the on-load handler, I call the electricSlide function on my <div id=“slides”> parent object. This initializes Electric Slide. There are many options I could have passed in here, but today I’m just using all of the default values. Check out Daniel’s github repo for documentation and examples for more info on what to pass in here.
- Electric Slide counts up the slides, and displays “previous” and “next” links for each one as necessary. I don’t need to worry about writing code to display the links or handle clicks on them: the navigation just works.
- Electric Slide also creates <div> tags automatically to hold the previous/next links, these are called <div class=“slide-header”> and <div class=“slide-footer”>. I’ve styled these using CSS to hide the header and to align the links with the edge of the image in the footer.
Detailed step-by-step example
Let’s create a new Rails app from scratch, copy the mickey images into it, install Electric Slide and then display them all as a slideshow… all in 10 minutes or less!
$ rails mickey-slides
$ cd mickey-slides
Go ahead and download the Mickey images from my site; or feel free to use any images you have instead:
$ curl -O http://patshaughnessy.net/assets/2010/4/28/mickey-images.tar.gz
$ tar zxvf mickey-images.tar.gz
images/
images/mickey1.jpg
images/mickey2.jpg
images/mickey3.jpg
images/mickey4.jpg
images/mickey5.jpg
images/mickey6.jpg
Now let’s use View Mapper to create my slide model with an “image” attachment:
$ sudo gem install view_mapper
$ ./script/generate scaffold_for_view slide --view paperclip:image
error The Paperclip plugin does not appear to be installed.
Oh yea… I forgot to install Paperclip; let’s do that now also and then repeat the View Mapper command:
$ ./script/plugin install git://github.com/thoughtbot/paperclip.git
$ ./script/generate scaffold_for_view slide --view paperclip:image
$ rake db:migrate
Next let’s load the images into our database using Paperclip in the Rails console:
$ ./script/console
Loading development environment (Rails 2.3.5)
>> Dir.glob('images/*.jpg').each do |filename|
?> Slide.create :image => File.new(filename)
>> end
=> ["images/mickey1.jpg", "images/mickey2.jpg", "images/mickey3.jpg",
"images/mickey4.jpg", "images/mickey5.jpg", "images/mickey6.jpg"]
Now we just need to install Electric Slide – let’s just get Daniel’s entire github repo including the examples and documentation:
$ cd ..
$ git clone git://github.com/flyingmachine/electric-slide.git
$ cd mickey-slides
And now let’s replace our default Rails prototype javascript files with JQuery and the Electric Slide code from Daniel’s repository:
$ rm -rf public/javascripts
$ cp -r ../electric-slide/javascripts public/javascripts
Now edit app/views/layouts/slides.html.erb and include Electric Slide, JQuery and a new style.css file by replacing the existing stylesheet_link_tag ‘scaffold’ line with the lines highlighted below:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Slides: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold', 'styles' %>
<%= javascript_include_tag 'jquery', 'jquery.sizes', 'jquery.electric-slide', 'jquery-ui' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= yield %>
</body>
</html>
Now we can add the CSS code snippet from above into our app – copy this into a new file called public/stylesheets/styles.css, which we just included above in the layout:
body {
background:#EEEEDD;
}
.slide {
display:none;
}
#slides .slide-header {
display: none
}
#slides .slide-footer {
margin:0;
width: 200px;
}
.slide-footer .previous {
width:75px;
float:left;
}
.slide-footer .next {
width:75px;
float:right;
text-align:right;
}
And finally replace the scaffolding slide index view, app/views/slides/index.html.erb, with the code from above:
<script type="text/javascript" charset="utf-8">
$(function(){
$("#slides").electricSlide();
})
</script>
<div id="slides">
<% @slides.each do |slide| %>
<div class="slide">
<%= image_tag slide.image.url %>
</div>
<% end %>
</div>
Now start up your server at look for the Mickey slide show at http://localhost:3000/slides!
Tags:rails
I just updated View Mapper to support scaffolding for models in a has_many, :through relationship. It generates a complex form that is a combination of the “belongs_to” scaffolding from part 1 of this series and the nested attributes scaffolding I wrote about in November:

Based on the programmer/assignment/project example from the ActiveRecord documentation page, this form will create a new programmer record and allow the user to add one or more assignments, each of which also has a name text field. For each new assignment the user can also select an existing project record. Here’s the Programmer model with the has_many :through association:
class Programmer < ActiveRecord::Base
has_many :projects, :through => :assignments
has_many :assignments
accepts_nested_attributes_for :assignments,
:allow_destroy => true,
:reject_if => proc { |attrs|
attrs['name'].blank? &&
attrs['project_id'].blank?
}
end
This implements a many-many relationship between programmers and projects; the assignments model is used to map the projects with the programmers. I’ve also specified that the programmer model accepts_nested_attributes_for assignments… more on that below.
You can now use the “view_for” generator from View Mapper to generate the form above for your models using a new view called “has_many_existing:”
$ sudo gem install view_mapper
$ script/generate view_for programmer --view has_many_existing:projects
Assumptions and requirements
Nested Attributes: the form above works by using ActiveRecord’s nested attributes feature to save multiple assignments for a single programmer. Therefore, you need to be sure you call accepts_nested_attributes_for in your target model; if you forget to do this, you’ll get an error from View Mapper:
$ script/generate view_for programmer --view has_many_existing:projects
warning Model Programmer does not accept nested attributes
for model Assignment.
To fix this problem you can use the code I showed above:
class Programmer < ActiveRecord::Base
has_many :projects, :through => :assignments
has_many :assignments
accepts_nested_attributes_for :assignments,
:allow_destroy => true,
:reject_if => proc { |attrs|
attrs['name'].blank? &&
attrs['project_id'].blank?
}
end
The options I’ve specified here tell ActiveRecord it is allowed to delete assignment records (when the user clicks “remove” in the form) and to avoid creating empty assignment records if all of their attributes are blank (if the user clicked “Add Assignment” an extra time).
Or if you prefer you can generate the entire target model including the nested attributes call using the scaffold_for_view generator like this – specify the new model’s columns using the same syntax as the standard Rails scaffold generator:
$ script/generate scaffold_for_view programmer
first_name:string last_name:string
--view has_many_existing:projects
It’s easy to overlook one very elegant detail here about ActiveRecord’s nested attribtues feature: note that “project_id” is one of the nested attributes, generated by each of the project select list boxes. (They are implemented with collection_select; see part 1 of this series). Now when the new programmer form is submitted all of the associations for each assignment – and for the new programmer – are setup. In other words, after you save the new programmer record this way you can immediately access the associated projects through assignments: “programmer.projects” – very cool! And it's all seamless: I don't have to write any code in my controller to associate the projects or assignments with the new programmer.
Correct associations among your models: if you forget to put the proper associations in your three models the has_many :through behavior will not work. You need to have six associations setup among your three models like this:
class Programmer < ActiveRecord::Base
has_many :projects, :through => :assignments
has_many :assignments
end
class Project < ActiveRecord::Base
has_many :programmers, :through => :assignments
has_many :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :project
belongs_to :programmer
end
View Mapper will help you out by displaying an error message if you’re missing one of these:
$ script/generate scaffold_for_view programmer name:string
--view has_many_existing:projects
warning Model Project does not contain a has_many association for Assignment.
…or if you’re missing one of the corresponding foreign key columns in the “through” model:
$ script/generate scaffold_for_view programmer name:string
--view has_many_existing:projects
warning Model Assignment does not contain a foreign key for Programmer.
Has many existing model identified by name attribute: In the form above, the Project records were identified in the select list boxes using their “name” attribute. Therefore, you need to insure that your existing model has a name column or method; if it does not View Mapper will display an error message like this:
$ script/generate scaffold_for_view programmer name:string
--view has_many_existing:projects
warning Model Project does not have a name attribute.
To fix this problem, add a “name” method to your existing model, or else you can specify that View Mapper use a different attribute (e.g. “code”) instead with this syntax:
$ script/generate scaffold_for_view programmer name:string
--view has_many_existing:projects[code]
Associated model name method in through model: The last requirement is that the through model, Assignment in this example, have a method (“project_name”) to display the name of its associated existing model. View Mapper requires this to avoid putting this code into the view:
class Assignment < ActiveRecord::Base
belongs_to :project
belongs_to :programmer
def project_name
project.name if project
end
end
If you forget this method, View Mapper will remind you with this error message:
$ script/generate scaffold_for_view programmer name:string
--view has_many_existing:projects[code]
warning Model Assignment does not have a method project_code.
Detailed Example
Here’s a step by step example of how to create a Rails application from scratch that contains the has_many :through scaffolding:
Here’s our model to hold the existing data:
$ cd hmt_example
$ script/generate model project code:string
And the through model to associate projects with programmers; note I’ve included integer attributes as the foreign keys for both the existing model and the new model:
$ script/generate model assignment name:string
project_id:integer programmer_id:integer
$ rake db:migrate
Next edit the new models and enter the required associations along with the project_code method:
class Project < ActiveRecord::Base
has_many :programmers, :through => :assignments
has_many :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :programmer
belongs_to :project
def project_code
project.code if project
end
end
Now we’re ready to create the programmer has_many :through scaffolding; note I’ve specified “code” as the attribute to use to identify each project:
$ sudo gem install view_mapper
$ script/generate scaffold_for_view programmer
first_name:string last_name:string
--view has_many_existing:projects[code]
$ rake db:migrate
Note this won’t work yet for a has_and_belongs_to_many association; dealing with that is next on my View Mapper to do list.
Tags:rails
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.
Tags:rails
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) =======================================
Tags:rails
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 < 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...
Tags:rails
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!
Tags:rails
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:
- 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.
- 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.
- 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.
Tags:rails
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.
Tags:rails
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!
Tags:rails
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.
Tags:rails
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:
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 eight 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.
- has_many_existing: Generates scaffolding for a complex form to edit two models that have a has_many, :through association with a third model. Use this if you have a many-many relationship with existing data.
- (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.
Tags:rails