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

A user can teach many topics, and each topic can have many lessons.
A user can join lessons created by other users.
(1) is pretty simple and I implemented it as follow:
#user.rb
has_many :topics
has_many :lessons, through: :topic
#topic.rb
has_many :lessons
#lesson.rb
belongs_to :topic
belongs_to :user
(2) is a many-to-many relationships and I think a join table - let's call it matches (extra points to help me find a better name!) - is required.
If I add the following
#user.rb
.
.
has_many :lessons, through: :match
#lesson.rb
.
.
has_many :users, through: :match
#match.rb
belongs_to :lesson
belongs_to :user
I think I will get an error since Rails cannot get the difference between the two relationships while calling #user.lessons, for example.
What can be a correct approach?
p.s. I see there are many questions similar to this one but I was not able to find a proper solution to my problem.

I think you should name the join table user_lessons rather than match being a convention in Rails for join tables.
After renaming, I think you should rather have it like this:
user.rb
has_many :user_lessons
has_many :subscribed_lessons, through: :user_lessons, source: :lesson
Also note that your user
has_many :lessons, through: :topics #not :topic
Let me know if that works.

Related

Rails 4 has_many through naming

I'm having problems with a Rails 4 join table. I have quite a simple setup which is working elsewhere in my application using a non-conventionally named table for users, groups and usergroupmemberships. I'm trying to set it up this time using the proper conventional naming and it's just not working.
Models involved are User, ManagementGroup and ManagementGroupsUser
db tables: management_groups_user, management_groups, users
app/models/user.rb
Class User < ActiveRecord::Base
...
has_many :management_groups, through: management_groups_users
has_many :management_groups_users
....
app/models/management_group.rb
class ManagementGroup < ActiveRecord::Base
has_many :users, through: :management_groups_users
has_many :management_groups_users
app/models/management_groups_user.rb
class ManagementGroupsUser < ActiveRecord::Base
belongs_to :users
belongs_to :management_groups
The association appears to work from with #user.management_groups_users but nothing else. I'm fairly sure this is a problem with naming / plurality but I can't figure it out.
This is the model which joins the remaining models user.rb and management_group
#app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
Since we are going to use model above to access another model management_group then
#app/models/user.rb
has_many :user_management_groups #This should come first
has_many :management_groups, through: user_management_groups
Since we are going to use model above to access another user model then
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
Now should work
Do it this way.
app/models/user.rb
has_many :user_management_groups
has_many :management_groups, through: user_management_groups
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
I hope these associations will help you.
This is another way if you pass foreign key and class name.
app/models/user.rb
has_many :user_management_groups, :foreign_key => "key", :class_name => "ClassName"
has_many :management_groups, through: user_management_groups, :foreign_key => "key", :class_name => "ClassName"
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user, class_name: "ClassName"
belongs_to :management_group, class_name: "ClassName"
This is another way around.
It's important to realize there is a convention rails uses for HABTM and has many through. HABTM does not have a model, so it needs to infer the table name which, as others point out, is both plural and alphabetical order.
If you are doing has many through and have a model, the convention is that it wants singular first word, plural second. See examples
User has and belongs to many groups.
HABTM: table name should be groups_users
Has Many Through: table name should be user_groups (flip order is more intuitive)
Model for the latter would be UserGroup. Has many through would specify it as through: :user_groups

RAILS: Relations (Select all answers from all topics)

category.rb
has_many :topics
topic.rb
belongs_to :category
has_many :answers
answer.rb
belongs_to :topic
Question:
How can i preform queries like Category.first.topics.answers.count
Use a has_many :through relation:
# Category.rb
has_many :topics
has_many :answers, through: :topics
Now you can access all answers from all topics like so:
Category.first.answers.count
if you are set on your schema configuration (i.e. not using a has_many :through), you'd want to start with Answers and utilize a couple of joins to get to Category
Answers.joins(topic: :category).where(categories: { id: category_id })
here we're joining on a nested association, and then using a where clause to filter out by the category_id
note: i think this is the right syntax, but you may need to fiddle around with the plurality of topic and category there

rails has_many through same destination

I have a User, Post and Like model. A user has many posts, and a user can like posts.
has_many :user_posts
has_many :users, through: :user_posts
has_many :likes
has_many :users, through: :likes
On the other side my rspecs are:
it { should have_many(:users).through(:user_posts) }
it { should have_many(:user_posts) }
it { should have_many(:likes) }
it { should have_many(:user_likes).through(:likes) }
This doesn't seem to go down very well, as rspec complains there already is a relationship through user:
Expected Post to have a has_many association called users (, Expected users to have users through likes, but got it through user_posts)
I tried all sort of combinations with class_name without much success. How should I define these relationships?
I'm making some inferences based on the code above. Let me explain the choices I made...
Your terminology around "user posts" is ambiguous to me, because it's unclear if a "user post" is something a user liked or something a user authored.
In my code, has_many :posts is referring to authorship and does not demand a join table because a post only has one author. On the other hand, has_many :liked_posts needs to go through: a join table (Likes) since it's a many-to-many relationship.
The only other thing that I've done differently is the use of the source: option in the association. Since a Like has no idea what either liked_posts or users_who_liked are, you need to tell it which of it's own associations to follow to get the records.
You can read more about Associations in the Rails API Documentation.
class User < ActiveRecord:Base
has_many :posts
has_many :likes
has_many :liked_posts, through: :likes, source: :post
end
class Like < ActiveRecord:Base
belongs_to :user
belongs_to: post
end
class Post < ActiveRecord:Base
has_many :likes
has_many :users_who_liked, through: likes, source: :user
end
Finally, as feedback on your question as-written: Tom's right in his comment above. It's not clear which classes those associations are a part of. Does a User have Users twice? Confusing. If I mis-inferred any of your business rules, let me know and I can revise my thoughts.

Does Rails have a built-in way to create triple-join objects? Do I create AR models for them?

My database stores data about a TV show. I want to store information about who worked on what episodes, and in what role.
Each crew member works on many episodes; each episode has many crew members; and crew members can work on the same episode in different roles (they can be writer and director on the same episode, for example).
I'm new to thinking about ActiveRecord, so I'm a bit confused about how to properly represent these relationships in Rails. I would really love it if someone could tip me off as to the 'Rails way' to do this.
My instinct is to create a triple-join (or double-join; three fields, anyway) table. I have three models: CrewMember (has a name), Episode (has a title, airdate, etc), and Role (has a job title, like Director, Writer or Actor). I could create a table with three fields: CrewMember_ID, Episode_ID, and Role_ID. Each row would then describe someone working on a specific episode in a specific capacity.
So the relationship would appear to be:
CrewMember has_many :episodes
Episode has_many :crew_members
CrewMember has_many :roles
Role has_many :crew_members.
Here's where I get confused. Do I create a new model for that table? And if I do, how exactly do I create instances of that object? I understand relationships like Author has_many :books where I can create a book with Author.books.new, but this kind of has-many has-many has-many thing stumps me a bit.
I can do what I want with SQL, I'm just having trouble approaching it from an ORM/ActiveRecord angle.
you can work around this issue by adding a model that collects the three models
rails g model big_model model1:references model2:references model3:references
Using "Jobs" as GreenTriangle suggested
class CrewMember < ActiveRecord::Base
has_many :jobs, dependent: :destroy, inverse_of: :crew_member
has_many :episodes, through: :jobs
has_many :roles, through: :jobs
end
class Episode < ActiveRecord::Base
has_many :jobs, dependent: :destroy, invserse_of: :episode
has_many :crew_members, through: :jobs
has_many :roles, through: :jobs
end
class Role < ActiveRecord::Base
has_many :jobs, dependent: :destroy, inverse_of: :role
end
class Job < ActiveRecord::Base
belongs_to :episode
belongs_to :role
belongs_to :crew_member
validates_presence_of :episode, :role, :crew_member
end

Newbie here: conflict associations to the same table. has_many:invoices, has_many :invoices, through: user_invoice_viewers

this seems pretty basic stuff here, but actually i'm finding it a bit harsh to define this scenario with Rails...
Perhaps any of you can provide some guidance?
So I have three Tables, Users, Invoices, and User_Invoice_Viewers (these basically map users that have viewer access to an invoice)
Now my models :
User.rb :
has_many :invoices
has_many :user_invoice_viewers
has_many :invoices, through :user_invoice_viewers
Invoice.rb
belongs_to user_invoice_viewers
belongs_to :user
User_Invoice_Viewers.rb
belongs_to :users
belongs_to :invoices
Now this just seems wrong... I repeat has_many :invoices on User model, so i expect conflict when executing : User.invoices ...
What would be the best solution for this? I had thought of putting it all on a user_invoice table, but since i expect to have more owners than viewers, for performance reasons, i decided to build a direct dependency between invoice and its owner...
Thanks
I would consider using the :class_name option on the association, so that the two relationships are named differently. Something like this:
class User < ActiveRecord::Base
has_many :invoices
has_many :user_invoice_viewers
has_many :viewable_invoices, through :user_invoice_viewers, :class_name => "Invoice"
...
end

Resources