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
Related
I have the following
class User < ApplicationRecord
has_one :physician
end
and
class Physician < ApplicationRecord
belongs_to :user
end
Since a user can only have one physician, I was surprised that multiple physician records can be created with the same user id.
Why is this? And how to I prevent it?
A belongs_to association does not in any way guarantee that the values are unique. It only stipulates that the association is stored in a foreign key on this models table and thus can only have a single value.
has_one doesn't actually provide any guarantees either. It just specifies that this table is referred to on the other table. If there are multiple matching rows on the other table it will chose the last.
If you want to enforce uniqueness per table you need a validation:
class Physician < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id
end
And a database index to actually guarantee uniqueness on the database level:
class AddUniqueIndexToPhysicians < ActiveRecord::Migration[6.0]
def change
add_index :physicians, :user_id, unique: true
end
end
Physician probably shouldn't have a user_id associated to it:
class User
belongs_to :physician # has a physician_id column
end
class Physician
has_many :users # has no mention of user_id in its schema
end
belongs_to is required by default for Rails 5 and later. If users can onboard without a physician and you need to disable this:
class User
belongs_to :physician, optional: true
end
And then later, validate the presence of physician only after some condition.
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
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.
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
We are trying to add multiple favoritable objects, where a user can favorite many different objects, but are not sure how to make it work.
Here is the Favorite model:
class Favorite < ActiveRecord::Base
# belongs_to :imageable, polymorphic: true
belongs_to :user
belongs_to :category
belongs_to :business
belongs_to :ad_channel
belongs_to :location
belongs_to :offer
end
The user model:
class User < ActiveRecord::Base
has_many :favorites, as: :favoritable
end
And one example model of something that can be favorited:
class Category < ActiveRecord::Base
has_many :sub_categories
has_many :ad_channels
has_many :offers
belongs_to :favoritable, polymorphic: true
end
I'm not sure if this is set up properly so that would be the first thing we need some feedback on.
Secondly how do we "favorite" something for a user?
This is what we've tried so far unsuccessfully:
#user.favorites << Category.find(1)
EDIT: Also will this need a favorites database table to record things? This is a pretty new concept for us.
Model Relationships
Your Favorite model looks like this:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, polymorphic: true
belongs_to :user, inverse_of: :favorites
end
Then, your User model will look like this:
class User < ActiveRecord::Base
has_many :favorites, inverse_of: :user
end
Then, the models that can be favorited should look like this:
class Category < ActiveRecord::Base
has_many :favorites, as: :favoritable
end
Yes, you will need a favorites table in your database.
Favoriting Items
So, this should allow you to do stuff like:
#user.favorites << Favorite.new(favoritabe: Category.find(1)) # add favorite for user
Just keep in mind that you need to add instances of Favorite to #user.favorites, not instances of favoritable models. The favoritable model is an attribute on the instance of Favorite.
But, really, the preferred way to do this in Rails is like so:
#user.favorites.build(favoritable: Category.find(1))
Finding Favorites of a Certain Kind
If you wanted to find only favorites of a certain type, you could do something like:
#user.favorites.where(favoritable_type: 'Category') # get favorited categories for user
Favorite.where(favoritable_type: 'Category') # get all favorited categories
If you're going to do this often, I think adding scopes to a polymorphic model is pretty clean:
class Favorite < ActiveRecord::Base
scope :categories, -> { where(favoritable_type: 'Category') }
end
This allows you to do:
#user.favorites.categories
Which is gets you the same result as #user.favorites.where(favoritable_type: 'Category') from above.
Allowing Users to Favorite an Item Only Once
I'm guessing that you might also want to allow users to only be able to favorite an item once, so that you don't get, for example, duplicate categories when you do something like, #user.favorites.categories. Here's how you would set that up on your Favorite model:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, polymorphic: true
belongs_to :user, inverse_of: :favorites
validates :user_id, uniqueness: {
scope: [:favoritable_id, :favoritable_type],
message: 'can only favorite an item once'
}
end
This makes it so that a favorite must have a unique combination of user_id, favoritable_id, and favoritable_type. Since favoritable_id and favoritable_type are combined to get the favoritable item, this is equivalent to specifying that all favorites must have a unique combination of user_id and favoritable. Or, in plain English, "a user can only favorite something once".
Adding Indexes to the Database
For performance reasons, when you have polymorphic relationships, you want database indexes on the _id and _type columns. If you use the Rails generator with the polymorphic option, I think it will do this for you. Otherwise, you'll have to do it yourself.
If you're not sure, take a look your db/schema.rb file. If you have the following after the schema for your favorites table, then you're all set:
add_index :favorites, :favoritable_id
add_index :favorites, :favoritable_type
Otherwise, put those lines in a migration and run that bad boy.
While you're at it, you should make sure that all of your foreign keys also have indexes. In this example, that would be be the user_id column on the favorites table. Again, if you're not sure, check your schema file.
And one last thing about database indexes: if you are going to add the uniqueness constraint as outlined in the section above, you should add a unique index to your database. You would do that like this:
add_index :favorites, [:favoritable_id, :favoritable_type], unique: true
This will enforce the uniqueness constraint at the database level, which is necessary if you have multiple app servers all using a single database, and generally just the right way to do things.