Many-to-many with Users and Groups and Items - ruby-on-rails

I'm having trouble trying to understand/wrap my brain around this. I'm trying to create a relationship that allows this:
User has_many Groups
Item has_many Groups
Groups has_many User
Groups has_many Items
So I think I need a Join-Table here but belongs this table to three models or do I need two Join-Tables?
class Group < ActiveRecord::Base
has_many :users
has_many :items
end
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Item < ActiveRecord::Base
has_and_belongs_to_many :groups
end
So what is the right migration here for my Group model?

These are two separate many-to-many associations. Each many-to-many to many association requires a join table.
For has_and_belongs_to_many you can generate the migrations with:
$ rails g migration CreateJoinTableGroupsUsers group user
$ rails g migration CreateJoinTableGroupsItems group item
However has_and_belongs_to_many is very limited and has_many through: is usually a better option.
One example of the limits of has_and_belongs_to_many is that you can't add any additional columns on the join table (metadata) and you can't query the join table directly. So you're skrewed if you want to keep track of stuff like when a user joined a group or who added an item to a group.
$ rails g model Membership user:belongs_to group:belongs_to
$ rails g model GroupItem group:belongs_to item:belongs_to
class User < ApplicationRecord
has_many :memberships
has_many :groups, through: :memberships
end
class Group < ApplicationRecord
has_many :memberships
has_many :group_items
has_many :users, through: :memberships
has_many :items, through: :group_items
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
class Item < ApplicationRecord
has_many :group_items
has_many :groups, though: :group_items
end
class GroupItem < ApplicationRecord
belongs_to :user
belongs_to :item
end

Related

Extracting data using rails query from a join table

I have users table, books table and books_users join table. In the users_controller.rb I am trying extract the users who have filtered_books. Please help me to resolve that problem.
user.rb
has_many :books_users, dependent: :destroy
has_and_belongs_to_many :books, join_table: :books_users
book.rb
has_and_belongs_to_many :users
books_user.rb
belongs_to :user
belongs_to :book
users_controller.rb
def filter_users
#filtered_books = Fiction.find(params[:ID]).books
#users = **I want only those users who have filtered_books**
end
has_and_belongs_to_many does not actually use a join model. What you are looking for is has_many through:
class User < ApplicationRecord
has_many :book_users
has_many :books, through: :book_users
end
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
end
class BookUser < ApplicationRecord
belongs_to :book
belongs_to :user
end
If you want to add categories to books you would do it by adding a Category model and another join table. Not by creating a Fiction model which will just create a crazy amount of code duplication if you want multiple categories.
class Book < ApplicationRecord
has_many :book_users
has_many :users, through: :book_users
has_many :book_categories
has_many :categories, through: :book_categories
end
class BookCategory < ApplicationRecord
belongs_to :book
belongs_to :category
end
class Category < ApplicationRecord
has_many :book_categories
has_many :books, through: :book_categories
end
If you want to query for users that follow a certain book you can do it by using an inner join with a condition on books:
User.joins(:books)
.where(books: { title: 'Lord Of The Rings' })
If you want to get books that have a certain category:
Book.joins(:categories)
.where(categories: { name: 'Fiction' })
Then for the grand finale - to query users with a relation to at least one book that's categorized with "Fiction" you would do:
User.joins(books: :categories)
.where(categories: { name: 'Fiction' })
# or if you have an id
User.joins(books: :categories)
.where(categories: { id: params[:category_id] })
You can also add an indirect association that lets you go straight from categories to users:
class Category < ApplicationRecord
# ...
has_many :users, though: :books
end
category = Category.includes(:users)
.find(params[:id])
users = category.users
See:
The has_many :through Association
Joining nested assocations.
Specifying Conditions on Joined Tables
From looking at the code i am assuming that Book model has fiction_id as well because of the has_many association shown in this line Fiction.find(params[:ID]).books. There could be two approaches achieve this. First one could be that you use #filtered_books variable and extract users from it like #filtered_books.collect {|b| b.users}.flatten to extract all the users. Second approach could be through associations using fiction_id which could be something like User.joins(:books).where(books: {id: #filtererd_books.pluck(:id)})

Rails: How to group by nested association?

I have a Follow model with user_id and track_id. The Track model has an artist_id field.
What I want to do is count which artists have the most followers, but since users follow "tracks" and not "artists", I need to figure out how to do a count through the tracks.
So, what I was thinking was to do some sort of group by on a nested association. i.e. Group the Follow records by "track -> artist_id", somehow.
Then I could count the number of users for each.
Is that even possible? Is there more info that would be useful here?
You need to use has_many :through to establish the Artist <-> Tracks <-> User relationship.
class Artist < ApplicationRecord
has_many :tracks
has_many :users, through: :tracks
end
class Follow < ApplicationRecord
belongs_to :user
belongs_to :track
end
class Track < ApplicationRecord
belongs_to :artist
has_and_belongs_to_many :users, join_table: :follows
end
class User < ApplicationRecord
has_and_belongs_to_many :tracks
has_many :artists, through: :tracks, join_table: :follows
end
Then Rails can take care of the joins between Artist and User.
Artist.includes(:users).group(:id).count("users.id")

Is has_many still necessary when has_many through exists?

I have what I feel like is a super simple question, but I can't find an answer anywhere!
Question:
If I previously had a has_many relationship like this: has_many :wikis, do I keep this relationship if later on I create a has_many through relationship like the following?
has_many :collaborators
has_many :wikis, through: :collaborators
This is all in my User model.
Background:
In my rails app, I have a User model and a Wiki model. I just gave users the ability to collaborate on private wikis so I migrated a Collaborator model and then came the step to create the has_many through relationships. I wasn't sure if I still needed has_many :wikis after putting has_many :wikis, through: :collaborators.
The reason I am confused is because Users should still be able to create wikis without collaborators and I'm not sure how the has_many through relationship works under the hood.
Originally I had only User and Wiki with a one-to-many relationship.
# User model
class User < ApplicationRecord
...
has_many :wikis # should I delete this?
has_many :collaborators
has_many :wikis, through: :collaborators
...
end
# Collaborator model
class Collaborator < ApplicationRecord
belongs_to :user
belongs_to :wiki
end
# Wiki model
class Wiki < ApplicationRecord
belongs_to :user
has_many :collaborators, dependent: :destroy
has_many :users, through: :collaborators
...
end
Is has_many still necessary when has_many through exists?
has_many not necessary when presence has_many through like your model
has_many :wikis # should I delete this?
has_many :collaborators
has_many :wikis, through: :collaborators
should I delete this?
Yes, you can delete this one, you don't need this as the same belongs_to
From The has_many Association
A has_many association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a belongs_to association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing authors and books, the author model could be declared like this:
From The has_many :through Association:
A has_many :through association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
You can work with only has_many association without has_many :through, but this is one-to-many, this not many-to-many
The has_many Association (without has_many :through) is one-to-many connection with another model
The has_many :through Association is up a many-to-many connection with another model
Update
Look, one physician may have many patients, on the other hand, one patient may have many physicians if you use has_many association without through for patient then this called one-to-many association, that means one physician has many patients, on the other hand, one patient belongs to one physician, and now association looks like this
class Physician < ApplicationRecord
has_many :patients
end
class Patient < ApplicationRecord
belongs_to :physician
end
Update 2
The has_many through the standard format your models after edited
# User model
class User < ApplicationRecord
...
has_many :collaborators
has_many :wikis, through: :collaborators
...
end
# Collaborator model
class Collaborator < ApplicationRecord
belongs_to :user
belongs_to :wiki
end
# Wiki model
class Wiki < ApplicationRecord
has_many :collaborators, dependent: :destroy
has_many :users, through: :collaborators
...
end

Rails naming convention for join tables to specialized tables

I get that if you have a posts and a categories table that the join table will be posts_categories. However you might have more than one type of category.
If we decide to create specialized category tables for each object type we would create a posts_categories table which would be a table of categories specifically for post objects. What would the many-to-many join table be called between posts and posts_categories?
If I were you, I would create the join table (categorizations) with additional column(s):
rails g model Categorization post:references category:references new_column:new_type ....
### models/post.rb
class Post < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
end
## models/categorization
class Categorization < ApplicationRecord
belongs_to :post
belongs_to :category
## You can add new columns as many as you want, just like other tables
end
# models/category.rb
class Category < ApplicationRecord
has_many :categorizations
has_many :posts, through: :categorizations
end
I'm not sure I can fully understand your question. But, if you want create a many-to-many relationship between Post and Category, you can try as bellow:
# post.rb
class Post < ApplicationRecord
has_many :post_categories
has_many :categories, through: :post_categories
end
and
#post_category.rb
class PostCategory < ApplicationRecord
belongs_to :post
belongs_to :category
end
and
# category.rb
class Category < ApplicationRecord
has_many :post_categories
has_many :posts, through: :post_categories
end
Hope it helps.

Multiple has_many through relationships Rails

I'm still learning how to use has_many and has_many through relationships effectly. I am currently building a system where I would like users to be able to access certain maps that they are added to.
The map model is what I need the user to be able to access if they are apart of a certain group.
class Map < ApplicationRecord
has_many :rows
has_many :mapgroups
has_many :groups, through: :mapgroups
end
Since a user can belong to many groups I have a has_many through relationship
class Usergroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
class User < ApplicationRecord
has_many :usergroups
has_many :groups, through: :usergroups
end
class Group < ApplicationRecord
has_many :usergroups
has_many :users, through: :usergroups
has_many :mapgroups
has_many :maps, through: :mapgroups
end
I thought about making a mapgroup model to take care of this but, so far, I am not so sure this is going to work.
class Mapgroup < ApplicationRecord
belongs_to :map
belongs_to :group
end
I am looking for a method to check to see what groups the user is apart of and then, based on those groups, give the user access to the corresponding maps. Am I on the right track with the relationships? How could I do this?
If you want to use MapGroup model only for keeping users an map connected (ModelGroup has only foreign keys on Group and Map) it's not the best approach. In this case it's better to opt for has_and_belongs_to_many assosiation. It will let you to assosiate Groups and Maps without creating useless model.
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model.
class Group < ApplicationRecord
...
has_and_belongs_to_many :maps
end
class Map < ApplicationRecord
...
has_and_belongs_to_many :groups
end

Resources