has_many through help, what am I doing wrong? - ruby-on-rails

My models are like this:
User
has_and_belongs_to_many :Roles
Role
has_and_belongs_to_many :Users
tables:
roles_users
user_id
role_id
roleGroups
id
role_id
some_column
Now I want to create another association on the User model, that will be a collection of all roleGroups the user belongs to.
i.e. if the user is in roles with id's 1 and 2, then fetch all RoleGroups where role_id = 1 and 2.
I think I need to use a through because it is based on the user's Role association right?
I tried:
User
has_many :RoleGroups, :through => :Roles
Role
has_many :RoleGroups, :through => :User
But I get an error saying:
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection macro :has_many :through for has_many :RoleGroups, :through => :Roles. Use :source to specify the source reflection.
Update
Ok my models look like this now:
User
habtm :Roles
has_many :RoleGroups, :through => :Roles
Role
habtm :Users
has_many :RoleGroups
RoleGroup
belongs_to :Role
mysql tables:
roles_users
user_id
role_id
role_groups
id
role_id
col3
col4
..
If I do:
u = User.find(1)
u.Roles (works fine)
u.RoleGroups #see error
Error message:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'roles.user_id' in 'where clause': SELECT `role_groups`.* FROM `role_groups` INNER JOIN `roles` ON `role_groups`.role_id = `roles`.id WHERE ((`roles`.user_id = 1))

You're looking for the has_and_belongs_to_many association.

You can't do this the way you're thinking. I'm not sure why you're capitalizing your associations, but there are a few other things wrong, too.
First, RoleGroups hangs off of Role (via has_many, but more on that in a sec), which means you don't have a direct connection between User and RoleGroup.
Second, it appears from your updated explanation that each RoleGroup can have more than one Role, which is normal, but in your code Role has_many :role_groups, which means each role can have more than one role group. This is counterintuitive naming, but perhaps intentional. I'm going to assume that your role groups contain multiple roles rather than the other way around.
Third, you can't use a HABTM as a :through model. HABTM only uses a table in the database, not a Rails model, so the information in your roles_users table can't be used directly; has_many :foos :through => :bars requires an actual Bar model.
# Models
User
habtm :roles
Role
habtm :users
belongs_to :role_group # add role.role_group_id attribute
RoleGroup
has_many :roles # remove role_groups.role_id attribute
# Console
u = User.find(1) # get user
r = u.roles # get user roles
u.roles.collect { |role| role.role_group.name }
# ["Administrative","Editorial","User"]

Related

Ruby on rails fetch name and show from the has many through

we have user table and also wee have a country table the association is given as
user.rb
has_many :user_countries
has_many :countries, :through => :user_countries
user_country.rb
belongs_to :user
belongs_to :country
country.rb
has_many :users, :through => :user_countries
now what i want is to fetch the country name and show it into the sql record such that the new user_country column should come which will show the country of the user while fetching users
data = User.select("users.*, countries.name as country_name").
joins("INNER JOIN `user_countries` ON `user_countries`.`user_id ` = `users`.`id`").
joins("INNER JOIN `countries` ON `countries`.`id ` = `user_countries`.`country_id`")
till now i am trying to do this but not succeeded it throws the mysql error . i need an extra column giving country_name as extra column. Please help me with this
since you already using activerecord associations, you can use active record query as the following:
User.joins(:countries).select("users.*, countries.name as country_name")
You can learn more about select here
Looks like you're trying to create a many to many relationship. This can be done very simply with a has_and_belongs_to_many association. Take a look at http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association.

2 or more One to Many relationships between two tables in rails

I have two tables:
Users and Groups
a User has_many groups and a group, belongs_to a user:
u = User.last
u.groups
Supposed I wanted a second list of different groups, for some strange reason. Where once again a User has may groups (called other_group in this example) and a group belongs to a User.
u = User.last
u.other_groups
How do I associate two models in this relationship, Twice using Active Record?
Your User model could have two foreign keys (attributes in rails)
User.group_id
User.other_group_id
You can do
class User
has_many :groups, :class_name => "Group", :foreign_key => "group_id"
has_many :other_groups, :class_name => "Group", :foreign_key => "other_group_id"
You can do
User(user_id)
Group(group_id)
UserGroup (id, user_id, group_id)
this allows you to have records with user_ids associated for different groups.
This way lets you have multiple user-group associations.

Rails ActiveRecord how to order by a custom named association

Ok so have created 2 models User and Following. Where User has a username attribute and Following has 2 attributes which are User associations: user_id, following_user_id. I have set up these associations in the respective models and all works good.
class User < ActiveRecord::Base
has_many :followings, dependent: :destroy
has_many :followers, :class_name => 'Following', :foreign_key => 'following_user_id', dependent: :destroy
end
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :following_user, :class_name => 'User', :foreign_key => 'following_user_id'
end
Now I need to order the results when doing an ActiveRecord query by the username. I can achieve this easily for the straight-up User association (user_id) with the following code which will return to me a list of Followings ordered by the username of the association belonging to user_id:
Following.where(:user_id => 47).includes(:user).order("users.username ASC")
The problem is I cannot achieve the same result for ordering by the other association (following_user_id). I have added the association to the .includes call but i get an error because active record is looking for the association on a table titled following_users
Following.where(:user_id => 47).includes(:user => :followers).order("following_users.username ASC")
I have tried changing the association name in the .order call to names I set up in the user model as followers, followings but none work, it still is looking for a table with those titles. I have also tried user.username, but this will order based off the other association such as in the first example.
How can I order ActiveRecord results by following_user.username?
That is because there is no following_users table in your SQL query.
You will need to manually join it like so:
Following.
joins("
INNER JOIN users AS following_users ON
following_users.id = followings.following_user_id
").
where(user_id: 47). # use "followings.user_id" if necessary
includes(user: :followers).
order("following_users.username ASC")
To fetch Following rows that don't have a following_user_id, simply use an OUTER JOIN.
Alternatively, you can do this in Ruby rather than SQL, if you can afford the speed and memory cost:
Following.
where(user_id: 47). # use "followings.user_id" if necessary
includes(:following_user, {user: :followers}).
sort_by{ |f| f.following_user.try(:username).to_s }
Just FYI: That try is in case of a missing following_user and the to_s is to ensure that strings are compared for sorting. Otherwise, nil when compared with a String will crash.

Rails4 validate uniqueness of multiple columns

I have a model Friendship where :friend_id is also a foreign key as :user_id
friendships
id | user_id | friend_id
I'm aware that validating the uniqueness of the two fields would be something like this
validates :user_id, :uniqueness => {:scope => :friend_id}
But is there a way to validate
user_id = 1, friend_id = 3
user_id = 3, friend_id = 1
so that only user_id = 1, friend_id = 3 is stored?
Join Model
Looks like you're using a join model with Rails - as mentioned in the comments, you'll want to change this to friendship (not friend).
Rails has two types of join mechanism - has_many :through and has_and_belongs_to_many - both allow you to associate many-to-many records, like this:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through: :friendships
end
#app/models/friendship.rb
Class Friendship < ActiveRecord::Base
belongs_to :friend, class: "User", foreign_key: "friend_id"
belongs_to :user
end
#friendships
id | user_id | friend_id | created_at | updated_at
I would recommend you have a friendship model (as opposed to a model-less HABTM setup), because HMT allows you to add extra data, such as created_at, updated_at, permissions, etc)
Self Referential
I would also use a self-referential model. This will allow you to keep a single model (for User), and then call #user.friends without an issue.
Whether my above association definitions are correct is another matter; this is how you'd want to set them up.
--
Validation
In terms of validating your associations, I believe you'll be able to use indexes in your DB. This is used often for HABTM tables -
#Migration
# Adding the index can massively speed up join tables. Don't use the
# unique if you allow duplicates.
add_index(:friendships, [:user_id, :friend_id], :unique => true)
This will work for your original case, but not for the second

Rails 3. has_many as another class

So I have two models: User and ScheduledSession
The users have roles. One of the roles is "instructor".
ScheduledSession
belongs_to :instructor, :class_name => 'User', :foreign_key => 'instructor_id'
User
has_many :scheduled_sessions
GOOD! So I can do this...
s = ScheduledSession.first
s.instructor
BAD! But I can't do this...
u = User.first
u.scheduled_sessions
I get this error...
SQLite3::SQLException: no such column: scheduled_sessions.user_id: SELECT "scheduled_sessions".* FROM "scheduled_sessions" WHERE "scheduled_sessions"."user_id" = 1
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: scheduled_sessions.user_id: SELECT "scheduled_sessions".* FROM "scheduled_sessions" WHERE "scheduled_sessions"."user_id" = 1
How can I setup the relationship the other way around so I can see which scheduled sessions belongs to that instructor (user)?
You just need to set the foreign key in the User has_many relationship as well.
has_many :scheduled_sessions, :foreign_key => 'instructor_id'

Resources