Ruby on Rails
HowToCreateASelfReferentialHasManyRelationship

Let’s say you want to modify the User class from the LoginGenerator to include a referred attribute, i.e., who told you about the service. The referred attribute is of class User, so the users table should have a referred_id column that references the id column of the same table. I’m assuming in this example that the login attribute is an email address, which I find is easier than a separate username for everyone involved.

In signup.rhtml, you’ll add a field for the user to enter their referrer:

  <label for="user_referrer_field">Email of the person who referred you:</label><br/>
  <%= text_field "user", "referrer_field", :size => 40 %><br/>

The User class model belongs_to a :referrer, which is stored in the referrer_id column:

class User < ActiveRecord::Base
  belongs_to :referrer, :class_name => "User", :foreign_key => "referrer_id" 
  has_many :users, :class_name => "User", :foreign_key => "referrer_id" 
  validates_uniqueness_of :login, :on => :create
  validates_format_of :login, :with => /
    ^[-^!$#%&'*+\/=?`{|}~.\w]+
    @[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])*
    (\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])*)+$/x,
    :message => "must be a valid email address",
    :on => :create
  validates_confirmation_of :password
  validates_length_of :login, :within => 3..80
  validates_length_of :password, :within => 5..40
  validates_presence_of :login, :password, :password_confirmation

  def referrer_field
  end

  def referrer_field=(field)
    return if field.empty?
    write_attribute("referrer_field", field)
    self.referrer = User.find_by_login(field)
    @referrer_invalid = true if self.referrer.nil?
  end

  protected

  def validate
    if @referrer_invalid
      errors.add(:referrer_field, "is not known; please fix or leave blank")
    @referrer_invalid = nil
    end
  end
end

The trick is to create accessors for referrer_field that convert the form submission into the proper format for the User model. We never need to get the field so we can leave the getter empty (but it needs to exist). The setter needs to both look up the User by login and also write back the contents of the field in case the form submission was not entirely valid and will be displayed back to the user.

We also want to validate that the referrer exists, so as to give the user feedback if they mistype the email address. Since we can see whether the lookup succeeded from within the referrer_field= method but we can only notify the user with errors.add from within the validate method, we create the instance variable referrer_invalid to communicate between the methods. The fancy email regex validation is from http://textsnippets.com/posts/show/243 .