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!

Yesterday, I discussed some reasons why I thought we would not see another bust on the internet comparable to that of the dot-com bust (at least in this Web 2.0 era). Via the blog of my first commenter, I discovered two interesting articles on why social networks can succeed and why others do not. A quick summary:

Why social networks can succeed

  1. Viral Nature. People want to expand their networks, so they invite their friends.
  2. Online Identity. People want a place to call home on the web. Ever see people link to their MySpace profile on forums? Additionally, people want to be able to customize their profile.
  3. Enhanced Knowledge.
  4. Basic human need to share. People like to share their thoughts, opinions, and experiences. Obviously why many networks allow users to have blogs.
  5. Basic human need to connect. People need to connect, and people love to expand their “network” of connections.

Why social networks fail

  1. Privacy concerns. Turn on CNN. I am sure if you watch it for an hour you will see something about websites online collecting your data, selling it, and sending you truckloads of spam. But then again, remember that many social networks ask for data much more personal than just your email!
  2. No real reward or penalty system. Not all “connections” are created equal. I may have 500 friends, but really, how good of a friend to me are the Red Hot Chili Peppers? A social network should reward people for quality connections.
  3. Not granular enough. As noted in #2, all relationships are not equal. But there are also different types of relationships. Jim is my friend, Sally is my co-worker, and the Red Hot Chili Peppers are my idol.
  4. Not integrated with other apps. Well gee, knowing that I have 500 “friends” is pretty darn cool, but now what? The author of this article suggests that social networks should take their integration a step further to make them more useful. LinkedIn is a good example. You don’t create a network for the fun of it, you do it to create a network of people which ultimately could lead to your next job.
  5. Walled gardens. Share information with other social network sites, don’t keep it all for yourself. If the larger social networks shared information with the smaller ones, people could easily go to their niche-specific social networks for their unique fix, and yet still have access to all of their friends and contacts.

Leading up to my current project, I had spent several years working with small PHP hack-um-up sites. This project, however, was much larger, and the time frame for this project was very limited. I looked at several options:

  • PHP (language)
    • As I mentioned, I had spent several years with PHP, so this was naturally my first option. I decided to look at some alternatives as well, though.
  • CakePHP (PHP4/5)
    • Even with several years of experience, I had spent very little time working with frameworks (despite the fact that at one point I tried joining the creation of an object-oriented CMS/framework hybrid), so this was very new to me. The problem was that, at this time, very little documentation and examples were available.
  • Symfony (PHP5)
    • Around this time, I had started gaining more and more interest in PHP5 and OOP (object-oriented programming). The truth was I didn’t actually understand it completely at the time (note: if you are looking for a good introduction to OOP in PHP, check out this free online book), but I really wanted a framework that used it (I even considered creating my own framework, which I was vastly underskilled to do).
  • Ruby on Rails (Ruby)
    • Finally, I repeatedly came across “Ruby on Rails”, a seemingly very popular framework that used a language I had never heard of — Ruby (this was actually a few months before I began development).

Meet Ruby on Rails

When I first experienced Ruby, I loved it. The code made sense: it was simple, effective, and easy to read. But as I read more and more about Rails I developed two main concerns: 1.) Ruby on Rails had a lot of hype. I had to know, was this the real deal? 2.) I ran into a lot of remarks about the speed of the Ruby language compared to other scripting languages (PHP, Python, etc). Being a perfectionist, this was quite distressing for me. I was the type of person who would write a web application that will receive minimal traffic in Java just so I can sleep better at night, knowing that I gained a few milliseconds on the page load (realize that I did not and still do not know Java, but I seriously considered it). I didn’t really have the resources to launch a Java-backed web application though, nor would I have needed to anyway.

After buying the Agile Web Development with Rails, 2nd Edition Beta Book (highly recommended), I started really liking the framework and began to agree with the methodologies that DHH was pushing. I read a lot of tutorials, I partially read a book on the Ruby language, and I finally decided to develop my application with it. Unfortunately, I had actually only spent a few weeks reading these books and investigating the framework and the language, so my knowledge was very impaired. This led to a lot of problems early-on in development, but now I am kind of glad that I just went ahead and started. Had I not, I would probably be nearly complete with the project (which I’m not now), but it would have been much less enjoyable.

And so, I was at last riding on Rails!