Verb Pluralization Helper
August 17, 2006
I haven’t posted much lately — sorry about that! There are only 20 days left to the private beta launch of my application and there is an amazing amount of work to be done. I did, however, want to share a cool little helper for pluralizing verbs (which pluralize() does not do):
def pluralize_verb(count, singular, plural = nil)
verbs = { 'has' => 'have', 'was' => 'were', 'is' => 'are' }
if count == 1
singular
elsif plural
plural
elsif verbs[singular]
verbs[singular]
else
singular
end
end
Relative IDs in Rails
August 11, 2006
UPDATE: I did run into a problem with this method. If you have 5 forums, IDs 1 to 5, if you delete #5 and create another forum, the id will be 5. To solve this, in the groups table I added a “forum_count” column instead of trying to find the greatest group_forum_id in the before_save callback. I will update the article tomorrow (Aug 12).
In my current application, people belong to groups. Group members can, in turn, communicate with eachother via forums created by the group’s leader. To access the group’s homepage, you would visit /group/123, assuming the group’s ID was 123. By convention, Rails assumes that you are using a primary key in the URL. So lets say that group 123’s leader decides to create a forum. You may access it at /group/123/forum/58034 or /forum/58034, depending on how you set up the routes. A second forum, created several weeks later, may be at /group/123/forum/61009.
Now, behind the scenes, this makes perfect sense. You use the database’s primary key in the URL because it is the only unique identifier you have. But the forum belongs to the group. So why can’t we have an ID that is not application-wide in scope, but is instead relative to the group’s id? If we have the group’s id in the URL anyway, why can’t we do this? This would make our URLs much prettier, consider: /group/123/forum/1 and /group/123/forum/2.
Doing this was really quite simple. To start off, I needed some routes:
# Forum/post actions map.connect 'group/:group_id/forum/:action', :controller => 'forum', :action => 'index' map.forum 'group/:group_id/forum/:forum_id/:action', :controller => 'forum', :action => 'show'
(In case you are wondering, the first route is for things like creating a forum, or other actions that are not specific to one forum.) But we don’t necessarily want the forum_id to be the same as the primary key (although they will overlap, but it really doesn’t matter). So in our migrations, we add a column:
add_column :forums, :group_forum_id, :integer
Of course, I also had a foreign key relating the forum to a group (naturally called “group_id”). Now, when you create a forum, the forum’s “id” (the primary key) will be auto-incremented, but we need to make a before_create callback to increment the “group_forum_id” column. So in the Forum model, I added this:
def before_save
unless self.group_forum_id
f = Forum.find(:first, :select => 'group_forum_id', :order => 'group_forum_id DESC',
:conditions => [ 'group_id = ?', self.group_id ])
if f
self.group_forum_id = f.group_forum_id + 1
else
self.group_forum_id = 1
end
end
end
Here we expend one query to find the group_forum_id of the most recently created forum and increment it by 1, giving our current forum that value. If this is the first forum, however, “f” will be nil, so we just assign it the value for the first forum in the table: 1.
So now we have special column that is the id relative to the group_id and a method that sets that. The last thing to do is use this! In a controller action, say “destroy”, we would need to find the forum from the URL. You could use something like this:
@group = Group.find(params[:group_id]) @forum = @group.forums.find_by_group_forum_id(params[:forum_id])
That is, of course, assuming that the URL was fed the group_forum_id and not the primary key id for the forum. You may want to change it to something like group_forum_id to prevent confusion. And finally, assuming you are using the forum_id parameter in your URLs like me, you would create a link like this:
link_to 'Forum 1 for Group 123', forum_url(:action => 'show', :group_id => 123, :forum_id => 1)
Works very well!