:has_many :through Question

I have 3 tables that are forming a has_many through relationship:

  create_table "sites", :force => true do |t|     t.column "account_id", :integer, :default => 0, :null => false     t.column "created_on", :timestamp     t.column "updated_on", :timestamp     t.column "lock_version", :integer, :default => 0, :null => false     t.column "deleted", :string, :limit => 1, :default => "N", :null => false   end

  create_table "sites_users", :force => true do |t|     t.column "created_on", :timestamp     t.column "updated_on", :timestamp     t.column "lock_version", :integer, :default => 0, :null => false     t.column "deleted", :string, :limit => 1, :default => "N", :null => false     t.column "site_id", :integer, :limit => 10, :default => 0, :null => false     t.column "user_id", :integer, :limit => 10, :default => 0, :null => false     t.column "activated", :string, :limit => 1, :default => "N", :null => false     t.column "role", :integer, :limit => 10, :default => 1, :null => false   end

  create_table "users", :force => true do |t|     t.column "created_on", :timestamp     t.column "updated_on", :timestamp     t.column "lock_version", :integer, :default => 0, :null => false     t.column "deleted", :string, :limit => 1, :default => "N", :null => false     t.column "name", :string, :limit => 100, :default => "", :null => false     t.column "login", :string, :limit => 40, :default => "", :null => false     t.column "email", :string, :limit => 100, :default => "", :null => false     t.column "hashed_password", :string, :limit => 40, :default => "", :null => false     t.column "salt", :string, :limit => 40, :default => "", :null => false   end

My user model looks like this: require 'digest/sha1'

class User < ActiveRecord::Base   has_many :sites, :through => :sites_users   has_many :sites_users

  validates_length_of :login, :within => 6..40   validates_length_of :password, :within => 6..40   validates_presence_of :login, :email, :password, :password_confirmation, :salt   validates_uniqueness_of :login, :email   validates_format_of :email,                       :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,                       :message => "Invalid email"   attr_protected :id, :salt

  attr_accessor :password, :password_confirmation

  def self.authenticate(login, pass)     u=find(:first, :conditions=>["login = ?", login])     return nil if u.nil?

    su = su = u.sites_users.detect{ |su| su.site = session[:site].id and su.active = 'Y' }     return nil if su.nil?

    return u if User.encrypt(pass, u.salt)==u.hashed_password     nil   end


The system is setup to have many users assigned to different sites. Sites_users contains their role (dependent on site) and whether they are active on that site. When logging in the user needs to be authorized against the users table and the sites_users.

The problem I'm getting is that the table name sites_users is giving rails a fit. When I run the authenticate method I get the following error: ...lib/active_support/dependencies.rb:100:in `const_missing': uninitialized constant SitesUser

It's looking for the wrong class, notice that "Sites" is still pluralized. Since the class name is SiteUser this is not working.

I'm assuming I've broken the naming convention someplace, but I'm not sure where. Do I rename my table? My class?

Any help would be... helpful!

Thanks! Joe

short answer: your class should be named SitesUser and not SiteUser, by default, this is what Rails expects.

the reason for this is because sites_users is not an invisible join table like what you would use for a habtm relationship between sites and users. It's actually a full on table that goes hand in hand with the SitesUser model. Rails doesn't know to remove the pluralization of sites in the table name because of this.

you have 3 options

1) change the model name to SitesUser to correspond to your table name

2) change the table name to site_users (and all your associations as well) to correspond to your model name

3) set the table_name in the SiteUsers model to 'sites_users'

i recommend option 2.

did i explain that well enough?

Hey Chris, perfect explanation yah, thanks for the help. I think I had habtm and has_many through twisted together in my brain. So much for late nights, not enough caffeine, and staring at a problem too long.

In order to keep this from being completely confusing I ended up making the table name “Assignments” and renaming everything.

as an afterthought...

rename the sites_users table (and therefore the model and associations) to something more descriptive, perhaps something like 'site_roles'. remember you're not locked into the habtm table naming requirements.

class SiteRole < ActiveRecord::Base   belongs_to :site   belongs_to :user end

class Site < ActiveRecord::Base   has_many :roles   has_many :users, :through => :site_roles end

class User < ActiveRecord::Base   has_many :site_roles   has_many :sites, :through => :site_roles end

Hi, I think I like your “user_roles” name a lot better than the “Assignments” name I switched it to. I’ll end up going with that I think - thanks again!