Rails: Multiple Join tables between the same two models - ruby-on-rails

I have two models: Player and Event with two join tables between them, participants and lessons.
class Event
has_many :participants
has_many :players, through: :participants
has_many :lessons
has_many :players, through: :lessons
end
class Player
has_many :participants
has_many :events, through: :participants
has_many :lessons
has_many :events, through: lessons
end
Not all events have lessons but all events have participants hence why I split the join table into two.
The problem is that if I were to do #player.events the resulting query would use the lessons join table instead of the participants.
Is there a better way to do this this?
EDIT: Here are the join table models:
class Lesson
belongs_to :player
belongs_to :event
end
class Participant
belongs_to :player
belongs_to :event
end

You can use the class_name option to change the name of one of the has_many associations but still associate with the other class. In my example I am using students for players on an event through lessons, and lectures for events on a player through lessons. But what you name them can be different.
class Event
has_many :participants
has_many :players, through: :participants
has_many :lessons
has_many :students, through: :lessons, class_name: "Player"
end
class Player
has_many :participants
has_many :events, through: :participants
has_many :lessons
has_many :lectures, through: lessons, class_name: "Event"
end

Related

Two similar Many-to-Many Relationships

I have two similar M:M relationships that I work with individually however I don't know how to them working without conflicting.
Relationships are Players & Teams
1) Many players "play for" many teams
2) Many players "are members of" many teams
class Player < ActiveRecord::Base
has_many :plays
has_many :members
has_many :teams, through: :plays
has_many :teams, through: :members
end
class Teams < ActiveRecord::Base
has_many :plays
has_many :members
has_many :players, through: :plays
has_many :players, through: :members
end
class Play < ActiveRecord::Base
belongs_to :players
belongs_to :teams
end
class Member < ActiveRecord::Base
belongs_to :players
belongs_to :teams
end
I need to be able to find:
Player.find(21).teams #who he plays for
Player.find(21).teams #who he is a member of
You have to give a different name to each has_many association, and use the source parameter to specify the actual association name.
Like this:
class Player < ActiveRecord::Base
has_many :plays
has_many :memberships
has_many :played_with_teams, through: :plays, source: :team
has_many :member_of_teams, through: :memberships, source: :team
end
class Team < ActiveRecord::Base
has_many :plays
has_many :memberships
has_many :played_players, through: :plays, source: :player
has_many :member_players, through: :members, source: :player
end
class Play < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
class Membership < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
That can be used like this:
Player.find(21).played_with_teams #who he plays for
Player.find(21).member_of_teams #who he is a member of
Hint: I updated the answer.

Rails 4 has_many :through association with :source

I was following the answer laid out at the link below to set up a many_to_many relationships on my Rails 4 app. (New to rails, here.)
Implement "Add to favorites" in Rails 3 & 4
I have Users and Exercises, and I want users to be able to have Favorite Exercises. I created a join table called FavoriteExercise with user_id and exercise_id as columns. I've got it populating, and it seems to be working fine, but I'm not able to use it to call directly to my favorites. 
Meaning, I want to type:
user.favorite = #list of exercises that have been favorited
I get this error when I try to load that list in my browser:
SQLite3::SQLException: no such column: exercises.favorite_exercise_id:
SELECT "exercises".* FROM "exercises" INNER JOIN "favorite_exercises"
ON "exercises"."favorite_exercise_id" = "favorite_exercises"."id"
WHERE > "favorite_exercises"."user_id" = ?
My models:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, source: :exercises
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorites
has_many :favorited_by, through: :favorite_exercises, source: :exercises
class FavoriteExercise < ActiveRecord::Base
has_many :exercises
has_many :users
I just tried switching FavoriteExercise to 'belongs_to' instead of 'has_many, because it seems maybe that's the way that should go? but then I get this error:
uninitialized constant User::Exercises
Just trying to figure out how to set up the tables and associations so I can call .favorites on a user and get all their favorites.
If you want the list of exercises of the user and at the same, the list of favorite exercise of the user, then I think your join table should just be users_exercises wherein it will list all the exercises by the users. To list the favorite exercises, just add a boolean field indicating if the exercise is a user favorite and add a :scope to get all the favorite exercises.
So in your migration file:
users_exercises should have user_id, exercise_id, is_favorite
Then in your model:
class User < ActiveRecord::Base
has_many :workouts
has_many :users_exercises
has_many :exercises, through: :users_exercises
scope :favorite_exercises, -> {
joins(:users_exercises).
where("users_exercises.is_favorite = ?", true)
}
class Exercise < ActiveRecord::Base
has_many :workouts, :through => :exercises_workouts
has_many :users_exercises
has_many :users, through: :users_exercises
class UsersExercise < ActiveRecord::Base
belongs_to :exercise
belongs_to :user
You just need to simplify your model logic as follow:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, class_name: "Exercise"
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorite_exercises
has_many :favorited_by, through: :favorite_exercises, class_name: "User"
class FavoriteExercise < ActiveRecord::Base
belongs_to :favorited_by
belongs_to :favorite
Then you can call user.excercises or excercise.users in your User/Excercise instance.
user.excercises = #list of exercises that have been favorited
Is that the many-to-many relationship you want?

Ruby: specify has_many :through association when several has_many :through associations are given

i have a simple data set-up with a model for Users and a model for Tasks.
Between these two models i have two has_many :through associations: Fellowships and Assignements. In total i want to specify for a task several followers and several assignees.
I now want to display for a specific task all assignees and all followers.
If there would only be one association I simply could do #task.users. As i have two associations i want to specify by which association i want to get all users.
See my code:
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, through: :assignments
has_many :fellowships
has_many :tasks, through: :fellowships
end
class Task < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
has_many :fellowships
has_many :users, through: :fellowships
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Fellowship < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
Let's assume i have a task as
#task = Task.first
I now want to have all assignees and all followers with something like
#assignees = #task.users "over association assignment"
#followers = #task.users "over association followship"
but i don't know how to do this.
Thanks for the help!
You can write in following way.
has_many :assignment_tasks ,through: :assignments ,source: :task
has_many :fellowship_tasks, through: :fellowships, source: :task

Rails belongs_to through association

I'm trying to add a new model to an existing model mesh. The existing one works perfectly but I can't get the new one to work properly and am wondering if the association is able to work the way I'm trying to make it work. Update: As I just got asked: belongs_to through was something I've read while gooling about the problem. If it doesn't exist, would has_one through be the correct way? I tried it as well but it also didn't work.
Here is the existing mesh:
class Course
has_many :users, through: :enrollments
has_many :enrollments
end
class User
has_many :courses, through: :enrollments
has_many :enrollments
end
class Enrollment
belongs_to :course
belongs_to :user
# has fields :user_id, :course_id
end
Now a user should be able to rate a course he's completed. (If he has, there is an enrollment with his id and a course id.) I thought it would be best to write it as follows:
class Course
has_many :users, through: :enrollments
has_many :enrollments
has_many :ratings, through: :enrollments
end
class User
has_many :courses, through: :enrollments
has_many :enrollments
has_many :ratings, through: :enrollments
end
class Enrollment
belongs_to :course
belongs_to :user
has_one :rating
# has fields :user_id, :course_id
end
class Rating
belongs_to :enrollment
belongs_to :course, through: :enrollment
belongs_to :user, through: :enrollment
end
When I try to create a Rating in the console, I get the following error:
User.first.ratings.create(text: "test", course_id: Course.first.id)
ArgumentError: Unknown key: through
Update
When I use has_one through insted, I get the following error:
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'User#ratings' because the source reflection class 'Rating' is associated to 'Enrolment' via :has_one.
Is it possible to do it this way at all? Thanks!
class Course
has_many :users, through: :enrollments
has_many :enrollments
has_many :ratings, through: :enrollments
end
class User
has_many :courses, through: :enrollments
has_many :enrollments
has_many :ratings, through: :enrollments
end
class Enrollment
belongs_to :course
belongs_to :user
belongs_to :rating
# has fields :user_id, :course_id, rating_id
end
class Rating
has_one :enrollment
has_one :course, through: :enrollment
has_one :user, through: :enrollment
end
Note: Add foreignkey columns
And if you there is just one/two columns in ratings table merge them into enrollments like this.
class Course
has_many :users, through: :enrollments
has_many :enrollments
end
class User
has_many :courses, through: :enrollments
has_many :enrollments
end
class Enrollment
belongs_to :course
belongs_to :user
# has fields :user_id, :course_id, rating-columns...
end
Structure
Maybe you're complicating this too much
class Enrollment
belongs_to :course
belongs_to :user
end
This means you have a join model which stores unique records, referencing both course and user. Your ratings are on a per user and course basis?
Why don't you just include rating as an attribute of your enrolment model?:
#enrolments
id | user_id | course_id | rating | created_at | updated_at
If you give rating a numeric value (1 - 5), it will give you the ability to rate the different enrolments like this:
user = User.first
course = Course.first
user.enrolments.create(course: course, rating: 5)
--
Ratings
This is, of course, based on your current model structure.
If you want to include ratings for courses by users (not tied to enrolment), you may wish to use a join model called course_ratings or similar:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :enrolments
has_many :courses, through: :enrolments
has_many :ratings, through: :courses, class_name: "CourseRating"
end
#app/models/course.rb
Class Course < ActiveRecord::Base
has_many :enrolments
has_many :students, through: :enrolments, class_name: "User"
has_many :ratings, class_name: "CourseRating"
end
#app/models/course_rating.rb
Class CourseRating < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
This will allow you to call:
user = User.first
user.courses.first.ratings

Rails: ignoring duplicates in an nested association

I have models User, Team, Document. There's a many-to-many relationship between Users and Teams, and a many-to-many relationship between Teams and Documents, using join tables called TeamMembership and TeamDocument respectively.
The relationships in my models look like this:
class Document < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :teams, through: :team_documents
end
class User < ActiveRecord::Base
has_many :team_memberships, dependent: :destroy, foreign_key: :member_id
has_many :teams, through: :team_memberships
has_many :documents, through: :teams
end
class TeamDocument < ActiveRecord::Base
belongs_to :team
belongs_to :document
end
class TeamMembership < ActiveRecord::Base
belongs_to :team
belongs_to :member, class_name: "User"
end
class Team < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :documents, through: :team_documents
has_many :team_memberships, dependent: :destroy
has_many :members, through: :team_memberships
end
The idea is that users can belong to many teams, a document can be associated with many teams, and users will only have access to documents that "belong" to at least one team that the user is a member of.
Here's the question: I can use User#documents to retrieve a list of all the documents that this user is allowed to view. But this will return duplicates if a document is viewable by more than one team which the user is a member of. How can I avoid this?
I know I can remove the duplicates after the fact with #user.documents.uniq, but as I will never want to include the duplicates in any case, is there a way I can just make #documents not include duplicates every time?
I don't have nested has_many :through like yours to test it, but I suspect using uniq option on your user association would help :
class User < ActiveRecord::Base
has_many :documents, through: :teams, uniq: true
end
You can add a default_scope on Document model:
class Document < ActiveRecord::Base
default_scope group: { documents: :id }

Resources