Having different associations between two models Rails - ruby-on-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

Related

Tricky Rails migration and modeling involving a self-join ":users" table and a ":bands" table: simultaneous one-to-many and many-to-many relationships

My :users table is successfully self-joined with all the necessary confusing (to this newbie) code and tables necessary to do that. :users's two groups are :teachers and :students.
I need to make the group :teachers join one-to-many with the :bands table (a band may have only one teacher) while at the same time joining :students many-to-many with the :bands table (a band may have many students and vice versa).
What's tripping me up is :students and :teachers are both :users. Therefore, if for a moment I pretend that there's only one kind of user and go for a one-to-many (teacher) relationship, then the Band model belongs_to :user and the User model has_many :bands
But if I go for the many-to-many (student) relationship instead, the Band model has_many :users, through :user_bands join table and the User model has_many :bands, through :user_bands join table. (UserBands model has belongs_to :user and belongs_to :band in this case)
But I need both relationships at the same time. I haven't actually tried putting has_many :bands in the User model while simultaneously having has_many :users (through join table) and belongs_to :users in the Bands model because, unless Rails is more magic than I give it credit for, it won't differentiate that teachers get the single-to-many while the students get the many-to-many.
I have not attempted my best guess (below) because I'm admittedly skittish: my database already has a sprawling number of many-to-many relations that are intact and functioning properly. The one time I attempted to make a complicated alteration to it earlier in the process, it thoroughly messed things up so badly that rolling back and undoing model alterations didn't get me back to where I'd started somehow, so I had to go back to rebuilding everything from scratch after pulling out enough hair for a tonsure. I do have github this time, so I should be able to revert the project if it blows up like before, but git is its own fussy minefield.
So if some folks could take a look at my guess first, I'd deeply appreciate it. Does it look right? Do I need to make changes before updating the database schema?:
In User model, add has_many :bands.
In Band model, add has_many :students, through :user_bands ; add belongs_to :teacher
In the create_bands migration, add t.belongs_to :teacher
In UserBands model, add belongs_to :teacher and add t.belongs_to :teacher in the create_user_bands migration.
The needed associations are not self-joining. Self-joins are primarily used to build tree like hierarchies from a single table - see the guides for a good example.
You just need multiple associations between two tables - the key thing here to remember is that you must use unique names for each association. If you declare multiple associations with the same name the later just overwrites the former without error.
Teachers
class AddTeacherIdToBands < ActiveRecord::Migration[5.2]
def change
add_reference :bands, :teacher, foreign_key: { to_table: :users }
end
end
class User < ApplicationRecord
has_many :bands_as_teacher,
class_name: 'Band',
foreign_key: 'teacher_id'
end
class Band < ApplicationRecord
belongs_to :teacher,
class_name: 'User'
end
We name the association bands_as_teacher to avoid conflict and confusion. This requires us to explicitly set the class_name and foreign_key options as they cannot be deduced from the name.
This is kind of where you tripped up and overcomplicated your solution. If the association is one to many you don't need to involve a join table.
Students
To create the association between a band and its members you need a m2m association through a join table:
class CreateBandMemberships < ActiveRecord::Migration[5.2]
def change
create_table :band_memberships do |t|
t.references :band, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :users, through: :band_memberships
end
class BandMembership < ApplicationRecord
belongs_to :band
belongs_to :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands, through: :band_memberships
end
You can improve the naming by providing the source option which tells Rails which association to use on the model its joining through.
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :members, through: :band_memberships, source: :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands_as_member, through: :band_memberships, source: :band
end

Rails - multiple relationships between two models

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.

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

ActiveRecord, polymorphic has_many, :through, :as

I am new to Rails and I'm having a superb deal of difficulty wrapping my head around what seems to be a very simple database structure, but I'm thrown by the idea that objects must belong to other objects.
In a site that I am creating, a User may create many Posts.
A Post may fit into any number of many different Topics.
So what Rails would like is that Posts belong to both Topics and to Users, while Topics also belong to Posts (many-to-many?). This makes some sense in my head, but then I can't imagine how to create a Topic independent of a Post (which is reasonable to the site's function).
Is this possible? Any help would be greatly appreciated - this is giving me a headache!
You can use has_and_belongs_to_many (HABTM) for this kind of relationship:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :topics
end
class Topic < ActiveRecord::Base
has_and_belongs_to_many :posts
end
in addition to the tables for users, posts and topics, you will need to create a migration for the HABTM relationship:
rails g migration create_posts_users post:references user:references
Note that the model names appear in alphabetical order in the table name
I don't think you are using polymorhpic relations here, just a regular many-to-many relation. Your model relations should look something like:
class User
has_many :posts
end
class Post
has_many :post_topics
has_many :topics, through: :post_topics
belongs_to :user
end
class Topic
has_many :post_topics
has_many :posts, through: :post_topics
end
class PostTopic
belongs_to :post
belongs_to :topic
end
This scenario is perfectly fine, a Topic can have may Posts, and a Post can have many Topics, which is a many-to-many relationship. In rails that would generally translate to has_and_belongs_to_many. Therefore you can define your models as follows:
class Post < ActiveRecord::Base
has_and_belongs_to_many :topics
end
class Topic < ActiveRecord::Base
has_and_belongs_to_many :posts
end
The corresponding generated Migration and database tables will look like this:
class CreatePotsAndTopics < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.timestamps
end
create_table :topics do |t|
t.string :name
t.timestamps
end
create_table :posts_topics, id: false do |t|
t.belongs_to :post
t.belongs_to :topic
end
end
end
As you can see both Topic and Post tables are standalone tables with no reference to any other table, which means they can be treated on their own. they way they get linked is through the posts_topics join table. Which enables you to do have access to #post.topics and #topic.posts
If you are uncomfortable with such a scenarios pick up a good rails book or do an online tutorial or course which walk you through creating a full rails application.

has_many and belongs_to of the same model

I am pretty new to rails. I am trying to figure out the most efficient way to create a relationship between two models that states:
A user can "favorite" many songs
A song has an owner.
This is what I am thinking of doing. Does it make sense ?
class User < ActiveRecord::Base
has_many :songs #songs this user has favorited
end
class Song < ActiveRecord::Base
belongs_to :user #the user whom submitted this song
end
My concern about this method is that I'm unsure about the efficiency of doing query on every song in the database just to figure out which songs a particular user owns. Is there a different way I should be thinking about this ?
By the way, is there a method by which I can call the attribute something different than it's model name. So rather than User.find(1).songs[0] I could say User.find(1).favorites[0] even though the model is still a "Song".
You'll need 2 separate relationships between the User and Song models. Namely, you'll need an 'owner' relationship and a 'favorite' relationship. The 'owner' relationship can be a simple has_many/belongs_to as you have it now. The 'favorite' relationship is many-to-many and will need a join table used either as a habtm table or a first class model with a has_many through relationship as explained here.
The generallly recommended approach is to use has_many through as it gives you better control:
class User
has_many :songs # these are songs 'owned' by the user
has_many :user_favorite_songs
has_many :favorite_songs, :through => :user_favorite_songs # these are the favorites
end
class Song
belongs_to :user
has_many :user_favorite_songs
end
class UserFavoriteSong
belongs_to :user
belongs_to :favorite_song, :class_name => 'Song', :foreign_key => :song_id
end
This looks perfectly fine.
Rails associations try to be most efficient - don't prematurely optimize.
You can alias the association's name like so:
class User < ActiveRecord::Base
has_many :favorites, class_name: 'Song'
end
see the docs about associations.
Regarding performance anyway, you might want to have a look at the :inverse_of association option.
I haven't tested this code, but you'll need something like this.
class User < ActiveRecord::Base
has_and_belongs_to_many :favorites, :class_name => "Song" #user's favorited songs
end
class Song < ActiveRecord::Base
belongs_to :user #the user who submitted the song
has_and_belongs_to_many :user, :as => :favorite
end
And since multiple users can favorite a song, you'll need a 'join table'
CreateUsersFavorites < ActiveRecord::Migration
def up
create_table :users_favorites do |t|
t.references :user
t.references :favorite
end
create_index :users_favorites, :user_id
create_index :users_favorites, :favorite_id
end
def down
drop_table :users_favorites
end
end
Also, I highly recommend taking a look at the rails guide for active record relationships.

Resources