Rails

February 07, 2008

Facebooker and Rails 2.0

If you are using the Ruby Facebooker plugin for developing Rails Facebook apps, you should note that the default cookie-based session storage engine is going to give you some weird errors concerning your Facebook session key being invalid or no longer valid. I assume that RFacebook would have the same problem, but I am just toying around, not seriously investigating Facebook development.

The obvious fix is to switch to a alternative session storage engine (e.g. active_record_store).

January 16, 2008

Using :joins in ActiveRecord

I always seem to forget about the :joins option in ActiveRecord. It is incredibly useful for a certain class of not-to-often needed problems. Like DHH says, Ruby on Rail's will let you do anything but will impose it's opinions by making some things more painful.

Let's say I have the following models:

class Contact < ActiveRecord::Base
  belongs_to :user
  has_many :receipts

end

class User < ActiveRecord::Base
  has_many :contacts
  has_many :roles     # Roles in many different product lines

end

class Role < ActiveRecord::Base
  belongs_to :user
  belongs_to :product_line

end

Now, let's say I have a receipt that says only which contact paid and the amount of money received, not which user made the sale (I am obfuscating what my app actually does with this salesman analogy.) I have to do a join from the contact table to the user table to the roles table (users can have identical contacts but may not necessarily be selling the same product). Basically, :include=>[...] won't work.

Instead, you use the wonderful little :joins=>... option.

...
conditions_string +=' AND `users`.id=`contacts`.user_id AND `roles`.user_id=`users`.id AND `roles`.product_line_id=:product_line_id'         conditions[:product_line_id]=params[:product_line_id]
@contacts=Contact.find(:all,
            :conditions=>[conditions_string, conditions],
            :joins=>',`users`,`roles`')

January 12, 2008

Ordering error_messages_for in Rails

Due to laziness, I have not, in the past, given much back to the open-source community. I would like to start doing so.

For the most part, I think Rails is an elegant solution to web application development. That is why I find it shocking that error_messages_for is still unordered due to hash storage. It is a pet peeve of mine to get a form with multiple errors that do not match the order presentation by the form. It just seems sloppy.

Anyways, I took the time to write a plugin named ordered_error_messages_for. It extends the core so that if you supply :order=>[:attr1,:attr2, …] to error_messages_for(…) you get a clean orderly display of errors.

Example:

error_messages_for(:user, :order=>[:name,:password,:phone_number])
error_messages_for(:user, :order=>{:user=>[:name,:password,:phone_number]})

Install it with a:

./script/plugin install http://orderederrors.rubyforge.org/svn/ordered_error_messages_for

More information at:
orderederrrors on RubyForge

Sexy. Rails Sexy.

January 10, 2008

Sharding with Cookie-Based Sessions

I used Rails 2.0 as part of my recent startup venture (actually, I started with 1.2.3 but migrated 2 days after the new release).  The cookie-based session storage was my primary motivation for using the latest release before it has been hardened by wide deployment.  Cookie-based session storage is sexy.  They appropriately move session storage to the user through the utilization of HMAC.  In other words, no scalability bottlenecks will be imposed because of PStore and ActiveRecord sessions.

While the decision to make Cookie-Based session storage the default session storage engine in Rails 2.0 still seems to be a bit controversial, the benefits, assuming security is maintained, are incredible.  In my venture, I used sessions in a way that many developers would find dangerous.  My venture was a dating website targeted to large metropolitan areas, each with it's own sub-domain.  The website emphasized finding people to go on a date with in real-time.  Offline users were not browsable. Users were grouped by metro shard: New York City residents would go to the nyc sub-domain, Philadelphia residents would go to the philly sub-domain, etc.  All of the user's profile information and messages was stored on server pointed to by the primary domain, but once logged in and ready to interact, the user would be dispatched to the appropriate shard.

Here's where things get…unique. Instead of doing MySQL replication to each shard, each shard had its own, unshared database.  When a user decided to interact in a specific metro area, the user clicked a form, disguised as a hyperlink, which submitted their encrypted profile to the appropriate shard.

For example, if the user wanted to find a date in Uptown Manhattan, they would click the Uptown Manhattan link, which did a post to the nyc shard, Uptown Manhattan homepage, sending the encrypted profile silently along.  The nyc shard then checked for tampering, decrypted the profile, and stored it in it's local memory only database.  If the user had had been idle for too long the profile was marked as stale, and a chron job would sweep it away shortly thereafter.

This technique is only worthwhile if there is something that can be sharded but there are clear benefits. The main server handled all permanent changes to the user's data (including message sending) but this was done via posts and redirects.  The main server and each shard were completely decoupled.  They did not, at any point, communicate with each other.  Obviously, this makes for a very scalable website.

I should note that by using this technique I was committing the dual sin of premature optimization and abuse of something cool for the sake of messing around.  That makes me dumb.  My point is, if you have a mature project that is running into scalability constraints but has a possibility for sharding, this technique could be valuable (or at least cool).