12

I've read the documentations and tons of tutorials about the has_many :through relations in Rails but I can't for the life of me get the hang of it.

I'm trying to add a group to my current_user(devise) and I have a table in between Group and User with a status(The user's status is changeable for that group).

Whenever I create a new Group now I get an error saying uninitialized constant Group::GroupUser

here are my models:

groupuser.rb

class GroupUser < ActiveRecord::Base
    belongs_to              :group
    belongs_to              :user
end

group.rb

class Group < ActiveRecord::Base
    has_many                            :clients
    has_and_belongs_to_many             :pictograms

    has_many :group_users
    has_many :users, :through => :group_users

    accepts_nested_attributes_for :clients

    validates_length_of                 :name,  :minimum => 5
    validates_presence_of               :name
    validates_presence_of               :background
    validates_presence_of               :clocktype
end

User.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable


  validates_presence_of   :first_name
  validates_presence_of   :last_name
  validates               :email, presence: true, uniqueness: true

  has_many :group_users
  has_many :groups, :through => :group_users

  has_attached_file :avatar, :styles => { 
                                :medium => "300x300#", 
                                :thumb => "100x100#" 
                             }
  validates_attachment_content_type :avatar, :content_type => ['image/jpg', 'image/png', 'image/jpeg']

  validates_attachment :avatar,
                  :size => { :in => 0..1.megabytes }

  def completeName
    "#{self.first_name} #{self.last_name}"
  end
end

And the related stuff from schema.rb

  create_table "group_users", id: false, force: true do |t|
    t.integer "group_id"
    t.integer "user_id"
    t.integer "status",   default: 0
  end

  add_index "group_users", ["group_id"], name: "index_group_users_on_group_id"
  add_index "group_users", ["user_id"], name: "index_group_users_on_user_id"

  create_table "groups", force: true do |t|
    t.string   "name"
    t.integer  "clocktype"
    t.string   "background"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
  create_table "users", force: true do |t|
    t.string   "first_name"
    t.string   "last_name"
    t.string   "password"
    t.string   "avatar_file_name"
    t.string   "avatar_content_type"
    t.integer  "avatar_file_size"
    t.datetime "avatar_updated_at"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true
  add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

And lastly. the line that throws the error

@group.users << current_user
CaptainCarl
  • 3,411
  • 6
  • 38
  • 71

1 Answers1

25

The problem I can see is your naming of GroupUser

We use join models all the time, and we also name them with an underscore:

group_user.rb
class GroupUser < ActiveRecord::Base
  belongs_to :group
  belongs_to :user
end

The uninitialized constant error will be caused by the model not loading correctly, which I imagine will be due to the naming issue. If that doesn't work, you should reference the model with the :class_name parameter in your relation declarations:

  has_many :group_users, :class_name => 'GroupUser'
  has_many :groups, :through => :group_users

Update

We wanted to call extra attributes from the join model, and found this way:

#app/models/message.rb
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy

This uses the select method which you can use when you created an ActiveRecord association, and as we discovered from this RailsCast, you can use that to create alias columns in your queries

For you, you may wish to try this:

#app/models/user.rb
has_many :group_users, :class_name => 'Groupuser'
has_many :groups, -> { select("#{User.table_name}.*, #{Groupuser.table_name}.status AS status") }, :class_name => 'Group', :through => :group_users, dependent: :destroy
Matilda Smeds
  • 1,384
  • 11
  • 18
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • 1
    Wow; It was that easy, huh? Thanks a lot! Works like a charm. – CaptainCarl Dec 16 '13 at 09:34
  • No problem buddy - sometimes, it's the simplest things! – Richard Peck Dec 16 '13 at 09:35
  • I've changed the name to Groupuser for easier use :-) Quick question though: How do I get the status from group_users to show for each user? Because when I'm looping through my Group.all and do `individualGroup.groupusers.status` nothing seems to be there. (`individualGroup.groupusers` does!) – CaptainCarl Dec 16 '13 at 09:43
  • Is status an extra attribute in your `GroupUser` join model? – Richard Peck Dec 16 '13 at 09:44
  • Status is in my groupuser table(See schema above) – CaptainCarl Dec 16 '13 at 09:46
  • Ohh thanks! We found a way to do this - I'll post update for you – Richard Peck Dec 16 '13 at 09:48
  • Please leave a comment when you do so I don't have to keep refreshing this page like a desperate maniac! :-p – CaptainCarl Dec 16 '13 at 09:55
  • Might not work out of the box, but we can work to fix it – Richard Peck Dec 16 '13 at 09:57
  • Looks quite complex! I thought that was the way to go for additional attributes, I was wrong! :D I'm getting this error though: `SQLite3::SQLException: no such table: users: SELECT users.*, groupusers.status AS status FROM "groups" INNER JOIN "groupusers" ON "groups"."id" = "groupusers"."group_id" WHERE "groupusers"."user_id" = ?` – CaptainCarl Dec 16 '13 at 10:01
  • Hmm okay, do you have a `users` table, called `users`? And also, is your `groupusers` table called that? We normally call our join model tables `group_users` and similar – Richard Peck Dec 16 '13 at 10:02
  • Schema shows: `create_table "groupusers",......` and `create_table "users",.....` – CaptainCarl Dec 16 '13 at 10:03
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/43245/discussion-between-captaincarl-and-rich-peck) – CaptainCarl Dec 16 '13 at 10:05
  • Can anyone explain why this worked? I have a model named Box, and a model named Order, and a has_many through BoxOrder named box_order.rb and it finds the model fine? – Siraris Mar 16 '18 at 15:50
  • Have you checked your database? ActiveRecord automatically creates a record in the join table and uses that as a reference. – Richard Peck Mar 18 '18 at 16:07