I have two Models Team and Match and a TeamMatch association.
class Match < ActiveRecord::Base
has_many :teams, :through => :team_matches, :source => :team
has_many :team_matches
def attend(team)
self.team_matches.create!(:team => team)
rescue ActiveRecord::RecordInvalid
nil
end
end
class Team < ActiveRecord::Base
has_many :matches, :through => :team_matches, :source => :match
has_many :team_matches
end
class TeamMatch < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
How do I restrict how many Teams can be assigned to a Match?
EDIT:
Update according to suggestions. m = FactoryGirl.create(:team), t..2 = FactoryGirl.create(:team)
1.9.3p194 :005 > m.attend(t)
(0.1ms) BEGIN
TeamMatch Exists (0.3ms) SELECT 1 AS one FROM `team_matches` WHERE (`team_matches`.`team_id` = BINARY 1 AND `team_matches`.`match_id` = 1) LIMIT 1
SQL (0.2ms) INSERT INTO `team_matches` (`match_id`, `team_id`) VALUES (1, 1)
(0.4ms) COMMIT
=> #<TeamMatch id: 1, match_id: 1, team_id: 1>
1.9.3p194 :006 > m.attend(t1)
(0.1ms) BEGIN
TeamMatch Exists (0.3ms) SELECT 1 AS one FROM `team_matches` WHERE (`team_matches`.`team_id` = BINARY 2 AND `team_matches`.`match_id` = 1) LIMIT 1
SQL (0.1ms) INSERT INTO `team_matches` (`match_id`, `team_id`) VALUES (1, 2)
(0.4ms) COMMIT
=> #<TeamMatch id: 2, match_id: 1, team_id: 2>
1.9.3p194 :007 > m.attend(t2)
(0.1ms) BEGIN
TeamMatch Exists (0.3ms) SELECT 1 AS one FROM `team_matches` WHERE (`team_matches`.`team_id` = BINARY 3 AND `team_matches`.`match_id` = 1) LIMIT 1
SQL (0.2ms) INSERT INTO `team_matches` (`match_id`, `team_id`) VALUES (1, 3)
(0.4ms) COMMIT
I just realize there is much neater solution to that, which is possible as it is has_many through association:
class TeamMatch < ActiveRecord::Base
belongs_to :match
belongs_to :team
validate :teams_per_match_limit
def teams_per_match_limit
errors.add(:base, 'blah') if par.children.size > 1
end
end
You can use association callback.
has_many :teams, :through => :team_matches, :source => :team, :before_add => :limit_number_of_teams
def limit_number_of_teams(added_team)
raise Exception.new('Team limit for the match reached') if teams.size >= 2
end
You should add some kind of custom validation like this:
class Match < ActiveRecord::Base
has_many :teams, :through => :team_matches, :source => :team
has_many :team_matches
validate :max_associate
def max_associate
errors.add(:teams, "can not be added more than 5") if teams.length > 5
end
end
Related
Hi I'm trying to create an app that has items users are selling. I have a table for Users selling the item and a table for items, but I'm a little confused about how I should setup the next table for the buyer. I have a separate table that is many to many between User and Item tracking user_id and item_id. Should I be creating a similar table tracking buyer_id and item_id? I want to be able to track what item has been bought from which user and vs versa. User and Buyers are from the same User table.
Thanks!
Edit:
class UsersController < ApplicationController
def my_page
#user = current_user
#seller_items = current_user.seller_orders.map { |so| so.order_items.map { |oi| { item: oi.item } } }.flatten
#seller_items.to_a
end
end
A more complete answer, with less models, and named joins. You might want to "merge" the order and order_items tables, and remove the multiple if you're dealing with singular items for sale, e.g. cars, but for anything that is either bought in bulk or might be sold at the same time as something else you might want this layout:
generate your models:
rails g model User name:string
rails g model Item name:string
rails g model Order order_date:time status:string
rails g model OrderItem order:references item:references multiple:integer
modify create_order to add in the additional references:
def change
create_table :orders do |t|
t.time :order_date, index: true, null: false
t.string :status
t.references :buyer, index: true, null: false, foreign_key: {to_table: :users}
t.references :seller, index: true, null: false, foreign_key: {to_table: :users}
t.timestamps
end
migrate the models:
rake db:migrate
== 20201126090851 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0036s
== 20201126090851 CreateUsers: migrated (0.0039s) =============================
== 20201126090858 CreateItems: migrating ======================================
-- create_table(:items)
-> 0.0030s
== 20201126090858 CreateItems: migrated (0.0032s) =============================
== 20201126091129 CreateOrders: migrating =====================================
-- create_table(:orders)
-> 0.0077s
== 20201126091129 CreateOrders: migrated (0.0081s) ============================
== 20201126091209 CreateOrderItems: migrating =================================
-- create_table(:order_items)
-> 0.0065s
== 20201126091209 CreateOrderItems: migrated (0.0067s) ========================
modify the models to add the joins:
app/models/user.rb
::::::::::::::
class User < ApplicationRecord
has_many :buyer_orders, class_name: "Order", foreign_key: :buyer, inverse_of: :buyer
has_many :seller_orders, class_name: "Order", foreign_key: :seller, inverse_of: :seller
end
::::::::::::::
app/models/item.rb
::::::::::::::
class Item < ApplicationRecord
has_many :order_items, inverse_of: :item
end
::::::::::::::
app/models/order.rb
::::::::::::::
class Order < ApplicationRecord
has_many :order_items, inverse_of: :order
belongs_to :seller, class_name: "User", inverse_of: :seller_orders
belongs_to :buyer, class_name: "User", inverse_of: :buyer_orders
end
::::::::::::::
app/models/order_item.rb
::::::::::::::
class OrderItem < ApplicationRecord
belongs_to :order, inverse_of: :order_items
belongs_to :item, inverse_of: :order_items
end
insert data:
User.create(name: "hello")
User.create(name: "again")
Item.create(name: "whatever")
Order.create(buyer: User.first, seller: User.last, order_date: Time.now())
OrderItem.create(item: Item.first, order: Order.first, multiple: 1)
test the output:
Check the status of the order:
2.7.0 :002 > Order.first
(0.5ms) SELECT sqlite_version(*)
Order Load (0.2ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Order id: 1, order_date: "2000-01-01 09:26:22", status: nil, buyer_id: 1, seller_id: 2, created_at: "2020-11-26 09:26:22", updated_at: "2020-11-26 09:26:22">
2.7.0 :003 > Order.first.seller
Order Load (0.2ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."id" ASC LIMIT ? [["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, name: "again", created_at: "2020-11-26 09:25:26", updated_at: "2020-11-26 09:25:26">
2.7.0 :004 > Order.first.buyer
Order Load (0.2ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."id" ASC LIMIT ? [["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "hello", created_at: "2020-11-26 09:25:18", updated_at: "2020-11-26 09:25:18">
Check the "buyer orders" of the first user:
2.7.0 :013 > User.first.buyer_orders
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Order Load (0.3ms) SELECT "orders".* FROM "orders" WHERE "orders"."buyer_id" = ? LIMIT ? [["buyer_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Order id: 1, order_date: "2000-01-01 09:26:22", status: nil, buyer_id: 1, seller_id: 2, created_at: "2020-11-26 09:26:22", updated_at: "2020-11-26 09:26:22">]>
check the "seller orders" of the second user:
2.7.0 :014 > User.last.seller_orders
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
Order Load (0.3ms) SELECT "orders".* FROM "orders" WHERE "orders"."seller_id" = ? LIMIT ? [["seller_id", 2], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Order id: 1, order_date: "2000-01-01 09:26:22", status: nil, buyer_id: 1, seller_id: 2, created_at: "2020-11-26 09:26:22", updated_at: "2020-11-26 09:26:22">]>
for peace of minds sake, check that the first user doesn't have any seller orders:
2.7.0 :015 > User.first.seller_orders
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Order Load (0.2ms) SELECT "orders".* FROM "orders" WHERE "orders"."seller_id" = ? LIMIT ? [["seller_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
2.7.0 :016 >
Controller
class UsersController < ApplicationController
def my_page
#user = current_user
#seller_orders = current_user.seller_orders
# remember that you might want to filter this in the future
# from_date = params[:from_date].present? ? params[:from_date] : Time.at(0)
# to_date = params[:to_date].present? ? params[:to_date] : Time.now()
# #seller_orders = #seller_orders.where(order_date: from_date..to_date)
end
end
view (I use haml)
%h1
= #user.username
Seller Orders
- #seller_orders.each do |so|
%table.seller_order{id: "seller_order_#{so.id}"}
%tr
%th Order Date:
%td= so.order_date
%tr
%th Buyer:
%td= so.buyer.username
%tr.spacer
%td{colspan: 2}
%tr
%th Item
%th Multiple
- so.order_items.each do |oi|
%tr
%td= oi.item.name
%td= oi.multiple
seller items
#seller_items = #seller_orders.map{|so| so.order_items.map{|oi| {multiple: oi.multiple, item: oi.item} }.flatten
or possibly (written from memory, not tested)
#seller_items = OrderItem.select("sum(order_items.multiple) as multiple, order_items.item_id as item_id").joins(:orders).joins(:buyer).where("users.id = ?", User.first.id).group("item_id")
You need something in the middle indeed - that's typicall called an "Order" in e-commerce with:
Link to User
Link to Item
but also maybe some more attributes:
Date of the sale
Status (cart/order/paid/delivered)
So indeed - you'll want an additional model there
class Seller < ApplicationRecord
belongs_to :user
has_many selling_items
has_many :items, through: :selling_items
end
class Buyer < ApplicationRecord
belongs_to :user
has_many bought_items
has_many :items, through: :bought_items
end
class Items < ApplicationRecord
end
class BoughtItem < ApplicationRecord
belongs_to :buyer
belongs_to :item
end
class SellingItem < ApplicationRecord
belongs_to :seller
belongs_to :item
end
class User < ApplicationRecord
end
I'm getting an error when trying to query a relation. These are my models:
class Course < ApplicationRecord
belongs_to :subject, inverse_of: :courses
# This is the important relation
has_one :department, through: :subject
end
class Subject < ApplicationRecord
belongs_to :department, inverse_of: :subjects
has_many :courses, dependent: :destroy, inverse_of: :subject
end
class Department < ApplicationRecord
has_many :subjects, dependent: :destroy
end
The thing is that if I do:
> Course.where(department: Department.first)
Department Load (0.3ms) SELECT `departments`.* FROM `departments` ORDER BY `departments`.`id` ASC LIMIT 1
Course Load (0.7ms) SELECT `courses`.* FROM `courses` WHERE `courses`.`id` IS NULL
=> []
As you can see, it includes a WHERE courses.id IS NULL. I'm able anyway to access this relation by doing:
> Course.joins(:subject).where(subjects: { department: Department.first })
Department Load (0.4ms) SELECT `departments`.* FROM `departments` ORDER BY `departments`.`id` ASC LIMIT 1
Course Load (0.5ms) SELECT `courses`.* FROM `courses` INNER JOIN `subjects` ON `subjects`.`id` = `courses`.`subject_id` WHERE `subjects`.`department_id` = 1
And I get many objects as result, but I'd like to know if I'm missing something to get this to work.
BTW, if I try to access the method directly, it works correctly:
> Course.first.department
Course Load (0.9ms) SELECT `courses`.* FROM `courses` ORDER BY `courses`.`id` ASC LIMIT 1
Department Load (0.6ms) SELECT `departments`.* FROM `departments` INNER JOIN `subjects` ON `departments`.`id` = `subjects`.`department_id` WHERE `subjects`.`id` = 1 LIMIT 1
=> #<Department:0x00007fc40dfab5f8
id: 1,
name: "Matemática",
slug: "matematica",
code: 1,
created_at: Sat, 15 Jun 2019 20:49:33 UTC +00:00,
updated_at: Sat, 15 Jun 2019 20:49:33 UTC +00:00>
Thank you!
I'm looking for help debugging an issue with a Rails has_many :through association. I have 3 models, Package, Venue, and my join table, Packagevenue
package.rb
class Package < ActiveRecord::Base
has_many :packagevenues
has_many :venues, through: :packagevenues
end
venue.rb
class Venue < ActiveRecord::Base
has_many :packagevenues
has_many :packages, through: :packagevenues
end
packagevenue.rb
class Packagevenue < ActiveRecord::Base
belongs_to :venues
belongs_to :packages
end
schema for packagevenues table
create_table "packagevenues", force: :cascade do |t|
t.integer "package_id"
t.integer "venue_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Test Case:
Packagevenue.first
Packagevenue Load (0.3ms) SELECT "packagevenues".* FROM "packagevenues" ORDER BY "packagevenues"."id" ASC LIMIT 1
=> #<Packagevenue:0x007fac12209750> {
:id => 1,
:package_id => 2,
:venue_id => 1,
.....
}
[11] webapp » p=Package.find(2)
Package Load (0.2ms) SELECT "packages".* FROM "packages" WHERE "packages"."id" = $1 LIMIT 1 [["id", 2]]
=> #<Package:0x007fac14eae738> {
:id => 2,
.....
}
[12] webapp » v=Venue.find(1)
Venue Load (0.2ms) SELECT "venues".* FROM "venues" WHERE "venues"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Venue:0x007fac1222e488> {
:id => 1,
.....
}
[13] webapp » v.packages
NameError: uninitialized constant Venue::Packages
.....
[14] webapp » p.venues
NameError: uninitialized constant Package::Venues
.....
I thought I did all of the setup correctly, can somebody please let me know why the Uninitialized Constant error keeps popping up?
The likely cause is due to the plurality of the belongs_to symbols in your Packagevenue model. You want those to be singular like so:
class Packagevenue < ActiveRecord::Base
belongs_to :venue
belongs_to :package
end
class Project < ActiveRecord::Base
has_many :accounts
has_many :sites, through: :accounts
end
class Site < ActiveRecord::Base
has_many :accounts
has_many :projects, through: :accounts
accepts_nested_attributes_for :accounts
end
class Account < ActiveRecord::Base
belongs_to :site
belongs_to :project
end
p = Project.find(1)
2.1.4 :011 > p.sites.create({"url"=>"site.ru", "accounts_attributes"=>{"0"=>{"email"=>"mail#site.ru"}}})
(0.3ms) BEGIN
SQL (1.8ms) INSERT INTO `sites` (`created_at`, `updated_at`, `url`) VALUES ('2015-09-04 07:09:53', '2015-09-04 07:09:53', 'site.ru')
SQL (0.3ms) INSERT INTO `accounts` (`created_at`, `email`, `site_id`, `updated_at`) VALUES ('2015-09-04 07:09:53', 'mail#site.ru', 3, '2015-09-04 07:09:53')
SQL (0.3ms) INSERT INTO `accounts` (`created_at`, `project_id`, `site_id`, `updated_at`) VALUES ('2015-09-04 07:09:53', 1, 3, '2015-09-04 07:09:53')
(1.2ms) COMMIT
=> #<Site id: 3, url: "site.ru", created_at: "2015-09-04 07:09:53", updated_at: "2015-09-04 07:09:53">
Question:
Why are added 2 record?
To add a single entry in the Account model with fields site_id, project_id, email?
The first account record is created automatically, because the Site is related to the Project through the Account.
The second record is created because you have accepts_nested_attributes_for :accounts in your Site model, and you pass the nested attributes while creating the Site record.
Could you clarify, what you want to archieve?
I have 3 tables and 3 models:
Car.rb
has_many :cars_domains, :dependent => :delete_all
has_many :domains, :through => :cars_domains
Domain.rb
has_many :cars_domains
has_many :cars, :through => :cars_domains
and cars_domain.rb
class CarsDomain < ActiveRecord::Base
belongs_to :car
belongs_to :domain
attr_accessible :car_id, :domain_id
end
class CreateCarsDomains < ActiveRecord::Migration
def change
create_table :cars_domains, :id => false do |t|
t.references :car, :domain
t.timestamps
end
add_index :cars_domains, [:car_id, :domain_id]
end
end
One car can be in some domains.
When I create a car and tied to a domain, then all is well.
But when I try destroy from cars_domains table, I have errors:
CarsDomain.where(:car_id => 2, :domain_id => 1).destroy
ArgumentError: wrong number of arguments (0 for 1)
Or
CarsDomain.where(:car_id => 2, :domain_id => 1).destroy_all
CarsDomain Load (0.5ms) SELECT "cars_domains".* FROM "cars_domains" WHERE "cars_domains"."car_id" = 2 AND "cars_do
mains"."domain_id" = 1
(0.2ms) SAVEPOINT active_record_1
Could not log "sql.active_record" event. NoMethodError: undefined method `name' for nil:NilClass
(0.3ms) ROLLBACK TO SAVEPOINT active_record_1
ActiveRecord::StatementInvalid: PGError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "cars_domains" WHERE "cars_domains"."" = $1
^
: DELETE FROM "cars_domains" WHERE "cars_domains"."" = $1
from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_record/connection_adapters/postgresql_
adapter.rb:1062:in `prepare'
What's wrong?
UPD:
CarsDomain.destroy_all(:car_id => 2, :domain_id => 1)
CarsDomain Load (0.9ms) SELECT "cars_domains".* FROM "cars_domains" WHERE "cars_domains"."car_id" = 2 AND "cars_do
mains"."domain_id" = 1
(0.2ms) SAVEPOINT active_record_1
Could not log "sql.active_record" event. NoMethodError: undefined method `name' for nil:NilClass
(0.2ms) ROLLBACK TO SAVEPOINT active_record_1
ActiveRecord::StatementInvalid: PGError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "cars_domains" WHERE "cars_domains"."" = $1
^
: DELETE FROM "cars_domains" WHERE "cars_domains"."" = $1
UPD 2
I think that problem in ID ---- create_table :cars_domains, :id => false. Because Destroy method need it.
Because you got more entries, and it belongs to CarsDomain. CarsDomain#destroy expects an id, so that's why you got this message. Use destory_all with condition!
CarsDomain.destroy_all(:car_id => 2, :domain_id => 1)
You're working on a collection, so use destroy_all.
See doc.
In your Car model (car.rb) you have the dependent set to :delete_all, when it should be :destroy, meaning has_many :cars_domains, :dependent => :destroy
If your domain is also dependent you need to add :dependent => :destroy to your domain model as well.
Problem was in :
create_table :cars_domains, :id => false do |t|
Correct variant is:
create_table :cars_domains do |t|
And CarsDomain.where(:car_id => params[:car_id], :domain_id => params[:domain_id]).destroy_all