I have following models
class TeamPlayer < ApplicationRecord
belongs_to :team
belongs_to :player
belongs_to :role
end
class Team < ApplicationRecord
has_many :team_players
has_many :players, :through => :team_players
end
class Role < ApplicationRecord
has_many :team_players
has_many :players, :through => :team_players
end
class Player < ApplicationRecord
has_many :team_players
has_many :teams, :through => :team_players
has_many :roles, :through => :team_players
end
Basically, I want to assign different roles to different players in a team.
id team_id player_id role_id
2 1 2 1
3 3 2 1
4 1 1 2
What should it look like in my teams_controller.rb to add new player with a role, to update a player with new role and to remove that player from my team?
This is only the start of a possible solution and it is pretty similar to what you have with some model and database validations added. Some of these validations ensure the uniqueness of every three-way relationship (FilledTeamRole), so either the error of attempting to create a duplicate record would need to be handled or you could filter the possible ids of each class that could be selected so that a duplicate cannot be created.
A complete solution would depend on what other associations you want between the Team, Player and Role classes other than one that requires all three. For example, do you want/need an association between Team and Player where a relationship exists between only those two classes without the necessity of a Role (TeamPlayer id: 1, team_id: 1, player_id: 1). If those relationships are desired, then additional code will be needed to achieve this, which I have and can provide as a suggestion.
As far as what your controller would look like, you could use the filled_team_roles controller (or perhaps create a dashboard controller), provide instance variables #teams, #players and #roles to populate drop-down menus for each class within a form to create the filled_team_roles relationship. You could also have additional forms within each of the other classes where, using two drop-downs instead of three with the third value the selected model id of the class whose controller the form is in (e.g. the edit action in the players_controller with drop-downs for team and role)
~/app/models/team.rb
class Team < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates :name, uniqueness: { scope: [:sport, :city] }
scope :by_name_asc, -> { order(name: :asc) }
end
~/app/models/player.rb
class Player < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates_uniqueness_of :ssn
scope :by_name_asc, -> { order(last_name: :asc, first_name: :asc) }
end
~/app/models/role.rb
class Role < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates_uniqueness_of :name
scope :by_name_asc, -> { order(name: :asc) }
end
~/app/models/filled_team_role.rb
class FilledTeamRole < ApplicationRecord
belongs_to :team
belongs_to :player
belongs_to :role
validates :team_id, presence: true
validates :player_id, presence: true
validates :role_id, presence: true
validates :team_id, uniqueness: { scope: [:player_id, :role_id] }
end
~/db/migrate/20170127041000_create_team.rb
class CreateTeam < ActiveRecord::Migration[5.0]
def change
create_table :teams do |t|
t.string :name
t.string :sport
t.string :city
t.string :state
t.string :country
t.timestamps null: false
end
add_index :teams, [:name, :sport, :city], unique: true
end
end
~/db/migrate/20170127041100_create_player.rb
class CreatePlayer < ActiveRecord::Migration[5.0]
def change
create_table :players do |t|
t.string :first_name
t.string :last_name, index: true
t.string :full_name_surname_first
t.string :ssn, index: { unique: true }
t.timestamps null: false
end
end
end
~/db/migrate/20170127041200_create_role.rb
class CreateRole < ActiveRecord::Migration[5.0]
def change
create_table :roles do |t|
t.string :name, index: { unique: true }
t.timestamps null: false
end
end
end
~/db/migrate/20170127051300_create_filled_team_role.rb
class CreateFilledTeamRole < ActiveRecord::Migration[5.0]
def change
create_table :filled_team_roles do |t|
t.timestamps null: false
t.references :team
t.references :role
t.references :player
end
add_index :filled_team_roles,
[:team_id, :player_id, :role_id],
unique: true,
name: 'index_filled_team_roles_unique_combination_of_foreign_keys'
end
end
~/db/seeds.rb
Team.create(name: 'Los Angeles Dodgers', sport: 'baseball', city: 'Los Angeles', state: 'CA', country: 'United States')
Team.create(name: 'New York Yankees', sport: 'baseball', city: 'New York', state: 'NY', country: 'United States')
Team.create(name: 'Chicago Cubs', sport: 'baseball', city: 'Chicago', state: 'IL', country: 'United States')
Team.create(name: 'St. Louis Cardinals', sport: 'baseball', city: 'St. Louis', state: 'MO', country: 'United States')
Player.create(first_name: 'Max', last_name: 'Walker', full_name_surname_first: 'Walker, Max', ssn: '123-45-6789')
Player.create(first_name: 'Homer', last_name: 'Winn', full_name_surname_first: 'Winn, Homer', ssn: '234-56-7890')
Player.create(first_name: 'Will', last_name: 'Steel', full_name_surname_first: 'Steel, Will', ssn: '345-67-8901')
Player.create(first_name: 'Lucky', last_name: 'Snag', full_name_surname_first: 'Snag, Lucky', ssn: '456-78-9012')
Role.create(name: 'pitcher')
Role.create(name: 'catcher')
Role.create(name: 'first baseman')
Role.create(name: 'second baseman')
Role.create(name: 'shortstop')
Role.create(name: 'third baseman')
Role.create(name: 'right fielder')
Role.create(name: 'center fielder')
Role.create(name: 'left fielder')
FilledTeamRole.create(team_id: 1, player_id: 1, role_id: 1)
FilledTeamRole.create(team_id: 2, player_id: 2, role_id: 2)
FilledTeamRole.create(team_id: 3, player_id: 3, role_id: 3)
FilledTeamRole.create(team_id: 4, player_id: 4, role_id: 4)
Related
Hi Im creating an ec site in my rails.
My migration: (Item) has :name and :price. (Basket_Item) has :item_id(fk), :basket_id(fk) and :quantity.
The system User will add some items to their basket. So Basket_items is JOIN Table between (Item) and (Basket) see like below.
What I want to do:
Get a price of Item and get a quantity from Basket_Items which is selected by user. Then I want to create #total_price = item_price * item_quantity.
Can anyone help me to create the #total_price please.
This is my a try code but it doesn't work on rails console.
Basket_items
class CreateBasketItems < ActiveRecord::Migration[5.2]
def change
create_table :basket_items do |t|
t.references :basket, index: true, null: false, foreign_key: true
t.references :item, index: true, null: false, foreign_key: true
t.integer :quantity, null: false, default: 1
t.timestamps
end
end
end
///
Items
class CreateItems < ActiveRecord::Migration[5.2]
def change
create_table :items do |t|
t.references :admin, index: true, null: false, foreign_key: true
t.string :name, null: false, index: true
t.integer :price, null: false
t.text :message
t.string :category, index: true
t.string :img
t.string :Video_url
t.text :discription
t.timestamps
end
end
end
///
This is my try a code but it doesn't work on rails console.
basket = current_user.prepare_basket
item_ids = basket.basket_items.select(:item_id)
items = basket.items.where(id: item_ids)
items_price = items.select(:price)
items_quantity = basket.basket_items.where(item_id: item_ids).pluck(:quantity)
def self.total(items_price, items_quantity)
sum(items_price * items_quantity)
end
#total_price = basket.total(items_price, item_quantity)
You provided only migration files, so my answer will be based on some assumptions:
So Basket_items is JOIN Table between (Item) and (Basket) - taking into account the logic of baskets and items, it means that you have many-to-many relation between Item & Basket through BasketItem as follow:
# basket.rb
class Basket < ApplicationRecord
belongs_to :user
has_many :basket_items
has_many :items, through: :basket_items
end
#item.rb
class Item < ApplicationRecord
has_many :baskets_items
has_many :baskets, through: :baskets_items
end
#basket_item.rb
class BasketItem < ApplicationRecord
belongs_to :basket
belongs_to :item
end
I'm not sure what prepare_basket on user instance do, just make sure that you get the right basket from this method.
With this configuration the total price can be calculated with one request as follow:
#total_price = basket.items.sum('items.price * basket_items.quantity')
or define it inside a model:
# basket.rb
class Basket < ApplicationRecord
belongs_to :user
has_many :basket_items
has_many :items, through: :basket_items
def total_price
items.sum('items.price * basket_items.quantity')
end
end
basket = get_user_basket # find basket you gonna work with
#total_price = basket.total_price
Create some basket, items, and basket_items (this one will be created automatically if you create an item with basket.items.create(params)) in console and investigate the resulting SQL query:
SELECT SUM(items.price * basket_items.quantity) FROM "items" INNER JOIN "basket_items" ON "items"."id" = "basket_items"."item_id" WHERE "basket_items"."basket_id" = ?
Read more about has_many :through association in Rails.
I have 2 models with many-to-many relation implemented by has_many/belong_to, as follows:
class Author < ApplicationRecord
has_many :author_books
has_many :books, :through => :author_books, :validate => false
end
class Book < ApplicationRecord
has_many :author_books
has_many :authors, :through => :author_books, :validate => false
end
class AuthorBook < ApplicationRecord
belongs_to :author
belongs_to :book
end
The tables were created in the following migration:
create_table :books do |t|
t.string :name
t.integer :price
end
create_table :authors do |t|
t.string :name
end
create_table :author_books do |t|
t.integer :book_id
t.integer :author_id
t.integer :percent
end
When I execute the following test code:
Book.delete_all
Author.delete_all
AuthorBook.delete_all
author1 = Author.create name: 'a1'
author2 = Author.create name: 'a2'
book = Book.new name: 'b', price: 40
book.authors = Author.where("id = #{author1.id} or id = #{author2.id}")
ret = book.save
AuthorBook.all.each do |ab|
puts "Author id #{ab.id} author #{ab.author_id} book #{ab.book_id}"
end
I get the following good output:
Author id 1 author 980190963 book 980190963
Author id 2 author 980190964 book 980190963
But when I add the following after_save to AuthorBook:
after_save :f
def f
self.book.author_books.each do |ab|
# nothing
end
end
I get the following good output:
Author id 1 author 980190963 book 980190963
Author id 2 author 980190964 book
And indeed, when I inspect the database I can see that the AuthorBook record contains nil in its book_id field. Any idea why this happens ?
I am new to rails so please bear with me, I've search all day on this. Apologize if this is a beginner questions :)
I have a model with a polymorphic association defined:
class SocialLink < ActiveRecord::Base
belongs_to :social, polymorphic: true
end
And two models that should have one of this association
class Staff < ActiveRecord::Base
validates :name, presence: true
belongs_to :establishment
has_one :image, as: :imageable
has_one :social_link, as: :social
end
class Establishment < ActiveRecord::Base
validates :name, presence: true
has_one :location
has_one :social_link, as: :social
has_many :staff
accepts_nested_attributes_for :location
accepts_nested_attributes_for :staff
end
Edit: initial table creation migration for social links
class CreateSocialLinks < ActiveRecord::Migration
def change
create_table :social_links do |t|
t.string :facebook
t.string :twitter
t.string :yelp
t.string :google_plus
t.string :youtube
t.string :instagram
t.string :linkedin
t.timestamps null: false
end
end
end
The migration for this was created like so (Edit: please note the table existed at time of migration)
class AddSocialLinkReferenceToEstablishmentAndStaff < ActiveRecord::Migration
def change
add_reference :social_links, :social_links, polymorphic: true, index: true
end
end
class UpdateSocialLinkReference < ActiveRecord::Migration
def change
remove_reference :social_links, :social_links
add_reference :social_links, :social, polymorphic: true, index: true
end
end
Edit: after the above migration, the social_id and social_type are available
#<SocialLink id: nil, facebook: nil, twitter: nil, yelp: nil, google_plus: nil, youtube: nil, instagram: nil, linkedin: nil, created_at: nil, updated_at: nil, social_id: nil, social_type: nil>
However, for some reason the association isn't available on either of the models. I am having trouble seeing what I have done wrong, it looks like I've set it up the same as another polymorphic association on an image model that is working
This one is working
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
Thanks or the help!
according to the rails guide on polymorphic associations your migration should look more like this:
class CreateSocialLinks < ActiveRecord::Migration
def change
create_table :social_links do |t|
t.string :name
t.references :social, polymorphic: true, index: true
t.timestamps null: false
end
end
end
otherwise the table will be misssing
The problem was that I was trying to access the association with the polymorphic name instead of just using the model name. Instead of doing staff.social I should have used staff.social_link. I got confused on this part.
Sorry for the confusion
I want to create a simple discussion board, a discussion will contain a title, content, a user_id and a topic_id.
To allow a user to reply to a discussion I need to self reference the original discussion, though I'm not sure how to do this.
This is my migration
class CreateDiscussions < ActiveRecord::Migration
def change
create_table :discussions do |t|
t.text :post
t.references :user, index: true
t.references :topic, index: true
t.string :title
t.references :discussion, index: true
t.timestamps
end
end
end
and my discussion model;
class Discussion < ActiveRecord::Base
belongs_to :user
belongs_to :topic
belongs_to :discussion
end
a user will create a disussion, another user can reply to that discussion in which I'll store the original discussion_id so I can do something like #d = Discussion.where discussion_id: nil to find the top-level discussions, and
#d.each do |d|
#replies = Discussion.where discussion_id: d.id
end
is this along the right track?
(I don't know how to properly implement the names as described here http://guides.rubyonrails.org/association_basics.html#self-joins)
( I've just realised that it's probably better/easier separated into two models, discussions and replies, but I'd still like to know how to do it in a single model)
You can structure your Discussion class as follows:
class Discussion < ActiveRecord::Base
belongs_to :user
belongs_to :topic
has_many :replies, class_name: "Discussion", foreign_key: "replied_to_id"
belongs_to :master_discussion, class_name: "Discussion", foreign_key: "replied_to_id"
end
Migration file:
class CreateDiscussions < ActiveRecord::Migration
def change
create_table :discussions do |t|
t.text :post
t.references :user, index: true
t.references :topic, index: true
t.string :title
t.integer :replied_to_id, index: true
t.timestamps
end
end
end
You can now use #discussion.replies to access replies belonging to a discussion and #discussion.master_discussion to access the discussion record to which this reply belongs
EDIT
changed:
t.references :discussion, index: true
to:
t.integer :replied_to_id, index: true
I tested and it's all working:
[11] pry(main)> #discussion = Discussion.create(post:"first post", title: "first discussion")
=> #<Discussion id: 3, post: "first post", title: "first discussion", replied_to_id: nil, created_at: "2014-10-23 20:15:38", updated_at: "2014-10-23 20:15:38">
[12] pry(main)> #discussion.replies << Discussion.create(post: "post to first thread",title: "reply to first thread")
=> [#<Discussion id: 4, post: "post to first thread", title: "reply to first thread", replied_to_id: 3, created_at: "2014-10-23 20:17:06", updated_at: "2014-10-23 20:17:06">]
[13] pry(main)> #discussion.replies
=> [#<Discussion id: 4, post: "post to first thread", title: "reply to first thread", replied_to_id: 3, created_at: "2014-10-23 20:17:06", updated_at: "2014-10-23 20:17:06">]
[14] pry(main)> #reply = #discussion.replies.first
[15] pry(main)> #reply.master_discussion
Discussion Load (0.2ms) SELECT "discussions".* FROM "discussions" WHERE "discussions"."id" = ? LIMIT 1 [["id", 3]]
=> #<Discussion id: 3, post: "first post", title: "first discussion", replied_to_id: nil, created_at: "2014-10-23 20:15:38", updated_at: "2014-10-23 20:15:38">
I'm curious why my has_many :through association is not populated until after my object is saved and reloaded. It seems like all the data should be there for the joins to be constructed.
Schema:
ActiveRecord::Schema.define(version: 20140821223311) do
create_table "cats", force: true do |t|
t.string "name"
t.integer "human_id"
end
create_table "houses", force: true do |t|
t.string "address"
end
create_table "humen", force: true do |t|
t.string "name"
t.integer "house_id"
end
end
Models:
class Cat < ActiveRecord::Base
belongs_to :human, inverse_of: :cats
has_one :house, through: :human
has_many(
:siblings,
through: :house,
source: :cats
)
end
class Human < ActiveRecord::Base
has_many :cats, inverse_of: :human
belongs_to :house, inverse_of: :humans
end
class House < ActiveRecord::Base
has_many :humans, inverse_of: :house
has_many :cats, through: :humans
end
I've saved an instance of House and Human in the db. The seed file looks like this:
h = House.create(address: "123 Main")
Human.create(house_id: h.id, name: "stu")
I've been testing with this:
c = Cat.new(human_id: 1, name: "something awesome")
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy []>
c.save
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy []>
c.reload
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy [#<Cat id: 2, name: "gizmo", created_at: "2014-08-21 22:37:07", updated_at: "2014-08-21 22:37:07", human_id: 1>]>
Any help on this would be greatly appreciated :)
here's a github repo if you want to play with it:
https://github.com/w1zeman1p/association_wat
It seems CollectionProxy is a bit too lazy. If you do:
c = Cat.new(human_id: 1, name: "something awesome")
c.save
c.siblings
You get what you expect.
So it might be the CollectionProxy being cached from your first call to .siblings before the cat is saved.