I have a model called Organization that has many teams and has many collaborations. A Collaboration also has many teams. So the team model has a polymorphic association with Organization and Collaboration.
What I would like to do is organization.teams and have that reflect all teams from the organization and all teams from the collaborations a part of the organization.
Tables
*organizations*
id
user_id
title
...
*collaborations*
id
organization_id
title
...
*teams*
id
teamable_id
teamable_type
title
...
Models
class Organization < ApplicationRecord
has_many :orgaization_teams, as: :teamable, class_name: "Team"
has_many :collaboration_teams, through: :collaborations, source: :teams, class_name: "Team"
end
class Collaboration < ApplicationRecord
belongs_to :organization
has_many :teams, as: :teamable
end
class Team < ApplicationRecord
belongs_to :teamable, polymorphic: true
belongs_to :organization, -> { joins(:teams).where(teams: {teamable_type: 'Organization'}) }, foreign_key: 'teamable_id'
belongs_to :collaboration, -> { joins(:teams).where(teams: {teamable_type: 'Collaboration'}) }, foreign_key: 'teamable_id'
end
Attempts
Attempt #1
class Organization < ApplicationRecord
has_many :teams, -> { joins(:organization, :collaboration) }, source: :teamable
end
Result:
SystemStackError (stack level too deep)
Attempt #2
class Organization < ApplicationRecord
def teams
orgaization_teams.or(collaboration_teams)
end
end
Result:
ArgumentError (Relation passed to #or must be structurally compatible. Incompatible values: [:joins])
Potential Solution
I'm considering separating the polymorphic association on Team to organization_id and collaboration_id
So the new table would look like this:
*teams*
id
organization_id
collaboration_id
title
...
I would go with two seperate foreign keys instead:
class Organization < ApplicationRecord
has_many :teams
has_many :collaborations
has_many :collaboration_teams,
through: :collaborations,
source: :team
end
class Collaboration < ApplicationRecord
belongs_to :organization
has_many :teams
end
class Team < ApplicationRecord
belongs_to :team, optional: true
belongs_to :organization, optional: true
end
If you want to get teams that directly belong to an or org or a its collaborations you want:
Team.where(
organization_id: org.id
).or(
Team.where(
collaboration_id: org.collaborations
)
)
I don't think this can actually be written as an association as its too structurally complex.
Related
I have 3 model User Project Bug. I want to create many to many relation with through. I create the relation in model i don't know it is correct or not, user have user type column which is enum type user type contain developer, manager , QA
user.user_type.manager belong to many project it has one to many relation
user.user_type.developer has many project and many project belong to developer. it has many to many realtion
project has many bugs and bugs belong to project
developer has many bugs and many bugs belong to developer
bug model
class Bug < ApplicationRecord
belongs_to :project
has_many :developers, -> { where user_type: :Developer }, class_name: 'User', through: :project, source: :bugs
end
project model
class Project < ApplicationRecord
has_many :bugs, dependent: :delete_all
has_many :developers, -> { where user_type: :Developer }, class_name: 'User', through: :users, source: :project
has_many :users //it belong to manager_id
end
user model
class User < ApplicationRecord
enum user_type: %i[Manager Developer QA]
has_many :projects
has_many :bugs
end
developer_bug model
class DevelopersBug < ApplicationRecord
has_many :bugs
has_many :users
end
project_developer model
class ProjectsDeveloper < ApplicationRecord
has_many :projects
has_many :users
end
This
has_many :developers, -> { where user_type: :Developer },
class_name: 'User',
through: :users,
source: :project
is not what you think it is. It means something on the line of:
I already have an association 'users'. The users have an association 'project'.
Please configure an association that makes both JOINs and gives me the list of projects associated to the associated users.
This association will be named "developers" and be of objects of class "User".
You can see how these instructions are inconsistent. This
has_many :projects, through: :users, source: :project
will define a list of associated projects, by jumping over users.
On the other side, this:
has_many :developers, -> { where user_type: :Developer }, class_name: 'User'
will define a direct has-many association with a subset of all the users.
Given your description, your data model seems wrong, maybe this will be better:
class User < ApplicationRecord
has_many :managed_projects, inverse_of: :manager, class_name: 'Project'
has_and_belongs_to_many :projects
has_many :bugs
end
class Project < ApplicationRecord
belongs_to :manager, class_name: 'User', inverse_of: :managed_projects
has_and_belongs_to_many :users
has_many :bugs
end
class Bug < ApplicationRecord
belongs_to :user
belongs_to :project
end
Your schema should include the three tables, and an additional many-to-many join table projects_users that holds foreign keys to both users and projects.
As rewritten has already pointed in his excellent answer out your data model is flawed. What you want instead is a join table which joins the users and projects:
class User < ApplicationRecord
has_many :project_roles
has_many :projects, through: :project_roles
end
class ProjectRole < ApplicationRecord
belongs_to :user
belongs_to :project
end
class Project < ApplicationRecord
has_many :users
has_many :projects, through: :project_roles
end
If you then want to give the user specific roles in a project you would add the enum to the join table and this is where it starts to get hairy so bear with me here:
class ProjectRole < ApplicationRecord
enum roles: [:manager, :developer, :qa]
belongs_to :user
belongs_to :project
end
class User < ApplicationRecord
has_many :project_roles
has_many :projects, through: :project_roles
has_many :project_roles_as_manager,
-> { manager }, # short for `where(role: :manager)`
class_name: 'ProjectRole'
has_many :projects_as_manager,
class_name: 'Project',
through: :project_roles_as_manager,
source: :project
has_many :project_roles_as_developer,
-> { developer },
class_name: 'ProjectRole'
has_many :projects_as_developer,
class_name: 'Project',
through: :project_roles_as_developer,
source: :project
# ...
end
This defines associations with a default scope and then joins through that association. You would then do the same thing on the other end of the assocation:
class Project < ApplicationRecord
has_many :users
has_many :projects, through: :project_roles
has_many :manager_project_roles,
-> { manager },
class_name: 'ProjectRole'
has_many :managers,
through: :manager_project_roles,
source: :user
# ...
end
Of course this is a lot of duplication which you can cut by looping over ProjectRoles.roles.keys and defining the assocations dynamically.
This is a very flexible way of modeling it which makes as few assumptions about the domain as possible. For example it allows multiple managers for a project and it allows users to have different roles in different projects.
If you want to model "bugs" as you would typically would with issues in a tracker you would create one table for the bug and a join table for the assignment:
class Bug < ApplicationRecord
belongs_to :project
has_many :bug_assignments
has_many :users, through: :bug_assignments
end
class BugAssignment < ApplicationRecord
has_one :project, through: :bug
belongs_to :bug
belongs_to :user
end
class User < ApplicationRecord
# ...
has_many :bug_assignments
has_many :bugs
end
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
I am trying to set up a polymorphic has-many-through relationship with ActiveRecord. Here's the end goal:
Users can belong to many organizations and many teams
Organizations have many users and many teams
Teams have many users and belong to an organization
I am trying to use has-many-through instead of has-and-belongs-to-many, since I need to associate some information along with the relationships (like user role in the organization or team), so I made a join table Membership.
How would I implement this?
I would design the schema like this:
Organization has many Team
Team has many TeamMember
User has many TeamMember
TeamMember belongs to User and Team
The models will be:
organization.rb
class Organization < ActiveRecord::Base
has_many :teams
has_many :team_members, through: :teams
has_many :users, through: :team_members
end
team.rb
class Team < ActiveRecord::Base
belongs_to :organization # fk: organization_id
has_many :team_members
has_many :users, through: :team_members
end
user.rb
class User < ActiveRecord::Base
has_many :team_members
has_many :teams, through: :team_members
has_many :organizations, though: :teams
end
team_member.rb
class TeamMember < ActiveRecord::Base
belongs_to :team # fk: team_id
belongs_to :user # fk: user_id
attr_accessible :role # role in team
end
So, compare with your requirements:
Users can belong to many organizations and many teams
=> Okay
Organizations have many users and many teams
=> Okay
Teams have many users and belong to an organization
=> Okay
Btw, we don't use any polymorphic here, and TeamMember stands for Membership in your early idea!
For polymorphic association,
class User
has_many :memberships
end
class Team
belongs_to :organization
has_many :memberships, :as => :membershipable #you decide the name
end
class Organization
has_many :memberships, :as => :membershipable
has_many :teams
end
class Membership
belongs_to :user
belongs_to :membershipable, polymorphic: true
end
Note that User is indirectly associated to Team and Organization, and that every call has to go through Membership.
In my projects, I use a Relationship class (in a gem I've named ActsAsRelatingTo) as the join model. It looks something like this:
# == Schema Information
#
# Table name: acts_as_relating_to_relationships
#
# id :integer not null, primary key
# owner_id :integer
# owner_type :string
# in_relation_to_id :integer
# in_relation_to_type :string
# created_at :datetime not null
# updated_at :datetime not null
#
module ActsAsRelatingTo
class Relationship < ActiveRecord::Base
validates :owner_id, presence: true
validates :owner_type, presence: true
validates :in_relation_to_id, presence: true
validates :in_relation_to_type, presence: true
belongs_to :owner, polymorphic: true
belongs_to :in_relation_to, polymorphic: true
end
end
So, in your User model, you would say something like:
class User < ActiveRecord::Base
has_many :owned_relationships,
as: :owner,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :organizations_i_relate_to,
through: :owned_relationships,
source: :in_relation_to,
source_type: "Organization"
...
end
I believe you may be able to leave the source_type argument off since the joined class (Organization) can be inferred from :organizations. Often, I'm joining models where the class name cannot be inferred from the relationship name, in which case I include the source_type argument.
With this, you can say user.organizations_i_relate_to. You can do the same set up for a relationship between any set of classes.
You could also say in your Organization class:
class Organization < ActiveRecord::Base
has_many :referencing_relationships,
as: :in_relation_to,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :users_that_relate_to_me,
through: :referencing_relationships,
source: :owner,
source_type: "User"
So that you could say organization.users_that_relate_to_me.
I got tired of having to do all the set up, so in my gem I created an acts_as_relating_to method so I can do something like:
class User < ActiveRecord::Base
acts_as_relating_to :organizations, :teams
...
end
and
class Organization < ActiveRecord::Base
acts_as_relating_to :users, :organizations
...
end
and
class Team < ActiveRecord::Base
acts_as_relating_to :organizations, :users
...
end
and all the polymorphic associations and methods get set up for me "automatically".
Sorry for the long answer. Hope you find something useful in it.
OS: Windows 7
Rails: 4.2.0
Hello, If I have two models lets say Game, and Team.
One game can have many teams, but teams can also belong to many games.
I can't seem to figure out the right way to do this, Belongs_to_many does not exist, and I don't think team can have_many games and game can have_many teams.
There are 2 ways to solve this:
One is like #Alex Pan's answer: using has_and_belongs_to_many
The other one is using belongs_to and has_many relation with a junction table.
in game.rb:
has_many :game_team_maps
has_many :teams, through: :game_team_maps
in team.rb
has_many :game_team_maps
has_many :games, through: :game_team_maps
in game_team_map.rb
belongs_to :game
belongs_to :team
In my opinion, the second way is more flexible and easier to maintain. I choose it for my own project.
There is a very detailed and useful cast from railscast that help you to solve this problem: http://railscasts.com/episodes/47-two-many-to-many
has_and_belongs_to_many
I believe is what you need
has_and_belongs_to_many
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Or you create a joining model (e.g. TeamMembership), that belongs_to Team and Game
You want to use the has_and_belongs_to_many association.
Your models should look like this:
class Games < ActiveRecord::Base
has_and_belongs_to_many :teams #append this line to your model
end
class Teams < ActiveRecord::Base
has_and_belongs_to_many :games #append this line to your model
end
You will also want to create a join table for Games and Teams like so:
class CreateGamesTeams < ActiveRecord::Migration
def change
create_table :gamess_teams do |t|
t.integer :game_id
t.integer :team_id
end
end
end
Remember to run rake db:migrate to create this join table.
While has_and_belongs_to_many is certainly a valid option, most "games" that I know of have a home team and an away team, so it might make sense to do something like:
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: 'Team'
belongs_to :away_team, class_name: 'Team'
end
class Team < ActiveRecord::Base
has_many :home_games, class_name: 'Game', foreign_key: :home_team_id
has_many :away_games, class_name: 'Game', foreign_key: :away_team_id
end
You could also model it with a has_many :through:
class Game < ActiveRecord::Base
has_many :team_games
has_many :games, through: :team_games
end
class TeamGame < ActiveRecord::Base
belongs_to :game
belongs_to :team
scope :home, -> { where(home: true) }
scope :away, -> { where(home: false) }
end
class Team < ActiveRecord::Base
has_many :team_games
has_many :games, through: :team_games
has_many :home_games, -> { TeamGame.home }, through: :team_games, source: :game, class_name: 'TeamGame'
has_many :away_games, -> { TeamGame.away }, through: :team_games, source: :game, class_name: 'TeamGame'
end
I need to do two associations in the same model. Where:
Team has_many User
Now, I want that Team has_one Leader
This "Leader" will be a User
Im trying to use has_one throught but I think that association isn't work.
Leader.rb
class Leader < ActiveRecord::Base
belongs_to :user
belongs_to :team
Team.rb
class Team < ActiveRecord::Base
has_one :user, through: :leader
end
User.rb
class User < ActiveRecord::Base
belongs_to :team
has_one :captain
end
and the get following error around line 27:
NoMethodError in TeamsController#create
26 def create
**27 #team = current_user.teams.create(team_params)**
28 #team.save
29 respond_with(#team)
30 current_user.update(team_id: #team.id)
In this case I think you need 2 model are enough
1). User model
class User < ActiveRecord::Base
belongs_to :team
end
2). Team model
class Team < ActiveRecord::Base
has_many :users
belongs_to :leader, class_name: 'User', foreign_key: :leader_id
end
How about setting a boolean flag in users table called leader. And then your association can become:
class Team < ActiveRecord::Base
has_many :users
has_one :leader, class_name: 'User', -> { where leader: true }
end
Team has_many User Now, I want that Team has_one Leader
This "Leader" will be a User
Use inheritance (also called sub-classing), Leader is a User.
class User < ActiveRecord::Base
belongs_to :team
end
class Leader < User
end
class Team < ActiveRecord::Base
has_many :users
has_one :leader
end
Your users table is also important. Ensure that users has t.belongs_to :team and t.string :type in its create_table method. Note that a Leader is a User and does not need a separate table, however you do need to allow ActiveRecord to record its type so it can return the correct Model later.
References:
inheritance specifically you need 'single table inheritance'
belongs_to scroll down for has_one and has_many, the three relationships used here.
current_user.teams.create(team_params)
Teams is for a has_many association, you want current_user.create_team(team_params)
You have has_one association between user and team. Try this:
current_user.create_team(team_params)
Also, you should add proper back association from team to leader.
class Team < ActiveRecord::Base
belongs_to :leader
has_one :user, through: :leader
end