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!
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 using rails 4 and was wondering if anyone could find what's wrong in my code.
I have project model and I created a team model that has a belongs_to - has_one relation with project.
project model:
class CrmProject < ActiveRecord::Base
has_one :crm_team
team model
class CrmTeam < ActiveRecord::Base
belongs_to :crm_project
accepts_nested_attributes_for :crm_project
belongs_to :crm_section
belongs_to :manager, class_name: "User"
has_many :users
accepts_nested_attributes_for :users
end
When submitting the form to create a new team i get this error:
ActiveModel::MissingAttributeError in CrmTeamsController#create
can't write unknown attribute `crm_team_id`
and log from server :
Parameters: {"crm_team"=>{"crm_project"=>"5", "manager"=>"3", "user_ids"=>["", "2"]}, "co
mmit"=>"Create Crm team"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1
[["id", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = 'email#gmail.com' AND "users"."
id" != 2) LIMIT 1
SQL (0.2ms) INSERT INTO "crm_teams" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2020-03-1
1 07:29:19.735115"], ["updated_at", "2020-03-11 07:29:19.735115"]]
(0.2ms) rollback transaction
Completed 500 Internal Server Error in 9ms (ActiveRecord: 1.0ms)
When you use a has_one relation like this
has_one :crm_team
Rails expects you to add crm_team_id to your crm_project, in order to understand which crm_team is related with crm_project object. Adding it going to solve your problem.
In my opinion defining opposite like this much better in logic. Because in future these teams can have multiple projects.
class CrmProject < ActiveRecord::Base
belongs_to :crm_team
class CrmTeam < ActiveRecord::Base
***has_one/has_many(pick one)*** :crm_project
accepts_nested_attributes_for :crm_project
belongs_to :crm_section
belongs_to :manager, class_name: "User"
has_many :users
accepts_nested_attributes_for :users
end
class Project < ActiveRecord::Base
has_many :photos, -> { order(main: :desc, id: :asc) }, as: :photoable, class_name: 'Photo', dependent: :destroy
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
projects = Project.limit(10).includes(:photos)
SELECT "projects".* FROM "projects" WHERE "projects"."is_deleted" = $1 LIMIT 10 [["is_deleted", "f"]]
Photo Load (0.5ms) SELECT "photos".* FROM "photos" WHERE "photos"."photoable_type" = 'Project' AND "photos"."photoable_id" IN (1, 403, 371, 8784, 12, 34, 11, 1111, 31, 22) ORDER BY "photos"."main" DESC, "photos"."id" ASC
projects.first.photo
Photo Load (0.6ms)
It is sending a DB query which gets executed in (0.6ms). Any idea how i can avoid a DB query ?
I'm using Rails 4.2.6 & Ruby 2.3.1p112
projects = Project.includes(:photos).limit(10)
Then iterate through the projects. It wont trigger another query.
projects.each do |project|
project.photo
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 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