Rails View Tips

Posted by jeff

Here is a list of quick tips that I find helpful using Ruby and Rails, inspired by the Railscasts Contest

1. Access view helpers from the console

Sometimes I need a quick reminder of the syntax of view helpers – like is it truncate(length, text) or truncate(text, length)? When this happens I just fire up a script/console and use the “helper” method. For example:

1
2
helper.truncate "Big sentence", 5
=> "Bi..."

2. Word Wrap

Speaking of view helpers, you probably know about pluralize, truncate and simple_format, but did you know that rails can word wrap for you? Frequently I find that I have a column of text that just doesn’t look good when some lines are a lot longer than others, and I need to have more fine-grained control over the output. Word wrap comes to the rescue. You can test this in script/console by doing the following:

1
2
3
text = "This is some very very long text that needs to be word wrapped"
helper.word_wrap text, 50
=> "This is some very very long text that needs to be\nword wrapped"

Notice that it inserts a newline, not a br element. If you want to convert that newline to an html br element, you can always use simple_format. What I particularly like about word_wrap is that it only breaks on whitespace characters, so you’ll never end up with a half-word.

3. Partial Counter

Let’s say you need to render a partial for a collection, and that you need to generate a sequential number that can’t be inferred from the database, like “Post #0, Post #1, Post #2” etc. where the numbers will always start with 0 and always be sequential. To start you might try something like this:

1
2
3
@posts.each_with_index do |post, index|
  render :partial => "post", :object => post, :locals => {:index => index}
end

Then from your partial, you would reference it like:

1
Post #<%= index %>

However, if you render a partial from a collection, Rails keeps track of the index for you auto-magically, in a local variable named partial_counter (in this case it would be post_counter). Refactored, it would look like this:

1
render :partial => @posts

And from the partial itself:

1
Post #<%= post_counter %>

Remember that partial_counter is zero-based. For regular partials, called with just an object, it will return 0.

4. Keep selects and model validations in sync with constants

The problem: you have a field in your model that validates_inclusion_of some array of values. You want to provide a dropdown list in your UI that matches these values, but provides a human readable description beyond what “humanize” can provide.

The solution: Constants. Create a constant in your model that has a hash with the names and the descriptions like so:

1
2
3
4
class Page < ActiveRecord::Base
  VISIBILITIES = {"Anyone can see it"=>"public", "Nobody can see it"=>"private"}
  validates_inclusion_of :visibility, :in => VISIBILITIES.values
end

Notice how you can validate the inclusion of the field just in the values. Then in the view just add a select field helper and pass it the constant:

1
select :page, :visibility, Page::VISIBILITIES

This keeps the code simple, readable and DRY.

5. Safely render environment-specific content

I like to use google analytics to keep track of my client’s site visitors. However, I don’t want to track my development machine, or the staging version of the site – just the production site. Rails makes this easy by making the RAILS_ENV environmental variable available. So anywhere in your rails app you can execute code conditionally – like this snippet from a view:

1
2
3
<%- if "production" == RAILS_ENV -%>
some google analytics javascript...
<%- end -%>

If you’re not careful, however, you can actually change the rails environment! Take this snippet of code, for example:

1
2
3
<%- if RAILS_ENV = "development" -%>
output some super-secret code trace...
<%- end -%>

Can you spot what’s wrong? Notice the ”=” instead of the ”==” – when the view execute this code it will actually change the rails_env to development, potentially wreaking all kinds of havoc on your app. In script/console this outputs a warning, and I haven’t tried it on production mode, but just to be safe always put the “production” part before the RAILS_ENV part – even if you forget an ”=” it will just throw an error.

Another great use of this is creating a banner to alert users to what environment they are looking at. Since staging sites often look exactly like production sites, I like to output a banner at the top of my page to say “hey – you’re on the staging site”. In my application.html.erb file I often have the following snippet:

1
2
3
4
5
<%- unless 'production' == RAILS_ENV -%>
  <div style="padding:.5em;font-weight:bold;text-align:center;background:orange;">
    YOU ARE CURRENTLY ON THE <%= RAILS_ENV.upcase %> SITE
  </div>
<%- end -%>

In edge rails this is a little easier with the Rails object – stay tuned for more on that later.

6. Locals with default values in partials

You can pass locals to partials, making partials very flexible. But sometimes you want to be able to optionally pass a local variable to a partial. You can accomplish this a variety of ways, but my favorite is just to use a simple ||= block at the top. For example if you have an optional “author” local you want to pass in, you can just write:

1
2
<%- author ||= nil -%>
Written by <%= author %>

The added benefit of this method is that the partial is self documenting, since you see all of the optional locals you can pass into it right at the top. You can optionally add comments for the required locals as well – depending on how busy you like your partials to be.

Starting ferret at reboot using Capistrano

Posted by jeff

I like using Ferret and acts_as_ferret to add full-text search to my models in Rails in a database-independent way. I’ve had a very difficult time getting this to work smoothly with my deployment process because the ferret server needs to be run in a separate process.

Every time the server reboots, you need to start the ferret server, and everytime you deploy your app you need to restart the ferret server. You can go to the acts_as_ferret site to get more information about deployment strategies, but here’s what’s been working for me for a while now.

First, you need to create a startup script that can be run on boot. I’ve taken mine from the railsmachine tutorial, but you can also use the one from the acts_as_ferret tutorial. Mine looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# located in config/templates/ferret_ctl.erb
#!/bin/bash
#
# This script starts and stops the ferret DRb server
# chkconfig: 2345 89 36
# description: Ferret search engine for ruby apps.
#
# save the current directory
CURDIR=`pwd`
PATH=/usr/local/bin:$PATH

RORPATH="<%= current_path %>"

case "$1" in
  start)
     cd $RORPATH
     echo "Starting ferret DRb server."
     RAILS_ENV=<%= rails_env %> script/ferret_start
     ;;
  stop)
     cd $RORPATH
     echo "Stopping ferret DRb server."
     RAILS_ENV=<%= rails_env %> script/ferret_stop
    ;;
  *)
     echo $"Usage: $0 {start, stop}"
     exit 1
     ;;
esac

cd $CURDIR

Next, add the following capistrano tasks to your deploy.rb file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# =============================================================================
# FERRET
# =============================================================================
set :ferret_script_name, "ferret_#{application}_ctl"
set :ferret_ctl, "/etc/init.d/#{ferret_script_name}"

namespace :ferret do
  desc "Uploads the ferret startup script"
  task :install, :roles => :app, :only => {:primary => true} do 
    require 'erb'
    upload_path = "#{shared_path}/ferret" 
    template = File.read("config/templates/ferret_ctl.erb")
    file = ERB.new(template).result(binding) 
    put file, upload_path, :mode => 0755
    sudo "cp #{upload_path} #{ferret_ctl}"
    sudo "chmod +x #{ferret_ctl}"
    sudo "/sbin/chkconfig #{ferret_script_name} on"
  end 

  desc "Starts the ferret server"
  task :start, :roles => :app, :only => {:primary => true} do
    sudo "#{ferret_ctl} start"
  end

  desc "Stops the ferret server"
  task :stop, :roles => :app, :only => {:primary => true} do
    sudo "#{ferret_ctl} stop"
  end

  desc "Restarts the ferret server"
  task :restart, :roles => :app, :only => {:primary => true} do
    ferret.stop
    ferret.start
  end
  
  desc "Deletes the ferret startup script"
  task :uninstall, :roles => :app, :only => {:primary => true} do 
    sudo "/sbin/chkconfig #{ferret_script_name} off"
    sudo "rm -rf #{ferret_ctl}"
  end 
  
end
after "deploy:symlink", "ferret:restart"

Now you are ready to deploy – just execute the following commands to get set up with your script:

cap ferret:install
cap deploy

The installation will:

  • Upload your startup script to /etc/init.d and set the correct permissions
  • When you deploy it will restart the drb servers before restarting mongrel

Thanks to the folks at Railsmachine for providing detailed instructions about chkconfig, and the acts_as_ferret folks for providing the drb server.

Adding patches to Rails now that it's on git

Posted by jeff

It took me a while to figure out how to try to contribute to Rails now that it’s on Lighthouse / Github. Here’s what I do now, and it seems to work:

First, create a clone of the main git repository (not your fork of it – there’s really no reason to fork unless you want others to pull your changes before core accepts them):

Setup your development directories

mkdir rails
cd rails
mkdir patches
git clone git://github.com/rails/rails.git source

Now you have:

rails
|--patches
`--source

Set up your dev branch

Then create a new branch where you’ll store just the changes you make for this patch:

git checkout -b your_patch_name master

Create the patch

Make your test-driven changes and when you are ready to create the patch run:

git diff -p master > ../patches/your_patch_name.patch

Setup a throwaway test branch

If you want to test your change

  • create a new branch and apply the patch (this is useful if other commits have happened since you first created your patch)
  • run the tests and make sure that everything passes
  • clear your changes and delete the test branch
git checkout master
git pull
git checkout -b your_patch_name_test master
git apply ../patches/your_patch_name.patch
git stash ...
git checkout master
git stash clear
git branch -D your_patch_name

Using git stash allows you to move back to the master branch without taking your changes with you – leaving you with a clean master branch. To learn more about git stash syntax, see the git documentation

Continue development

When it’s time to update the code you put in the patch, you can just rebase from the master branch:

git co your_patch_name
git rebase master

Then you can fix whatever changes you need to fix and recreate your patch (or create a new one if your old changes were accepted).

Share you patch

The next step is to go to http://rails.lighthouseapp.com/ and create a ticket. Make sure that you

  • tag it with “patch” as well as whatever else it applies to
  • don’t forget to upload the patch itself.

When you upload, it appear in the middle of the right-hand column as a blue link – it’s hard to find, so look carefully. Then get as many people as possible to grab your changes and test them and add +1’s where necessary.

Summary

When all is said and done you are left with a directory full of patches you can apply, a clean master working copy and individual branches for all of your patches that you can maintain over time. While this was possible with subversion, it’s way cleaner with git. Contributing to rails is easier than ever!

References

New Rails Core Feature Proposal: Super Sexy Migrations

Posted by jeff

If you are running Rails Edge from github, you can now get Super Sexy Migrations, like this:

1
2
3
4
5
6
7
8
    change_table :videos do |t|
      t.add_timestamps
      t.add_belongs_to :goat
      t.add_string :name, :email, :limit => 20
      t.remove_column :name, :email # => that's right - remove finally takes an array!
      t.rename
      t.string :some_string # => executes against the renamed table name!
    end

Check out the github wiki for instructions. You can also freeze your app to this fork by executing:

1
git clone git://github.com/zilkey/rails.git vendor/rails

ActsAsStateMachine gets hippified

Posted by jeff

I was very pleased to learn earlier tonight that acts_as_state_machine has become infinitely hipper.

  • It’s on github, making patches oh so easy to push
  • It works with any class, not just active record
  • The “state” column is renamed to aasm_state so that you can have a city, state and zip in your active record model (man that was a pain before)
  • It’s a gem (in both senses of the word)
  • It’s tested with specs, and there’s decent coverage
  • The api is basically the same, making it easy to upgrade

It still has the same issues that the old subversion plugin had, so I incorporated my acts_as_state_machine_hacks plugin into my own fork of aasm on github, which you can check out at http://github.com/zilkey/aasm/wikis

I also published some rdocs for my fork at http://aasm.zilkey.com/

Thanks to Scott Barron for updating me on the latest changes, and for a killer gem.

To install my fork of the app:

1
2
3
4
5
git clone git://github.com/zilkey/aasm.git aasm
cd aasm
rake gem
sudo uninstall aasm # => if you already have a version installed
sudo gem install pkg/aasm-3.0.0.gem

Enjoy!

New Plugin: acts_as_state_machine_hacks

Posted by jeff

UPDATE: while this plugin will work for those of you who don’t want to upgrade, there is is a much hipper version of acts_as_state_machine called aasm (that link goes to my fork of it, with the changes listed below incorporated).

Inspired by the evil twin plugin architecture I just made a few small, test-driven tweaks to one of my favorite plugins: acts_as_state_machine

The biggest thing that bugged me about aasm was when you call a method like object.event! it writes to the database, instead of updating the current state, which makes it hard to set a state before validation.

Installation

script/plugin install http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/
script/plugin install git://github.com/zilkey/acts_as_state_machine_hacks.git

What this plugin does

This plugin hack adds new methods without the exclamation point – it just updates the attribute. So if you have:

1
2
3
4
5
6
7
8
9
  class Conversation < ActiveRecord::Base
    acts_as_state_machine :initial => :open
    state :opened
    state :closed

    event :open do
      transitions :to => :opened,   :from => :closed
    end
  end

ActsAsStateMachine would give you:

1
  conversation.open!

That uses update_attribute under the hood. This plugin adds:

1
  conversation.open

Which just sets state to opened, without touching the database. Suitable for calling before you save, like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  def create
    @conversation = Conversation.new(params[:conversation])
    @conversation.open if params[:conversation][:publish] == "open"
    if @conversation.save
      ...
    else
      ...
    end
  end

  def update
    @conversation = Conversation.find(params[:id])
    @conversation.open if params[:conversation][:publish] == "open"
    if @conversation.update_attributes(params[:conversation]) # => if this doesn't validate, the state column will not have been written
      ...
    else
      ...
    end
  end

I wrote this primarily because I often change state based on fields in the model – like if you check a “published” check box that changes a state from pending to published.

This also allows the possibility of adding a dropdown list to an admin screen where admins can set the status, while maintaining the integrity by still relying on events.

Requirements

You must have the acts_as_state_machine plugin installed. To get the tests to run the acts_as_state_machine directory must be named “acts_as_state_machine”.

Warning

This copies methods directly from acts_as_state_machine. If acts_as_state_machine is updated, this plugin hack will likely break.

For developers

If you want to add your own hacks, you can fork this project on http://github.com/zilkey/acts_as_state_machine_hacks/tree/master

It includes working tests, which can be the hardest part of testing a plugin.

Lightrail just got a lot hipper

Posted by jeff

I just released a new version of Lightrail (http://lightrail.zilkey.com/) that is significantly hipper than the earlier version. This version comes with support for taking your existing models and turning them into lightrail’s structure.

Soon to come will be similar options for controllers, helpers, migrations, specs, views, plugins, static assets and whatever else I can shove into a few near-term releases.

If you are one of the people thinking about using lightrail please be aware that the api may change significantly, and the locations of files and the names of generators etc.. are all subject to change.

Make any comments here, or at the github repository (http://github.com/zilkey/multiple_migration_paths/tree/master). See the Lightrail Rdoc (http://lightrail.zilkey.com/) for a more detailed walk through.

New plugin: multiple_migration_paths

Posted by jeff

When Rails 2 came out it made it much easier to manage the view_paths and the plugin locations. Now edge rails has timestamped migrations, which opens up a whole new possibility for multiple migration paths with the same ease as view_paths.

I’ve written a plugin that does just that, and you can get it at http://github.com/zilkey/multiple_migration_paths/tree/master

Installation

Make sure you are edge rails, then:

script/plugin install git://github.com/zilkey/multiple_migration_paths.git

Summary

This allows you to store migrations in multiple directories, and they will all be run together, as if they were in the same directory. No changes have been made to any rails rake tasks, no additional tables are needed to migrate.

Take the following directory structure:

  db
  |-- migrate
  |   |-- 20080427101407_first.rb
  |   |-- 20080427101408_second.rb
  |   `-- 20080427101409_third.rb
  |-- schema.rb
  `-- your_plugin_migrations
      |-- 20080427101308_fourth.rb
      `-- 20080427101408_third.rb

You would add your custom directory by adding the following at the bottom or environment.rb or in an initializer or plugin:

1
2

  ActiveRecord::Migration.migration_paths << File.join(RAILS_ROOT,"db","your_plugin_migrations")

In this case, migrations would be run in the following order:

  • your/plugin_migrations/20080427101308_fourth.rb (it’s the first chronologically, even though it’s named “fourth”)
  • migrate/20080427101407_first.rb
  • migrate/20080427101408_second.rb
  • migrate/20080427101409_third.rb

What happens if you add a migration that comes chronologically before the last one? Rails automatically picks it up for you – so no worries!

Notice how it takes a full path – so your migrations don’t even need to be in your app (think migrations in gems)! Also, because you get to set the paths, you can have migrations from multiple gems/plugins all at the same time, and have any kind of directory structure – like:

  db
  |-- migrate
  |   |-- some_sub_folder
  |   |   `-- 20080427101409_third.rb
  |   `-- 20080427101308_fourth.rb

Important note on ordering

Rails will treat migrations in order (I believe) of the version – not in the order that they were applied to the database.

This means that if you add migration X, then add migration Y with a prior date, then migrate down and migrate up again, it will migrate down X then Y.

Also, if you add X, then a prior Y before you migrate, Y will migrate before X.

This is all standard rails behavior and would hold true for any migrations that were checked into version control at different times. I’m just reiterating it here because there is a slightly higher risk of conflicts with this plugin. Backup your database before running them.

Rake Tasks

Since all kinds of mayhem can break loose with migrations, you may want to see what migrations are in the database. You can do so with:

  rake db:migrations:files
  rake db:migrations:paths

If I have time, I can make this a bit more human readable by presenting the dates, but that’s for later…

Behind the scenes

This change was accomplished in roughly 5 lines of code, not counting whitespace and comments, but because of the way the methods were structured, I had to copy/paste an entire method from rails. As such, it’s not very future-proof, but I’ll submit it to rails core so hopefully this will be obsolete soon!

Extending Complex Forms: generate valid dom ids

Posted by jeff

A few weeks ago I was hired to build a simple event registration form. The requirements were that users needed to be able to:

  • Register multiple attendees for the same organization in one step
  • Specify a meal preference for each attendee, using radio buttons

Since I had recently bought Advanced Rails Recipes and watched the Ryan Bates’ excellent Railscast on complex forms I thought I was all set to go.

I followed Ryan’s instructions exactly and within a few minutes I was up and running with the javascript additions, skinny controllers and fat models and life was good. I fired up my browser, added a few attendee subforms and went to set their meal preference with the radio button when, to my dismay, clicking on any attendee’s meal preference set the meal preference for the first! Not good.

Whenever these things happen I run the page through the w3c html validator to make sure that I’ve got valid HTML. When I ran it through, it gave me several errors – I had lots of repeated ids. Why? If you follow the ARR/railscast example, all of the new subforms will have the same id. In reality, there are 2 things that need to happen:

  • when the page is loaded Rails needs to set an :index on all of the fields
  • when the form is added dynamically via javascript, the javascript needs to insert the correct id

Do your homework

ARR prohibits people from reproducing their code from their tutorials without permission (which I didn’t obtain). So this blog post won’t make much sense unless you read Advanced Rails Recipes and/or watch Railscast on complex forms – both of which I highly recommend. Once you have a working example of that, the rest of this post will make sense.

The setup

For this example, I’ll use the following classes:

1
2
3
4
5
6
7
class Registration < ActiveRecord::Base
  has_many :attendees
end

class Attendee < ActiveRecord::Base
  belongs_to :registration
end

The javascript

So let’s get started. First, we need to create a javascript method that will enable us to increment the index value every time – I decided to go with a generic one so that I could reuse it across my app, and have mutliple subfrms of different types on the same page but still be dry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Subform = Class.create({
  lineIndex: 1,
  parentElement: "",
  initialize: function(rawHTML, lineIndex, parentElement) {
    this.rawHTML        = rawHTML;
    this.lineIndex      = lineIndex;
    this.parentElement  = parentElement;
  },
  parsedHTML: function() {
    return this.rawHTML.replace(/INDEX/g, this.lineIndex++);
  },
  add: function() {
    new Insertion.Bottom($(this.parentElement), this.parsedHTML());
  }
});

Next, we need to set the index when the page loads. I accomplished that like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# /app/views/layouts/application.html.erb

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title><%= yield :title %></title>
    <%= javascript_include_tag :defaults, :cache => 'defaults' %>
    <%= yield :head %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

# /app/views/registrations/_form.html.erb

<%- content_for :head do -%>
  <script type="text/javascript" charset="utf-8">
    //<![CDATA[
    attendeeForm = new Subform('<%= escape_javascript(render(:partial => "attendee", :object => Attendee.new)) %>',<%= @registration.attendees.length %>,'attendees');
    //]]>
  </script>
<%- end -%>

In Ryan’s original recipe he creates a rails helper to create the add link. Now that we’ve written the javscript ourselves we no longer need the helper – our “add” link now look like this:

1
<%= link_to_function 'Add Attendee', "attendeeForm.add()" %>

The partial

We need to add the index to all of the form helpers, which will require some work. In addition, we need to make sure that all of the radio buttons have unique ids and that all of the labels have ids that match up with the radio buttons. So here’s what my partial looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="attendee"><%- 
  index ||= "INDEX" 
  new_or_existing = attendee.new_record? ? 'new' : 'existing'
  id_or_index = attendee.new_record? ? index : attendee.id 
  prefix = "registration[#{new_or_existing}_attendee_attributes][]" 
-%> 

  <% fields_for prefix, attendee do |attendee_form| -%>     
  <p><%= attendee_form.label :email, nil, :index => id_or_index %> <%= attendee_form.text_field :email, :index => id_or_index %> </p>

  <%- MealPreference.find(:all).each do |preference| -%>
    <p>
       <%- radio_id = "registration_#{new_or_existing}_attendee_attributes_#{id_or_index}_meal_preference_#{preference.name}" -%>
       <%= attendee_form.radio_button :meal_preference, preference.name, :id => radio_id, :index => id_or_index %>
       <%= content_tag :label, preference.name, :class => "radio", :for => radio_id %> 
     </p>
  <%- end -%>
  <%= content_tag :p, link_to_function("Remove this attendee", "if(confirm('Are you sure?')){$(this).up('.attendee').remove()}") %> 
  <% end -%>
</div> 

What just happened? I added an index to every field. For existing records, this index will correspond to the attendee.id. For new records, it will be the string “INDEX”. If you recall from the javascript above the parsedHTML function replaced the word INDEX with the correct numeric index.

NOTE: getting correct radio_button ids requires my label_with_index plugin if you are running gem rails, but it looks like radio buttons create correct ids in edge.

The form

This means, however, that you need to pass an index into the partial for existing records. In my form, I’ve got the following:

1
2
3
4
5
6
7
8
9
  <h3>Attendees</h3>
  <div id="attendees"> 
    <%- @registration.attendees.each_with_index do |attendee, index| -%>
      <%= render :partial => "attendee", :object => attendee, :locals => {:index => index} %>
    <%- end -%>
  </div> 
  <p id="add-attendee"> 
  <%= link_to_function 'Add Attendee', "attendeeForm.add()" %>
  </p> 

Note the use of each_with_index so that we can pass the correct index in. “But wait!” you say, “What if the attendee is an existing record? Won’t that mess this up?” Fear not – in the partial we first check whether it’s a new record, and only use the index if it’s a new record.

The model

Making the changes in the model is trivial. The existing_attributes stay exactly the same – but we have to make one small change to the new attributes:

Old:

1
2
3
4
5
6
7
8
# app/models/registration.rb

# add all new attendees
def new_attendee_attributes=(attendee_attributes) 
  attendee_attributes.each do |attributes| 
    attendees.build(attributes) 
  end 
end 

Changes to:

1
2
3
4
5
6
7
8
# app/models/registration.rb

# add all new attendees
def new_attendee_attributes=(attendee_attributes) 
  attendee_attributes.each do |index, attributes| 
    attendees.build(attributes) 
  end 
end 

And voila! Correct dom ids and reusable javascript.

Credits

While I wrote the javascript in that example I reconstructed it from another javascript snippet I had seen (maybe on a railscast, or in the source code for some other rails app like Basecamp or Blinksale). If that looks like your javascript, please contact me at jeff at zilkey . com – I can’t find the original source right now. Other credits include:

References

Introducing label_with_index: a simple rails plugin

Posted by jeff

The problem

A while back Rails introduced the label helper, so you can can easily add labels to forms like so:

1
2
<%= label :user, :name %>
# => <label for="user_2_name">Name</label>

Unfortunately the person who committed that change neglected to add support for the auto index and :index options. For example, take the following code snippet:

1
2
<%= text_field :user, :name, :index => 1%>
# => <input id="user_1_name" name="user[1][name]" size="30" type="text" />

Following the principle of least surprise, you might assume that labels would work the same way, but they don’t:

1
2
<%= label :user, :name, nil, :index => 1%>
# => <label for="user_name" index="1">Name</label>

The fix

I’ve just released a plugin called label_with_index that fixes this. Now label helpers take advantage of the autoindex feature and :index keys as you would expect:

1
2
<%= label :user, :name, nil, :index => 1%>
# => <label for="user_1_name">Name</label>

Fixing radio button ids

Rails has also had a bug for quite some time where it will by default emit radio button ids that will fail XHTML validation. To boot, they even advertise this in the docs. Here’s what’s written on http://api.rubyonrails.com/classes/ActionView/Helpers/FormHelper.html :

1
2
3
4
5
# Let's say that @post.category returns "rails":
radio_button("post", "category", "rails")
radio_button("post", "category", "java")
# => <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
#    <input type="radio" id="post_category" name="post[category]" value="java" />

See how those two radio buttons have the same id? Not only will that fail validation, but it can cause unexpected javascript behavior and will cause very odd label-clicking results (clicking the label will most likely highlight the first radio button, regardless of which label is clicked).

label_with_index also fixes this in two ways:

  • Makes the id unique (or at least, as unique as the option is)
  • Adds a :value option to the label helper that can match the radio button’s value

Before:

1
2
3
4
radio_button("post", "category", "java")
label("post", "category")
 # => <input type="radio" id="post_category" name="post[category]" value="java" />
 # => <label for="post_category">Category</label>

After:

1
2
3
4
radio_button("post", "category", "java")
label("post", "category", nil, :value => "java")
 # => <input type="radio" id="post_category_java" name="post[category]" value="java" />
 # => <label for="post_category_java">Category</label>

So with this plugin the rails label helper, and the radio_button helper have support for creating valid ids out of the box.

Installation

git clone git://github.com/zilkey/label_with_index.git vendor/plugins/label_with_index

See the rdoc at http://label_with_index.zilkey.com/

To Do

  • Create a fork of rails on github and add this to core with tests

References

Notes:

It appears that the radio button id may have been fixed on edge rails – I haven’t tested it out fully and I’m not sure if all of the svn patches are synced up on github, so I’ll wait until things settle a bit and make changes as necessary.

Introducing Lightrail: Productize your rails apps with generators

Posted by jeff

NOTE: this is an almost complete rewrite of the earlier article, written for v0.5.0.

Lightrail is a ruby on rails plugin generator intended to make it easier to share controllers, models, helpers, views and migrations across multiple apps without hacking Rails internals.

Basically, lightrail is a scaffold generator that generates the code into a plugin, rather than the main app directory.

Installation

Currently this is a gem, but you have to go through some hoops to install it:

1. Install the gems

  sudo gem install hoe newgem
  git clone git://github.com/zilkey/lightrail.git lightrail
  cd lightrail
  rake local_deploy

2. Generate your plugin generator

Once the plugin is installed, you can create a plugin which will store your reusable code.


script/generate lightrail_plugin plugin_name namespace

3. Install your reusable code with your plugin generator

Once you‘ve generated your plugin, you‘ll see that there is a new generator available. If you ran script/generate lightrail_plugin zilkey/product your generator would be named zilkey_product


script/generate zilkey_product

When you run your own generator, it:

  • Copies the controller, model, helper and views to the appropriate app directory
  • Copies all of the migrations in the plugins lib/db/migrate directory to the app‘s db/migrate directory

Example

1
2
3
4
5
6
7
8
9
rails lightrail-demo
cd lightrail-demo
script/generate lightrail_plugin zilkey cms
script/generate model page name:string
rake db:migrate
script/generate lightrail_pluginize_model page #=> creates a base class and an inherited class and sets the table name correctly
script/generate zilkey cms # => moves the generated files back to the app directory
# make some changes to the generated base class then run
script/generate lightrail_clone # => moves all of the changes you made in the _app_ back to the _plugin_

How it works

When you create a lightrail plugin like zilkey cms, the plugin has a directory structure like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  |-- MIT-LICENSE
  |-- README
  |-- Rakefile
  |-- generators
  |   `-- your_plugin
  |       |-- USAGE
  |       `-- your_plugin_generator.rb
  |-- init.rb
  |-- install.rb
  |-- lib
  |   |-- create
  |   |   |-- app
  |   |   |   |-- controllers
  |   |   |   |   `-- your_namespace
  |   |   |   |-- helpers
  |   |   |   |   `-- your_namespace
  |   |   |   |-- models
  |   |   |   |   `-- your_namespace
  |   |   |   `-- views_your_namespace
  |   |   |-- config
  |   |   |   `-- initializers
  |   |   |-- db
  |   |   |   |-- migrate
  |   |   |   |   `-- migrate_your_namespace
  |   |   |-- lib
  |   |   |-- public
  |   |   `-- spec
  |   |       |-- controllers
  |   |       |-- fixtures
  |   |       |-- helpers
  |   |       |-- models
  |   |       `-- views
  |   `-- destroy
  |       |-- app
  |       |   |-- controllers
  |       |   |   `-- your_namespace
  |       |   |-- helpers
  |       |   |   `-- your_namespace
  |       |   |-- models
  |       |   |   `-- your_namespace
  |       |   `-- views_your_namespace
  |       |-- config
  |       |   `-- initializers
  |       |-- db
  |       |   |-- migrate
  |       |   |   `-- migrate_your_namespace
  |       |-- lib
  |       |-- public
  |       `-- spec
  |           |-- controllers
  |           |-- fixtures
  |           |-- helpers
  |           |-- models
  |           `-- views
  |-- tasks
  |   `-- your_plugin_tasks.rake
  `-- uninstall.rb

When you run script/generate on your plugin, you‘ll notice that your main RAILS_ROOT directory looks like this:

1
2
3
4
5
6
7
8
  |-- app
  |-- controllers
  |    `-- your_namespace
  |-- helpers
  |    `-- your_namespace
  |-- models
  |    `-- your_namespace
  `-- views_your_namespace

Notice that it‘s placed a copy of each of your controller, model, helpers and views and migrations in the main app directory.

So for every controller, helper, model, view, migration and route in your plugin there is a corresponding file in the usual location in your main app. In addition, the migration fits logically into your migration scheme (that is, it‘s renamed to have the correct numbering scheme). The files in your app directory inherit from, or include, the files in the plugin, and the base classes are generated into the app directory for easy editing and cloning.

You can create modules for your controllers and helpers, so that the files look like this:

app/controllers/articles_controller.rb

1
2
3
class ArticlesController < ApplicationController
  include Zilkey::ArticlesController
end

app/helpers/articles_helper.rb

1
2
3
module ArticlesHelper
  include Zilkey::ArticlesHelper
end

app/models/article.rb

1
2
class Article < Zilkey::Article
end

This means that you can override specific methods in the controller, helpers and model and those changes extend or replace the plugin‘s functionality. Changes to the base classes/modules can be shared across all apps. Customizations to each individual app remain untouched. Upgrading

Let‘s say you‘ve added a new field to Article and you‘ve updated one of the views. When you reinstall your plugin, and regenerate the code Rails will ask if you want to overwrite the existing files the same way it normally would.

New models, views, controllers, migrations and routes are added (actually, all files in the plugin’s create directory are added – so you can even add dependent plugins to your generator!)

Let‘s say you have an Article model that you want to share across 2 apps. One app has a separate Publisher model that has_many :articles, the other app does not. When you add a field to Article, you want to be able to seamlessly update it across both installations, but you don‘t want to have to rewrite your Publisher customizations each time. With Lightrail, it‘s easy – here‘s how it works:

  • Create an Article plugin
  • Install it on app 1
  • Install it on app 2
  • Add the publisher model to app 2 and modify the files in your app folder with the changes

When Rails updates

Rails is constantly coming out with new versions and upgrades. Other plugins and systems like appable_plugins and engines rely on changing rails internals. Lightrail changes no rails internals and instead adds a few new methods to the generator commands. Other than that it is non-obtrusive.

As an example, rails just updated it‘s migration names to be named-based as opposed to numeric. This plugin, without making any changes, works the same on Rails 2.0.2 and edge (rev 9166)

Guides for making code more reusable

  • Cut your views into smaller partials – this way you can update some parts of the page without updating others
  • Never change migrations – always add new migrations with the changes

Ease of development

When developing plugins, it‘s helpful to be able to make changes to the base classes. As such, all models, controllers, helpers and views are removed from the load_once paths so as you make changes to the plugin, you don‘t have to restart your web server to see changes (in development mode).

Introducing webmaster_tools: a new rails plugin

Posted by jeff

If you manage websites and track them with Google, Yahoo! and MSN Live you know what a chore it can be to manage the webmaster verification process and building sitemaps. This should all be much easier now with my webmaster_tools plugin

To install it with git, run

1
git clone git://github.com/zilkey/webmaster_tools.git vendor/plugins/webmaster_tools

You can also check out the readme and rdocs at http://webmaster_tools.zilkey.com/

References

NOTE: this is a complete replacement of webmaster_verification

Breaking the cycle of doom, Part 2: Dating your vendor

Posted by jeff

Most folks in non-profits that I’ve met think that contracting a vendor to create a website is like buying a car. The RFP is their list of things they want in the car and the vendor’s job is to find the right car on their lot. If there’s nothing on their lot, you just go to the next one. In reality, choosing a technology vendor is a lot more like dating then it is shopping. In this installment of Breaking the Cycle of Doom I’m going to introduce some of the high-level concepts of agile programming, agile billing and the process of finding the right vendor.

The first date – leave the RFP at home

Scenario – you show up to your first date with someone and the first thing they do is pull out a list. You must be able to deliver 3 kids, 2 of them boys, get along smashingly with the in-laws, bring home at least $80K per year and this has to start happening in 3 weeks. Your date needs a written response detailing how you will make this happen, and a list of references of people for whom you have made this happen in the past. They need an answer by Friday at the latest. That’s what it feels like as a developer when I get a 70-page Request for Proposal from an organization I’ve never worked with, from people I’ve never met.

RFP’s take a long time to write, read and respond to and in most cases are completely unnecessary. Instead, I suggest going on an in-person, 1-hour first date with each vendor you are considering. If in-person meetings are not possible because of distance, you may want to consider looking for someone closer to home. Consider the fact that many of the biggest web sites that have shaped the information age came from the same 12-mile radius of Silicon Valley. Long-distance relationships are much harder to maintain in your personal life, and your organization relationships are no different.

On your first date, the vendor really only needs to know:

  • What’s the real goal? is it to find new donors? Treat existing donors better? Enhance your services?
  • Where’s the pain? What do you do now that you wish you didn’t have to? How long do those things actually take? Can you even do what you need to without your website?

Keep in mind that if you had all of the answers, you would be able to hire the programmers yourself but you don’t – you need help. Your RFP gives vendors the false sense that you know what you are talking about and closes off communication early on. You have a goal and you are looking for someone to help you achieve it withing your budget and you need to know when that can happen. Articulate what you want early on, in person, but leave the writing for later.

Like any first date, honesty and good listening skills are key. Is the vendor listening to you? Coming up with suggestions? Raising red flags? Can they talk about money (they should be able to tell the difference between a ~$10K site and a ~$200K site)? Do they come up with helpful suggestions on other ways to accomplish the same thing? Can they give you detailed timelines for a few small features? Nobody will be able to tell you with certainty how long or how much things will cost, but honest, knowledgeable vendors will be able to give you ballpark figures for specific types of functionality immediately.

I recently spoke with salesperson for a web host who suggested that I go to another hosting company because he thought that it would be a better fit for both of us. He flat out told me to go to the competition rather than just taking my money knowing it wouldn’t be ideal. In doing so he built my trust, and now I use their hosting company for several other clients for whom it’s a great fit. Vendors who are not willing to speak frankly about other options are likely hiding big flaws.

Going steady – one step at a time

Scenario – after a few dates you realize that you’re a great match. It’s time to go steady. Your date immediately slaps down a prenup the size of war and peace that details every date you’ll go on, what you’ll eat and who will pay. They ask you to review it and sign it, but there is a deadline. You look into each other’s eyes with puppy love and they promise it’s just a formality but you can’t escape that nagging feeling that you forgot something. You’ve already been asked to step into a point of no-return, but you barely know each other. As odd as that sounds for dating, that’s exactly what happens when you sign one of those massive scope-of-work (SOW) documents (usually after several weeks of revising the document itself). Even just the name, SOW, sounds awful.

In relationships I’ve always been told to take things one step at a time, and in programming it’s no different. In programming a step is called an “iteration” – a short period of time in which a small, complete set of working functionality is added. Let’s say there is a big vendor and they are going to design and build your site. The first iteration might be just to design it, and turn over the Photoshop files. They have a massive content management system? Great – the next iteration is to put your new designs into their CMS and transfer your content.. Every week, or few weeks, your vendor should be providing you with something that you can use immediately or take away – and you should pay them for what they do.

Most vendors won’t structure their contracts like this, which is troublesome. If that’s the case, maybe you’re not such a good match. After all, if they are confident that they can deliver, they shouldn’t be at all nervous about taking smaller chunks at the beginning. You’ll get to learn how they bill, and they’ll get to learn how you pay (let’s be honest – you’re a non-profit, sometimes bills are paid late).

This kind of iterative programming that puts relationships before contracts is called Agile Programming, sometimes called Extreme Programming. You can learn more about it at http://en.wikipedia.org/wiki/Agile_software_development

Breaking up – keep your apartment

Scenario – You’ve lost that lovin’ feeling. No one is really happy – you’ve changed your mind a million times, they want to know “where you are in the relationship” and you just want to chill and take it slow. You both decide that it’s time to move on. 2 weeks later you get a bill for all of the times you didn’t pay your half at the restaurant, and there’s an early-break-up fee that wasn’t in the prenup, not to mention the fact that the credit card is in their name and you have moved into their apartment. Now you not only need to deal with starting to date again, but you also have to find a new place and a new bank. Sound ugly? That’s what happens when these 134-page contracts end prematurely – it’s enough to make you want to just see it through even though it’s clearly not working out anymore – and that’s exactly what many non-profits do.

Contrast that with a more iterative approach where you have been paying all along for working features. Contracts are short and specific, and you can adapt them as your organization changes over time. If you get a massive spike in donors you can change priorities and focus on scaling first, instead of real-time event management software synchronization. When it’s time to go you are in a better place to have constructive conversations with the real human beings that work for the vendor to figure out an amicable way to change services.

As utopian as it sounds, I’ve seen this happen several times, but rarely in non-profits. You can’t function in a relationship if you are constantly thinking about what’s going to happen when you break up, but you can certainly have honest conversations about the consequences of certain decisions should you break up. The same is true for technology partnerships. If you split from a vendor prematurely you will both likely lost money. The bigger the upfront contract, the more is at stake and the more likely you are going to have a contentious and ugly break up.

In addition, the more you depend on them for every aspect of the site the easier it is for you at first, but the harder it is to ever make a change. Let’s say they use Verisign as their payment gateway, but Authorize.NET just became a big donor of your organization and said they’d give you service for a huge discount. Having the merchant account coupled with the hosting fee, coupled with the programming work makes it difficult or sometimes impossible, to take advantage of the ever-changing technology landscape.

Summary

  • Ditch the RFP – meet in person, consider this a partnership from the beginning
  • Ditch the bulky SOWs – instead create short, specific contracts that culminate in working software every week or two weeks
  • Don’t put your eggs in one basket – keeping things separate helps you move update small parts of your site without lots of dependencies

Related Reading

Breaking the cycle of doom, Part 1: Understanding the cycle

Posted by jeff

I’ve seen it time and time again – it all starts with a 70-page RFP from a non-profit that wants donor-relation management, content management, events management, complete real-time integration with their legacy systems, web services integration with their partners, mass-email capabilities, online donations and an online store, real-time tracking of the work they are doing in the field updated from satellites. They want a quote, and they want it now. The cheaper the better.

Then the 60-page responses come flooding in as well – there’s the Indian firm that promises all of that, plus several weeks of training for $15K – delivered in 6 weeks or less. There’s the massive vendor that’s throws in $125K as a starting point, vague about the timing and the custom programming costs but more than willing to put you in contact with one of their 50 sales representatives. There’s the small firm that recommends using a few existing tools, using a few pre-built tools and offers an expensive but competitive bid.

Depending on what the non-profit chooses, one of a few things happen. The Indian firm ends up charging 3-4 times what they originally planned since nobody at the non-profit realized that for $15K the requirements gathering phase amounts to little more than reading the RFP again, so every piece of code that was written had to be re-written multiple times before it was adequate. That big vendor? Well, they forgot to mention the $1 per email fee, the $500/hour training fee, the $4,500 AS-400 integration fee and the fact that nobody actually knows how to download that data from the field to a GPS, so that feature was scrapped. Oh, did I mention the $2K per month hosting fee? Over at the smaller firm they are really making an effort – but staffing is an issue and their contractors are spread thinly across multiple projects and progress is very slow. As the requirements change, the small firm adapts quickly, but it means that lots of the core functionality keeps getting pushed off.

As bleak as those scenarios may seem, more often than not what I’ve observed in real life is that many non-profits experience most if not all of these in an almost cyclical way. Once burned by big vendors, they turn the projects in-house, but then have staffing problems and outsource again, each time around paying again and again for the same 90% of the functionality they already had and never really getting that last 10% that actually makes people’s lives better. This is the cycle of doom.

I don’t propose to have a solution to the cycle of doom but I am convinced that using Ruby on Rails and agile methodologies can be a big part of the solution. Over the next few weeks I’ll be posting several non-technical posts for those in non-profits who are trying to figure out what they need as well as technical posts for developers who are trying to use Ruby on Rails to build their personal product-lines for non-profits.

Advanced acts_as_list scope with multiple columns

Posted by jeff

I just ran across a problem that I’m sure very few other people will encounter, but if I can help just one googler….

Scenario

You are using acts_as_list, and you want to scope your list by more than one column and one of the columns is a reserved word. You went to the rails documentation page for acts_as_list and you added something like this to your model:

1
2
3
4
class Element < ActiveRecord::Base
   belongs_to :page
   acts_as_list :scope => 'page_id = #{page_id} and `group` = \'#{group}\' '
end

Whoa – that’s some ugly code. Let’s see what’s happening:

  • The string you pass in to :scope is used to dynamically create a method called “scope_condition” with the contents of the string
  • The :scope has to be in single quotes so that the contents of the #{} blocks don’t get evaluated
  • The word “group” has to be in special quotes, otherwise it will throw a MYSQL error

There are a number of problems with the code above – namely:

  • The combination of single, double and angled quotes makes it very difficult to read
  • The angled-quotes will not work for every database adapter
  • The string contains #{} blocks, but they are not evaluated – instead they are evaluated later

The fix

Making the code database-independent is easy. Rails provides a method on every connection called “quote_column_name” which adds the correct quotes for whichever database adapter you are using. The connection object is a part of every Active Record instance, so the following line does the trick:

1
2
3
4
class Element < ActiveRecord::Base
  belongs_to :page
  acts_as_list :scope => 'page_id = #{page_id} AND #{connection.quote_column_name("group")} = \'#{group}\' '
end

Making the code easier to read is almost as simple – just define your own “scope_condition” method. The final code will look like this:

1
2
3
4
5
6
7
8
9
class Element < ActiveRecord::Base
  belongs_to :page
  acts_as_list  

  # scope_condition for acts_as_list
  def scope_condition
    "page_id = #{page_id} AND #{connection.quote_column_name("group")} = #{quote_value(group)}"
  end
end

Now we’re talkin’. The scope_condition is now database-independent, the quotes make sense and programs like TextMate will highlight the syntax appropriately.

References