Rails - multiple relationships between two models - ruby-on-rails

I have an existing database migration in my app that contains the following contents:
class User < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
belongs_to :user
has_and_belongs_to_many :locations
has_and_belongs_to_many :categories
end
Each user can vote once either like or dislike for a course, so I created a join table in db/migrate:
class CreateUsersCourses < ActiveRecord::Migration[5.0]
def change
create_table :users_courses do |t|
t.integer :course_id, null: false
t.integer :user_id, null: false
end
end
Now I am not sure how to add new relationship in User and Course models that does not make it overlapped with the One-Many relationship.

Firstly, don't forget that the associations between classes ought to describe the nature of the association. In this case there are multiple possible associations: a user can create a course, they can vote for a course, and they might study a course. Simple Rails naming conventions for the associations are not going to help you make sense of that.
If a course is created by a single user, then the relationship ought to be that the course :belongs_to :creating_user, and the user has_many :created_courses. You would add a user_id column to the courses table for this.
To allow voting by users on courses, you would use a join table between them. However, your association should describe the nature of the association, so calling the join table user_course_votes would make sense. Then you can have an association on the user of has_many :user_course_votes, and has_many :course_votes, :through :user_course_votes -- and appropriate associations on the course also.
The only thing you need to do to be able to name your associations whatever you like is to name the class or source in the association definition.
class User < ApplicationRecord
has_many :created_courses, class_name: "Course"
has_many :user_course_votes
has_many :course_votes, through: :user_course_votes, source: course
end
This methodology allows you to add other associations between user and course, and assign meaningful names to them that will make your code easier to write and to understand.

Now you will want to add User has_many :courses, through :user_courses to User.
Create a new migration to add a boolean attribute on User to represent your like/dislike.
Course probably has many users. It would make sense to want to see all of the users of a course.

Related

How to create a one to many relation between models in Ruby on Rails?

i've students and courses in my ruby models and controllers, so i want to connect this two things and show an user and the courses he have registered and click those courses to see what is inside that course. i'm new to ruby so i dont know much about has_many and i cant find something that can make what i want to work
i've use scaffold to create the models and controllers,
user only have a name, email and courses only have course_name
student:
create_table :student do |t|
t.string :name
t.string :email
course:
create_table :course do |t|
t.string :name
t.timestamps
in the index of students i only list all of the students i have.
halp pls
Looks like you want to use a many-to-many association between students and courses. There are a number of ways to achieve this. I would go with the has_many :though option described here, in which you add an additional model called StudentCourse.
So in your scenario, you would:
generate this StudenCourse model with rails generate model StudentCourse student:references model:references
add the following to your Student model
class Student
...
has_many :student_courses
has_many :courses, through: student_courses
...
end
add the following to your Course model
class Course
...
has_many :student_courses
has_many :students, through: student_courses
...
end
run migrations with rake db:migrate
now you can start adding students to courses and vice versa. For example:
Student.last.courses << Course.last
Course.first.students << Student.first
and in your controllers, you can simply call student.courses to see courses that are associated with a given student, or course.students to view students taking a specific course.
notice how both Course and Student models will are now associated with each other using has_many: ..., through: :student_courses.
Another benefit of using this type of many-to-many association is flexibility. For instance, you might want to start recording whether students have dropped specific courses. You can do so by simply adding a dropped_at column to this new student_courses table.
EDIT
adding a few more detailed examples of how to use this new association:
as mentioned, you can append courses to students and vice versa via rails console. For instance, a student with id of 1 wants to enroll into a course with id of 2:
Student.find(1).courses << Course.find(2)
similarly, you can just add students to courses like so:
Course.find(2).students << Student.find(1)
under the hood, both of these associations would be creating a new instance of the StudentCourse class we added. So a third option of creating this association would be:
StudentCourse.create(student: Student.find(1), course: Course.find(2))
What you most likely want here is actually a many to many assocation. Not one to many.
class Student < ApplicationRecord
has_many :enrollments
has_many :courses, through: :enrollments
end
class Course < ApplicationRecord
has_many :enrollments
has_many :courses, through: :enrollments
end
class Enrollment < ApplicationRecord
belongs_to :student
belongs_to :course
end
This lets you use courses as a normalization table which contains the information about the course instead of duplicating it for each student.
enrollments works as a join table and lets you join any number of students to any number of courses. Its also where you would store information that describes the relation between student and course - like for example the students grade (marks).
You need to set the relations i.e. associations in your model classes.
You propably have two model classes in a folder named app/models by now (assuming scaffolding has created them):
app/models/student.rb
app/models/curso.rb
in app/models/student.rb you need to have something like this:
class Student < ActiveRecord::Base
belongs_to :curso
end
in app/models/curso.rb you need to have something like this:
class Curso < ActiveRecord::Base
has_many :students
end
That's how associations are being created in rails.

Multiple Associations With the Same Table (Many-to-Many) Ruby on Rails

I am trying to figure out the best way to accomplish a relationship and having some trouble. I have come across a few articles that somewhat address what I am looking to do but not quite.
I have a Users model and a Tickets model. Both are joined through a UserTickets model so I am able to assign multiple Users to a ticket. What I would like to do is segment the Users assigned to a Ticket into requesters and agents. The Users model does not have any columns to declare if the user is a requester or agent, rather I have is_admin, is_customer, etc. I think what I need is along the lines of Ruby On Rails - many to many between the same table but not quite.
Ideally, I'd like to have my Tickets table take agent_id's (user_id from User class) and requester_id's (user_id from User class) rather than the general user_id's (user_id from User class which combines all the users into one group). I would assume would still allow me to call #current_user.tickets to pull all the tickets that are assigned to that user.
Here is my Users model:
has_many :user_tickets
has_many :support_tickets, through: :user_tickets
Here is my Tickets model:
has_many :user_tickets
has_many :users, through: :user_tickets
Here is my UserTickets join model:
belongs_to :user
belongs_to :support_ticket
Your help is greatly appreciated!
This is not a duplicate.
#Skylar,
1) If your goal is to assign multiple Users to a ticket? Because this doesn't require a many-to-many relationship. You just need to a one-many relationship and a boolean attribute agent? on User model. You can even create am Agent model that uses User model, if you like this better.
2) However, if you wanted to create a many-to-many relationship check out this. The Rails documentation is better than I write. See section 2.6 The has_and_belongs_to_many Association.
Solution to 1)
Models
class User < ApplicationRecord
has_many :tickets, class_name: :Ticket, foreign_key: :agent_id
def agent?
self.agent
end
end
class Agent < User
default_scope { where(agent: true) }
end
class Ticket < ApplicationRecord
belongs_to :user
belongs_to :agent, class_name: :User
end
Migrations
class CreateTickets < ActiveRecord::Migration[5.0]
def change
create_table :tickets do |t|
t.references :user, index: true
t.references :agent, index: true
t.string :event_name
t.timestamps
end
end
end

Model Structure & has_many Association Migrations

I'm trying to set up models in such a way that Users can create Lessons and then other users can sign up for them.
Right now my models are set up like this:
class Lesson < ApplicationRecord
belongs_to :teacher, class_name: 'User'
has_many :students, class_name: 'User'
end
class User < ApplicationRecord
has_many :lessons
has_many :students, :through => :lessons
end
I want to be able to access the users signed up for a lesson by #lesson.students for example. I'd also like to be able to get all the lessons that a student is participating in (can't really see how I'd do this with my current set up).
Are my model associations right for how I'd like to use them? If so, how can I create the migrations to add the necessary references to my database models?
If you want the ability to create nested resources from it's parents then you have to add:
accepts_nested_attributes_for
to the parent model.
Also, I recommend you to read how to set up has_many through relationships, you need a join model for rails to do its magic and link the 2 models
Once you set everything up, create the join model (with it's respective foreign keys, one for lesson and the other for user) rails will take care of the associations between the models, allowing you to do things like:
User.last.lessons #lessons created by the last user
and
Lesson.first.users #users subscribed to a lesson, in this case the first one

Having different associations between two models Rails

I am building an event app that lets users post events, and other users can 'register' themselves as attending that event. I currently have two models, 'User' and 'Article', where article is the event that a user can post. An article belongs_to :user and a user has_many :articles, dependent: :destroy.
I am trying to set up a new database to store which users plan to attend an event, in the form of a 'Registration', where it stores a user and the event he plans to attend. This means i need a many to many association between Users and Articles (as a user can attend multiple events, and an event can have multiple attendees). But will this not conflict with the original setting that states an article belongs to a single user?
How do i set this up so my associations dont interfere?
You could try either a has_many through: or a has_and_belongs_to_many relationship. Personally, I think I would use a HABTM for this, but the advantage of a HM Through is that there is an intermediate model, which can be used for additional information (such as whether an "attendee" is going or merely interested, etc): http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
As for having multiple different associations between the same two models, you can name the association anything you like but specify the class_name of the model you are pointing to: http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
For example:
class Article < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :attendees, class_name: "User", join_table: "articles_attendees", foreign_key: "attended_event_id", association_foreign_key: "attendee_id"
...
end
And for your User model:
class User < ActiveRecord::Base
has_many :articles
has_and_belongs_to_many :attended_events, class_name: "Article", join_table: "articles_attendees", foreign_key: "attendee_id", association_foreign_key: "attended_event_id"
...
end
That way you're able to name your association whatever you like, just be sure to keep your singulars singular and your plurals plural, and generally everything readable. class_name should be the name of the model to which you are defining the relationship. foreign_key is the database column name containing the ID of the models in which the relationship is defined. For example, in your User model, foreign_key should be the user ID. The association_foreign_key is the column containing the ID of the model to which you are linking.
Also don't forget to create your migration. Something like this example:
class CreateArticlesAttendees < ActiveRecord::Migration
def self.up
create_table :articles_attendees, :id => false do |t|
t.references :attended_event
t.references :attendee
end
add_index :articles_attendees, [:attended_event_id, :attendee_id]
add_index :articles_attendees, :attendee_id
end
def self.down
drop_table :articles_attendees
end
end

Modelling Many Rails Associations

I'm trying to wrap my head around how I should model my database for a parent model with many has_one associations (20+). I have one model called House which is the parent model of all the other models:
class House < ActiveRecord::Base
has_one :kitchen
has_one :basement
has_one :garage
has_one :common_room
#... Many other child models
end
All the child models contain unique properties specific to their own class. I've thought about STI, but there isn't really any shared functionality or inputs that I can use across models. I've also thought of making one 'super model', but that doesn't really follow Rails best practices, plus it would contain over 200 columns. Is there another design pattern or structure that I can use to model this efficiently so that I can reduce database calls?
class House
has_many :rooms
Room::TYPES.each do |room_type|
has_one room_type, -> { where(room_type: room_type) }, class_name: "Room"
end
class Room
belongs_to :house
TYPES = %i/kitchen basement garage common_room etc/.freeze
end
In your migration make sure to add_index :rooms, [:type, :house_id], unique: true. Unless a house can have more than 1 type of room. If that is the case, I think a different approach is needed.
To your second question, it depends really, what type of database are you using? If its PostgreSQL you could use hstore and store those as a properties hash. Or you could serialize you db to take that as a hash. Or you could have another model that room has many of. For instance has_many :properties and make a property model that would store that information. Really depends on what else you want to do with the information
Why not using Polymorphism in Rails? It would be much simpler
class House < ActiveRecord::Base
has_many :properties, as: :property_house
end
class Kitchen < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
class Garage < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
For more information, go here: http://terenceponce.com/blog/2012/03/02/polymorphic-associations-in-rails-32/

Resources