I am trying to figure out how to best do my model associations. I have 3 tables.
players, teams, teamplayers
my outline is that a player can belong to multiple teams. a team can have multiple players, but then in my teamplayers table I have 2 fields teamid and playerid(not counting the primary key id).
For example:
Player has id of 1000
team has id of 501
In the teamplayers table that would be stored as:
team_id player_id
501 1000
so how would I design the relations belongs_to and has_many in my models?
You can achieve this as given below :
class Team < ActiveRecord::Base
has_many :team_players
has_many :players, :through => :team_players
end
class Player < ActiveRecord::Base
has_many :team_players
has_many :teams, :through => :team_players
end
class TeamPlayer < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
The corresponding migration might look like this:
class CreateTeamPlayers < ActiveRecord::Migration
def change
create_table :teams do |t|
t.string :name
t.timestamps
end
create_table :players do |t|
t.string :name
t.timestamps
end
create_table :team_players do |t|
t.belongs_to :player
t.belongs_to :team
t.timestamps
end
end
end
Then if you want to fetch players of a particular team, just do
#team = Team.first (do as per your requirement)
#team.players
Also, to fetch a player's teams,
#player = Player.first (do as per your requirement)
#player.teams
H2H :)-
Following should do:
class Player < ActiveRecord::Base
has_many :team_players
has_many :teams, through: :team_players
end
class TeamPlayer < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :team_players
has_many :players, through: :team_players
end
you want the has_many through association.
class Player < ActiveRecord::Base
has_many :teamplayers
has_many :teams, through: :teamplayers
end
class TeamPlayer < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :teamplayers
has_many :players, through: :teamplayers
end
http://guides.rubyonrails.org/association_basics.html
This is possible in two way:
first is:
class Team < ActiveRecord::Base
has_and_belongs_to_many :players
end
class Player < ActiveRecord::Base
has_and_belongs_to_many :teams
end
second is:
class Player < ActiveRecord::Base
has_many :teamplayers
has_many :teams, through: :teamplayers
end
class TeamPlayer < ActiveRecord::Base
belongs_to :player
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :teamplayers
has_many :players, through: :teamplayers
end
Related
So I have two standard models (Contracts and Agents) with a “standard” many-to-many relationship (ContractAgents).
What I would like to do is add an integer field “description” to the ContractAgent table so that I can enum it with (you guessed it) descriptions for that specific relationship (ie “buyers_agent” or “sellers_agent”).
Is this possible?
Here is the code (now that I am on my laptop).
//current agent.rb
class Agent < ApplicationRecord
has_many :contract_agents
has_many :contracts, through: :contract_agents
end
//current contract.rb
class Contract < ApplicationRecord
belongs_to :user
has_many :contract_agents
has_many :agents, through: :contract_agents
end
//current agent_contract.rb
class ContractAgent < ApplicationRecord
end
//current schema for agent_contract
t.integer agent_id
t.integer contract_id
Can I make it like the below, or will doing that to the "ContractAgent" model which follows the RoR naming convention mess it up?
//proposed agent.rb
class Agent < ApplicationRecord
has_many :contract_agents
has_many :contracts, through: :contract_agents
end
//proposed contract.rb
class Contract < ApplicationRecord
belongs_to :user
has_many :contract_agents
has_many :agents, through: :contract_agents
end
//proposed agent_contract.rb
class ContractAgent < ApplicationRecord
belongs_to :contract
belongs_to :agent
enum description: [:seller_agent, :buyer_agent]
end
//proposed schema for agent_contract
t.integer agent_id
t.integer contract_id
t.integer description
Rails 6, Ruby 2.7. Thanks.
It sounds like a has_many :through association would be best.
One way to do it:
class Contract < ApplicationRecord
has_many :relationships
has_many :agents, through: :relationships
end
class Agent < ApplicationRecord
has_many :relationships
has_many :contracts, through: :relationships
end
# this model will have description, and/or agent_type, etc
class Relationship < ApplicationRecord
belongs_to :contract
belongs_to :agent
end
In general, has_many :through relationships are better than the standard "many-to-many" relationships precisely because of the kind of issue you are running into.
The goal is for a shop to create rewards and associate each reward to a follower of his choice. This is my setup:
class Shop < ApplicationRecord
has_many :rewards
has_many :follows
has_many :users, through: :follows
end
class Reward < ApplicationRecord
belongs_to :shop
end
class Follow < ApplicationRecord
belongs_to :shop
belongs_to :user
has_many :reward_participant
end
class User < ApplicationRecord
has_many :follows
has_many :shops, through: :follows
end
I created this model in order to capture the reward and follower association.
class RewardParticipant < ApplicationRecord
belongs_to :reward
belongs_to :follow
end
And I have created the following migrations:
class CreateRewards < ActiveRecord::Migration[6.0]
def change
create_table :rewards do |t|
t.string :title
t.text :body
t.date :expires
t.integer :shope_id
t.timestamps
end
end
end
class CreateRewardParticipants < ActiveRecord::Migration[6.0]
def change
create_table :reward_participants do |t|
t.integer :reward_id
t.integer :follow_id
t.timestamps
end
end
end
I'm having trouble figuring out if this is the correct approach to the model associations and migrations. Thanks for the help in advance!
Generally you are right.
We want users to follow a shop, and a shop can create rewards and grant many rewards to many followers.
1. Visual schema:
2. Model associations (complete version)
user.rb
has_many :follows
has_many :reward_follows, through: :follows
has_many :rewards, through: :reward_follows # NOT through shops
has_many :shops, through: :follows
follow.rb
belongs_to :user
belongs_to :shop
has_many :reward_follows
shop.rb
has_many :rewards
has_many :reward_follows, through: :rewards # NOT through follows
has_many :follows
has_many :users, through: :follows
reward.rb
has_many :reward_follows
belongs_to :shop
has_many :follows, through: :reward_follows
has_many :users, through: :follows
3. Do not use date field. Use datetime field.
Justification: https://www.ruby-forum.com/t/time-without-date/194146
This personally saved me hours of work long-term.
In my rails app I'm trying to create a system that will reward users with badges for various achievements
created a table 'user_badges'
migration:
class CreateUserBadges < ActiveRecord::Migration[5.1]
def change
create_table :user_badges do |t|
t.references :user, foreign_key: true
t.references :badge, foreign_key: true
t.timestamps
end
end
end
model UserBadge:
class UserBadge < ApplicationRecord
belongs_to :user
belongs_to :badge
end
модель Badge:
class Badge < ApplicationRecord
has_many :users, through: :user_badges
has_many :user_badges
end
model User:
class User < ApplicationRecord
...
has_many :badges, through: :user_badges
has_many :user_badges
...
end
when I try to add a badge to the user:
b = Badge.create(title: 'first')
User.last.badges << b
I get this error:
ActiveRecord::HasManyThroughOrderError: Cannot have a has_many
:through association 'User#badges' which goes through
'User#user_badges' before the through association is defined.
also when I simply call:
User.last.badges
same error:
ActiveRecord::HasManyThroughOrderError: Cannot have a has_many
:through association 'User#badges' which goes through
'User#user_badges' before the through association is defined.
Define has_many association first then add through: association
class UserBadge < ApplicationRecord
belongs_to :user
belongs_to :badge
end
class Badge < ApplicationRecord
has_many :user_badges # has_many association comes first
has_many :users, through: :user_badges #through association comes after
end
class User < ApplicationRecord
...
has_many :user_badges
has_many :badges, through: :user_badges
...
end
Note, in case you mistakenly wrote first has_many 2 times, then it can reproduce this error too. E.g.
class User < ApplicationRecord
...
has_many :user_badges
has_many :badges, through: :user_badges
...
has_many :user_badges
end
# => Leads to the error of ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'User#badges' which goes through 'User#user_badges' before the through association is defined.
Active Record should alert has_many being used two times IMHO...
I have this relationship between users, teams
class CreateTeamsUsers < ActiveRecord::Migration
def change
create_table :teams_users, :id => false do |t|
t.references :user
t.references :team
t.timestamps
end
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
end
The issue is that I want to add extra attribute in HABTM,attribute name is "user_name"
How to do this?
Instead of HABTM you'd use has_many and has_many :through.
class User < ActiveRecord::Base
has_many :memberships
has_many :team, through: :membership
end
class Membership < ActiveRecord::Base # This would be your old 'join table', now a full model
belongs_to :user
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
Short version, you can't do what your're trying to do without a little refactoring. Here is how I would do it (apologies if there's syntax issues, I'm doing this from memory I haven't tested the code but the principle is sound)
Create a new model to represent "membership" of a team (maybe call it "Membership") and the associated migration to create the table:
class Membership
belongs_to :team
belongs_to :user
end
Then change your team and user models to use this new model:
class User
has_many :memberships
has_many :teams, through: :memberships
end
class Team
has_many :memberships
has_many :users, through: :memberships
end
Once you've refactored this far, adding additional columns / attributes to "memberships" is easy because you can just treat it like any other model.
I have to following relationship:
class Course < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :courses
end
Then I have the following table:
create_table :courses_users, :force => true, :id => false do |t|
t.integer :user_id
t.integer :course_id
t.integer :middle_value
end
How can I access (edit/update) the middle value in the many to many record?
HABTM should be used to only store the relation. If you have any fields you want to store in the relation, you should create another model, eg. CourseSignup. You would then use this model to create a has_many :through => :course_signups relation, so your models would look like this:
class Course < ActiveRecord::Base
has_many :course_signups
has_many :users, :through => :course_signups
end
class CourseSingup < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :course_signups
has_many :courses, :through => :course_signups
end
Then you can add your middle_value in the CourseSignup model.
You can find more details in the guide to ActiveRecord associations.
You want a has_many :though, not a HABTM.
A HABTM does not have a join model, but a has_many :through does. Something like:
class Course < ActiveRecord::Base
has_many :enrollments
has_many :users, :through => :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
end