Rails models with Single Table Inheritance and HABTM - ruby-on-rails

I have a project with 2 models, Game and Team. A Game has two Teams, an away team and a home team. There is a set number of Teams(no more get created) and each will belong to many Games.
I want to be able to do #game.home_team.name instead of #game.teams.find_by_id(#game.home_team_id).first.name. I'm not sure if I can do that without creating two additional models, AwayTeam and HomeTeam, which will have the same columns as Team except for an additional :game_id and maybe :type.
Currently the HABTM relationship between Games and Teams works but I have no way of turning a Team into a HomeTeam or AwayTeam once it belongs to a Game.
game.rb
class Game < ActiveRecord::Base
has_and_belongs_to_many :teams
has_one :away_team, -> {where(type:'away')}, class_name: 'Team'
has_one :home_team, -> {where(type:'home')}, class_name: 'Team'
end
team.rb
class Team < ActiveRecord::Base
has_and_belongs_to_many :games
# has_many :away_teams
# has_many :home_teams
end
###
# class AwayTeam < Team
# belongs_to :game
# belongs_to :team
# end
# class HomeTeam < Team
# belongs_to :game
# belongs_to :team
# end
games_controller.rb
def create
#game = Game.create(game_params)
#game.teams << Team.find_all_by_id([ #game.away_team_id, #game.home_team_id ])
#game.away_team = #game.teams.find_by_id(#game.away_team_id)
#game.home_team = #game.teams.find_by_id(#game.home_team_id)
#game.save
end
Any help would be really appreciated, thanks.

Try to add one additional model GameTeam and make has_many :through relation
class GameTeam
belongs_to :game
belongs_to :team
validates :team_type, presence: true
end
class Game
has_many :game_teams
has_may :teams, through: :game_teams
end
Then you can make game.teams to fetch all teams. You can additionally implement scopes of methods to get home or away team by GameTeam type

Related

Rails Associations Structure

How do I structure these 3 models with associations?
Issue
I'm having a lot of trouble setting up the model associations for a personal project I'm working on. Essentially I'm building a referee assigning system consisting of 3 models:
Assignor: the user who assigns referees to games
Referee: the user assigned to the game
Game: has 1-4 referees assigned
What I have so far in my models is:
class Assignor < ApplicationRecord
has_many :games
has_many :referees
has_many :assigned_referees, through:referees
end
class Game < ApplicationRecord
belongs_to :assignor
has_many :referees
end
class Referee < ApplicationRecord
has_many :assignors
has_many :games, through: :assignor
has_many :assigned_games, :through:assignor
end
What I'd like to do with these associations within my app is:
List the referees an assignor has => Assignor.referees
List the assignors a referee has => Referee.assignors
Where I'm having trouble is...
List the referees assigned to a game
List the referees NOT assigned to a game
-For Example:
If a user(Assignor) was to pull up a game and assign a referee, I want to make a drop down that populates with a list of referees NOT assigned
I would just go for a 3 way join table:
class Game < ApplicationRecord
has_many :referee_assignments
has_many :referees,
through: :referee_assignments,
inverse_of: :assigned_games
has_many :assigners,
through: :referee_assignments
def build_assignment(referee:, assigner:)
referee_assignments.new(
referee: referee.
assigner: assigner
)
end
end
class RefereeAssignment
# User who assigned the referee
belongs_to :assigner,
class_name: 'User'
belongs_to :game
belongs_to :referee
end
class Referee < ApplicationRecord
has_many :referee_assignments
has_many :assigners,
through: :referee_assignments
has_many :assigned_games,
through: :referee_assignments,
source: :game,
inverse_of: :referees
def build_assignment(game:, assigner:)
referee_assignments.new(
game: game,
assigner: assigner
)
end
end
List the referees assigned to a game
game = Game.find(1)
game.referees.each do |r|
# ...
end
List the referees not assigned to a game
referees = Referee.where.not(
id: game.referees
)
Using a subquery is the simplest possible way but you could also use NOT EXIST or a join.

Tournament -> Team -> Player Associations

I'm a relative beginner to Rails, but am learning as I go. I'm trying to create a Tournament Entry portal, where a team would enter players for a given tournament. I've done a bit of reading about associations, but am having some trouble wrapping my head around how to apply them in this instance.
As a basic overview:
One tournament, has many teams.
Each team has many players
Therefore one tournament also has many players (through the teams
entered)
Here's my code for this, but I'm not sure it's right because I'm unable to get any tournament_ids associated to players.
(tournament.rb)
class Tournament < ApplicationRecord
has_many :teams
has_many :players, :through => :teams
end
(team.rb)
class Team < ApplicationRecord
belongs_to :tournament
has_many :players
end
(player.rb)
class Player < ApplicationRecord
has_one :team
has_one :tournament, :through => :team
end
Within the Players table there is both team_id & tournament_id fields, however I'm only able to populate the team_id field through association when I try in console.
I'm wondering if there's something amiss with my associations.
The usage of 'belongs_to', 'has_many', 'has_one' depends on the data model in database of course.
If you have team_id foreign key in players table, then you need to define Player class as:
class Player < ApplicationRecord
belongs_to :team
has_one :tournament, :through => :team
end
In addition, I really believe that Tournament <-> Team should have many-to-many association (if team can participate in many tournaments of course). I would suggest adding model TeamTournament and define final model structure as:
class Tournament < ApplicationRecord
has_many :team_tournaments
has_many :teams, :through => :team_tournaments
has_many :players, :through => :teams
end
class TeamTournament < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
class Team < ApplicationRecord
has_many :team_tournaments
has_many :tournaments, :through => :team_tournaments
has_many :players
end
the Player class should have belongs_to associations with Team and Tournament
class Player < ApplicationRecord
belongs_to :team
belongs_to :tournament
end
OK. I assume your question is about your models associations rather than how to set up association for getting tournament_id from player and so on. So I'll try to hand you some tips about your project and associations could be set up for it.
As I got your portal idea... You want the tournament to has many teams and the team to has many players. But then you want to get tournament_id from player. I believe you don't want to do that because in real life tournament indeed may "has" some players but every single player don't has to belong to some tournament. He can take part in many tournaments. So you don't need to set up association for that. Same thing with tournament and teams. But since team has the player he has to belong to that team. So you need association for that.
Wrapping up my setup for you will be like:
(tournament.rb)
class Tournament < ActiveRecord::Base
has_many :teams
end
(team.rb)
class Team < ActiveRecord::Base
has_many :players
end
(player.rb)
class Player < ActiveRecord::Base
belongs_to :team
end
And an example about how you can get the tournament where certain team take part in without the direct association:
team = Team.first # just take some team
Tournament.includes(:teams).where(teams: { id: team.id })
The same way you can achieve your other goals (get the tournament certain player belongs to and so on). But such cases don't need associations. Associations are needed when the object relates to another conceptually.

Polymorphic has-many-through relationships in Rails

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.

How to do has_many and has_one association in same model?

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

belongs_to has_one structure

I have an application which has the following characteristics
There are Clubs
Each Club has Teams
Each Team has Players
I have a users table. The user table basically contains the username and password for the club manager, team manager and the player to login to the system.
How should I structure the models and the tables?
I plan to create tables for Club, Team and Players. But I am not sure show to structure the relationship between them and the users table.
I could create user_id in each of the model, but the relationship would be Club belongs_to User which doesn't seems right. Moreover I would end up with a User model that has the following
has_one :club
has_one :team
has_one :player
Which is not right. A user will have only one of them at any given time.
Is there a better way to structure this?
Under Rails, has_one is really "has at most one". It's perfectly valid to have all three has_one decorators in User. If you want to ensure they only have precisely one, you could add a validation, for instance:
class User < ActiveRecord::Base
has_one :club
has_one :team
has_one :player
validate :has_only_one
private
def has_only_one
if [club, team, player].compact.length != 1
errors.add_to_base("Must have precisely one of club, team or player")
end
end
end
Since you have the ability to change the users table in the database, I think I would put club_id, team_id, player_id in users, and have the following:
class Club < ActiveRecord::Base
has_one :user
has_many :teams
has_many :players, :through => :teams
end
class Team < ActiveRecord::Base
has_one :user
belongs_to :club
has_many :players
end
class Player < ActiveRecord::Base
has_one :user
belongs_to :team
has_one :club, :through => :team
end
class User < ActiveRecord::Base
belongs_to :club
belongs_to :team
belongs_to :player
validate :belongs_to_only_one
def belongs_to_only_one
if [club, team, player].compact.length != 1
errors.add_to_base("Must belong to precisely one of club, team or player")
end
end
end
I'd even be tempted to rename User as Manager, or have has_one :manager, :class_name => "User" in the Club, Team and Player models, but your call.

Resources