Rails group model that can own groups that can own users - ruby-on-rails

I'm trying to figure out how to model groups and users for my app, and I'm having trouble figuring out the proper way to do it.
I have users, and I have admins. I don't have a model for groups yet. I'd like a group to be able to own multiple users, and users could have multiple groups. To complicate things further, groups could have multiple users through other groups.
class Group
belongs_to :admin
has_many :users
has_many :users, through: :groups
end
class User
belongs_to_many :groups
end
class Admin
has_many :groups
end
I think I need a membership table. Then each user would be connected to a group through a membership. But then how could I connect groups --> groups --> users?
class Membership
?
end

A couple pointers here.
belongs_to_many is not a thing, at least as of the latest version I'm familiar with. Maybe you mean has_and_belongs_to_many or probably has_many :groups, through: :memberships. I'm going to use this latter form below.
In order to use the through :groups option in the Group class, you first need to define a :groups association. If all you are trying to define it as is any other Group that has a User that is a member of both that and this Group, then you can do this easily:
class Membership
belongs_to :user
belongs_to :group
end
class User
has_many :memberships
has_many :groups, through: :memberships
end
class Group
has_many :memberships
has_many :users, through: :memberships
has_many :groups, through: :users
end
You might want to rename this association to be a little less confusing:
has_many :connected_groups, through: :users, source: :groups
Now you want to define another users association on Group that represents the users of any connected group. You need a different name, you cannot meaningfully define two associations of the same name (how would you access one vs. the other?). So I'd suggest using the names connected_groups and connected_users, or something similar:
class Group
has_many :memberships
has_many :users, through: :memberships
has_many :connected_groups, through: :users, source: :groups
has_many :connected_users, through: connected_groups, source: :users
end

Related

Rails ActiveRecord Associations that include "owner" role

I’m creating a chore tracker app as my first Rails project and I’m wondering if the associations I've created make sense or could be improved. Here are the details of the app:
A user makes a chore list and becomes the “owner”(i.e. “admin”) of
that list.
The owner can create/edit chores for the list. They can also “approve" other users to complete tasks on the list. These users can ONLY complete tasks.
The owner, along with the usual admin abilities, can also complete
tasks on a list that they own.
Owners can own multiple lists. Users can be approved to complete
tasks on multiple lists.
And here are the relationships I’ve roughed out that I’m looking for feedback on:
(Model)User
has_many :lists
has_many :owners, class_name: “List”, foreign_key: “list_id"
has_many :chores, through: :lists
(Model)List
has_many :users
has_many :chores
belongs_to :owner, class_name: “User”, foreign_key: “owner_id"
(Model)User_List
belongs_to :user
belongs_to :list
(Model)Chore
belongs_to :list
has_many :users, through: :lists
Any red flags? Thank you in advance!
I would do something like this:
# user.rb
class User
has_many :lists_users
has_many :lists, through: :lists_users
has_many :chores, through :lists
end
# list.rb
class List
has_many :lists_users
has_many :users, through: :lists_users
has_many :chores
end
# lists_user.rb
class ListsUser
belongs_to :user
belongs_to :list
# field (Representational, add it in database)
:user_role
- member (can view)
- collaborator (can mark complete)
- owner (can do everything)
end
# chore.rb
class Chore
belongs_to :list
end
NOTE: I changed the association model to ListsUser as per rails naming conventions.
Please let me know if i missed something.

Rails has_many and has_many through

I am confused on how to go about approaching this. I am connecting users and groups through the membership model, but I also want users to be able to create new groups. Clearly a group must then belong to a user, but the groups also belong to users through the memberships table.
I have this in my user.rb file, but I feel it is wrong. Do I remove the first one and just have the through one? How do I work in the creator of the group in that case?
class User < ApplicationRecord
has_many :groups
has_many :groups, through: :memberships
end
In other words, the user is a member of many groups, but also the creator of many groups. The memberships table only has two columns (group id and user id). The user id in this column is used to store users who are members of that group. I am stuck on what to do about the user who created the group.
You should have two relationships between Groups and Users. One reflecting the fact that a user created a group, and one that a user belongs to a group. You can reflect this idea by configuring the naming of your relationships. You will have to add a user_id field to your Groups table as well.
class User < ApplicationRecord
has_many :created_groups, class_name: "Group"
has_many :memberships
has_many :groups, through: :memberships
end
class Group < ApplicationRecord
belongs_to :creator, class_name: "User"
has_many :memberships
has_many :subscribers, through: :memberships, source: :user
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end

Rails model has_many :through relationship

User, Car, Part, Tire.
User has many Cars. Car has many Parts. Part has many Tires.
In user.rb, I have
has_many :tires, through: :cars
However, it seems like this is not a correct way of doing it since there is another model Part in the middle.
How should I successfully do to make has_many relationships between User and Tire?
You just need to add the connection in the middle.
# User.rb
has_many :cars
has_many :parts, through: :cars
has_many :tires, through: :parts

Inject attribute from association table into the associated record?

I have the following model in my Rails app:
class Course < ActiveRecord::Base
has_many :memberships
has_many :members, through: :memberships, class_name: 'User'
end
While course.members successfully returns the course's members, I don't get access to the Membership model which has a role attribute.
How do I find the user role without having to find the Membership given my Course and User? Can I inject the role attribute to User somehow in the context association?
User model:
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :courses, through: :memberships
end
Here's a solution, not the most beautiful and idiomatic though
class Course < ActiveRecord::Base
has_many :memberships
def members
User.joins(:memberships).where(id: memberships.ids).select('users.*, memberships.role')
end
end
A better approach, suggested through the comments:
has_many :members, -> { select('users.*, memberships.role') }, class_name: 'User', through: :memberships, source: :user
I had the exact same problem, and couldn't find any way to fetch the intermediate association without explicitly finding it. It's not that ugly, though:
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :courses, through: :memberships
def role_in(course)
memberships.find_by!(course: course).role
end
end
user.role_in(course)
# => "teacher"
I think dynamically storing the contextual role depending on what Course is used is not a good idea. It feels hacky to me because the attribute value becomes set implicitly rather than by explicit execution. A User would appear different depending on where and how it's instantiated, despite representing a row in the database.
I would instead implement something like below:
class User
def course_role(course)
memberships.find_by_course(course).role
end
end
class Course
def user_role(user)
members.find_by_user(user).role
end
end
This way, it's explicit that calling methods on either the User or Course to get the role depends on the respective memberships they are associated with.

Two has_many_through Relationships To Same Model

I have a Contributor Model and a Resource Model. In a simple world I would have the following setup:
class Resource
has_many :authorships
has_many :contributors, through: :authorships
end
class Contributor
has_many :authorships
has_many :resources, through: :authorships
end
However, my requirements have changed. A contributor can now either be an editor of a resource or an author of a resource. A Contributor can be an Editor of one resource and the Author of another. So it seems I have two ways to handle this requirement:
Add some kind of is_editor? attribute to my Authorships join model and effectively annotate each relationship.
Create a second join model – Editorship:
class Resource
has_many :authorships
has_many :editorships
has_many :contributors, through: :authorships
has_many :contributors, through: :editorships
end
class Contributor
has_many :authorships
has_many :editorships
has_many :resources, through: :authorships
has_many :resources, through: :editorships
end
Which is the most sensible approach, or is there another approach I'm missing?
Given your clarification, I would use the first approach, but instead of just introducing an is_editor boolean for Authorship, you might want to the generalize the language and the concept and instead use ResourceContributorship with a contributor_type field which could now be either :author or :editor, but could be extended in the future.

Resources