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.
Related
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 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
in rails 3 i have 2 models: User,Event. User has_many Event through events_staffs and Event has_many Event through events_staffs.
class Staff < ActiveRecord::Base
has_many :events_staffs
has_many :events, through: :events_staffs
end
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
end
i wish that an Event have an author and some members where author and members are record of staffs table.
I wish I could do in the console something like this:
e=Event.first #ok it works
e.author=Staff.first
e.members=Staff.all - [Staff.first]
is possible to do it?
SOLUTION
#Event model
has_many :events_staffs, dependent: :destroy
has_many :members, through: :events_staffs, source: :staff #http://stackoverflow.com/a/4632456/1066183
#http://stackoverflow.com/a/13611537/1066183
belongs_to :author,class_name: 'Staff'
#migration:
class AddForeignKeyToEventsTable < ActiveRecord::Migration
def change
change_table :events do |t|
t.integer :author_id
end
end
end
Yes, quite easy. Add an author_id integer column to your Event model, then update your code as follows:
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
belongs_to :author, class_name: 'Staff'
end
As for members, I'd create another join table similar to your staffs_events table, but for members. Within that model you'd need to do the same belongs_to association as with Event where you specify the class_name for the member.