How to differentiate similar has_many :through associations in Rails? - ruby-on-rails

I'll start off with my models:
class Project < ApplicationRecord
has_many :permissions
has_many :wallets, through: :permissions
has_many :follows
has_many :wallets, through: :follows
end
class Permission < ApplicationRecord
belongs_to :project
belongs_to :wallet
end
class Follow < ApplicationRecord
belongs_to :project
belongs_to :wallet
end
class Wallet < ApplicationRecord
has_many :permissions
has_many :projects, through: :permissions
has_many :follows
has_many :projects, through: :follows
end
As you can see, Permission and Follow are both through associations for Projects and Wallets.
They serve different purposes (Permission gives Wallets access to manage Projects while Follow lets Wallets "follow" projects for updates).
So how can I differentiate them? For example, if I do Wallet.find(1).projects, it defaults to using the "Follow" model...though in some scenarios I'd want it to actually use the "Permission" model.

Believe you'd find it will default to the has_many :projects that is defined last.
Need to give the associations different names, which will require something like ...
class Wallet < ApplicationRecord
has_many :permissions
has_many :projects, through: :permissions
has_many :follows
has_many :follow_projects, through: :follows, source: :project
end

Related

Over-writing ActiveRecord association macros

I'm building an API for an app that allows users to rate and favorite different ski mountains. At the moment, my associations are set up as follows:
class Mountain < ApplicationRecord
has_many :ratings
has_many :users, through: :ratings
has_many :favorites
has_many :users, through: :favorites
end
class Favorite < ApplicationRecord
belongs_to :mountain
belongs_to :user
end
class Rating < ApplicationRecord
belongs_to :mountain
belongs_to :user
end
class User < ApplicationRecord
has_many :ratings
has_many :mountains, through: :ratings
has_many :favorites
has_many :mountains, through: :favorites
end
The problem here is that User has many mountains through both ratings and favorites, but calling User.first.mountains will only give me mountains through ratings, since that's listed first. I'll need to utilize both in the app - any guidance here? Same deal for mountains - has many users through both ratings and favorites.
Thanks in advance for your help!

Rails Complex Model Association, Shared Document Between Users and Teams

I have a complex model association in mind, and was wondering how I could accomplish it. This is what i want to accomplish.
I have a User and a Document model
A User can create documents. He is now the document admin.
He can add other users to his document, and give them permissions such as Editor, Viewer, Admin
He can also make a team, a group of users, and add multiple teams to his document. Each user on a team that the User has added to his document will also have permissions. A user can belong to many teams.
I am a little bit confused about the associations I will have to setup. This is the code I have so far, which has not incorporated the team aspect:
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
end
class Participation < ApplicationRecord
belongs_to :user
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
I would recommend introducing a Team and TeamMembership models in a similary way to existing models. Also change the belongs_to association on Participation from user to a polymorphic participant.
class Team < ApplicationRecord
has_many :team_memberships
has_many :users, through: :team_memberships
has_many :participations, as: :participant
end
class TeamMembership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :team_memberships
has_many :teams, through: :team_memberships
has_many :participations, as: :participant
end
class Participation < ApplicationRecord
belongs_to :participant, polymorphic: true
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
class Document < ApplicationRecord
has_many :participations
# if desired create a users method to conveniently get a list of users with access to the document
def users
#users ||= participations.flat_map do |participation|
case participation.partipant
when User
[participation.participant]
when Team
participation.participant.users
end
end
end
end
I would only add has_many :through associations as you discover a benefit/need to having them. That will reduce complexity of maintaining them unless you have specific use case for them. In the case of User having a teams association, it's pretty obvious that you'll be likely to want to get the teams that the user is a part of and since there's no specific information in the TeamMembership object that you are likely to need in that determination, it's a good has_many :through to have.
EDIT: Added Document model.
Since you already have a participation model, you can use that as the join model between users and teams. Since a user can belong to multiple teams, and a document can have multiple teams, you can use a has_many through relationship between teams and documents. We'll call it the DocumentTeam model.
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
has_many :teams, through: :participations
end
class Participation < ApplicationRecord
belongs_to :document
belongs_to :user
belongs_to :team, optional: true
enum role: [ :admin, :editor, :viewer ]
end
class Team < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :document, through: :document_teams
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :teams, through: :document_teams
end
class DocumentTeam < ApplicationRecord
belongs_to :document
belongs_to :team
end

How to do associations with two user types and one model with has and belongs to many relationships in rails?

I've been trying to solve this problem for a while now. Looked here, but it is not exactly what I need.
I have three models: User, Group, GroupMembership. User can be a teacher and a student, so user has different Roles through UserRoles table. Groups can have multiple teachers. What I want is something like this:
Role.rb
has_and_belongs_to_many :users #this works fine
User.rb
has_and_belongs_to_many :roles # this works fine
has_many :group_memberships
has_many :groups, through: :group_memberships
has_many :teachers, through: :group_memberships
has_many :students, through: :group_memberships
Group.rb
has_many :students, through: :group_memberships
has_many :teachers, through: :group_memberships
GroupMembership.rb
belongs_to :student
belongs_to :teacher
belongs_to :group
User with roles works fine, there is no problem with the different roles for user. The problem is with group memberships. The above is just something that I want to work, but in some cases I need to supply source or class name and I am not sure what exactly to do. And what kind of migrations should I create?
I'll delete this if required; I'm pretty sure you'd benefit from using source: in your User and Group models:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :roles # this works fine
has_many :group_memberships
has_many :groups, through: :group_memberships
has_many :teachers, through: :group_memberships, source: :teacher
has_many :students, through: :group_memberships, source: :student
end
#app/models/group_membership.rb
class GroupMembership < ActiveRecord::Base
belongs_to :student, class_name: "User"
belongs_to :teacher, class_name: "User"
belongs_to :group
end
#app/models/group.rb
class Group < ActiveRecord::Base
...
has_many :students, through: :group_memberships, source: :student
has_many :teachers, through: :group_memberships, source: :teacher
end
This is not tested & I don't have super experience with source so it could be wrong.
The modeling is a bit off - GroupMembership should be acting as a many to many join model. That means that each user and group needs a GroupMembership to tie them together.
Also you would alter the relation between users and roles to be a has_many through: so that you can have metadata in the join model.
class User
has_many :user_roles
has_many :roles, through: :user_roles
has_many :group_memberships
has_many :groups, through: :group_memberships
end
class Role
has_many :user_roles
has_many :users, through: :user_roles
end
class UserRole
belongs_to :user
belongs_to :role
end
class Group
has_many :group_memberships
has_many :users, through: :group_memberships
end
class GroupMembership
belongs_to :user
belongs_to :group
end
To query for group users with a specific role you could now use:
teachers = group.users.where(users: { roles: [Role.find_by(name: 'teacher')])
To make this a bit more convenient we can setup special relations with conditions:
class Group
has_many :group_memberships
has_many :users, through: :group_memberships
has_many :teachers, ->{ where(users: { roles: [Role.find_by(name: 'teacher')] }) } through: :group_memberships, source: :user
end
Note that we need to specify source as it tells AR that "teacher" is in fact group_memberships.user.

Ruby: specify has_many :through association when several has_many :through associations are given

i have a simple data set-up with a model for Users and a model for Tasks.
Between these two models i have two has_many :through associations: Fellowships and Assignements. In total i want to specify for a task several followers and several assignees.
I now want to display for a specific task all assignees and all followers.
If there would only be one association I simply could do #task.users. As i have two associations i want to specify by which association i want to get all users.
See my code:
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, through: :assignments
has_many :fellowships
has_many :tasks, through: :fellowships
end
class Task < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
has_many :fellowships
has_many :users, through: :fellowships
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Fellowship < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
Let's assume i have a task as
#task = Task.first
I now want to have all assignees and all followers with something like
#assignees = #task.users "over association assignment"
#followers = #task.users "over association followship"
but i don't know how to do this.
Thanks for the help!
You can write in following way.
has_many :assignment_tasks ,through: :assignments ,source: :task
has_many :fellowship_tasks, through: :fellowships, source: :task

has_many of the same table through several others

What am trying to do is:
i have a User model and i have a Task model
Task has 2 types of users Owners and Supervisors all of them are users !
so what i have so far is:
Task Model
class Task < ActiveRecord::Base
has_many :task_owners, dependent: :destroy
has_many :task_supervisors, dependent: :destroy
has_many :users, through: :task_owners
has_many :users, through: :task_supervisors
end
TaskSupervisor Model
class TaskSupervisor < ActiveRecord::Base
belongs_to :task
belongs_to :user
end
TaskOwner Model
class TaskOwner < ActiveRecord::Base
belongs_to :task
belongs_to :user
end
and finally the User Model
class User < ActiveRecord::Base
has_many :task_owners
has_many :task_supervisors
has_many :tasks, through: :task_owners
has_many :tasks, through: :task_supervisors
end
now as you can imagine ... my problem is when i get a task and retrieve the users i only get one of my associations ... what i need is a way to change the getters name or identify them some how basically to be able to say something like
task.owners
task.supervisors
class Task < ActiveRecord::Base
has_many :task_owners, dependent: :destroy
has_many :task_supervisors, dependent: :destroy
has_many :owners, through: :task_owners, source: :users
has_many :supervisors, through: :task_supervisors, source: :users
end
You should be able to do this.
Then you should get your task.owners and task.supervisors
Edit:
You will need to change your user model to
class User < ActiveRecord::Base
has_many :task_owners
has_many :task_supervisors
has_many :owned_tasks, through: :task_owners, source: :tasks
has_many :supervised_tasks, through: :task_supervisors, source: :tasks
end

Resources