Recently I ran into mystical bug with rails 4 and HABTM relation. first of all my Gemfile:
最近我遇到了rails 4和HABTM关系的神秘错误。首先是我的Gemfile:
source 'https://rubygems.org'
gem 'rails', '~> 4.1.6'
gem 'pg'
Next. my Models:
下一个。我的模特:
class User < ActiveRecord::Base
end
class Teacher < User
has_and_belongs_to_many :resources, foreign_key: :user_id
end
class Resource < ActiveRecord::Base
has_and_belongs_to_many :teachers, association_foreign_key: :user_id
end
Raw DB data:
原始数据库数据:
select * from resources;
id | created_at | updated_at
----+----------------------------+----------------------------
1 | 2014-10-13 08:24:07.308361 | 2014-10-13 08:24:07.308361
2 | 2014-10-13 08:24:07.889907 | 2014-10-13 08:24:08.156898
3 | 2014-10-13 08:24:08.68579 | 2014-10-13 08:24:08.884731
4 | 2014-10-13 08:24:09.997244 | 2014-10-13 08:24:10.205753
(4 rows)
select * from users;
id | created_at | updated_at | type
----+----------------------------+----------------------------+---------
13 | 2014-10-13 08:24:01.086192 | 2014-10-13 08:24:01.086192 | Teacher
12 | 2014-10-13 08:24:00.984957 | 2014-10-13 08:24:00.984957 | Teacher
2 | 2014-10-13 08:23:59.950349 | 2014-10-16 08:46:02.531245 | Teacher
(3 rows)
select * from resources_users;
user_id | resource_id
---------+-------------
13 | 1
2 | 2
12 | 3
2 | 4
(4 rows)
Finally the bug:
最后这个bug:
➜ rails_test bundle exec rails c
Loading development environment (Rails 4.1.6)
2.1.2 :001 > Resource.all.includes(:teachers).map(&:teachers).map(&:to_a)
Resource Load (0.6ms) SELECT "resources".* FROM "resources"
SQL (1.3ms) SELECT "resources_users".*, "resources_users"."user_id" AS t0_r0, "resources_users"."resource_id" AS t0_r1, "users"."id" AS t1_r0, "users"."created_at" AS t1_r1, "users"."updated_at" AS t1_r2, "users"."type" AS t1_r3 FROM "resources_users" LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id" AND "users"."type" IN ('Teacher') WHERE "users"."type" IN ('Teacher') AND "resources_users"."resource_id" IN (1, 2, 3, 4)
=> [
[#<Teacher id: 13, created_at: "2014-10-13 08:24:01", updated_at: "2014-10-13 08:24:01", type: "Teacher">],
[],
[],
[]]
As you see only first array of teachers returned in collection. However SQL generated by Rails is correct and returns all data:
正如您所看到的,只有第一批教师返回收藏中。但是Rails生成的SQL是正确的并返回所有数据:
SELECT "resources_users".*, "resources_users"."user_id" AS t0_r0, "resources_users"."resource_id" AS t0_r1, "users"."id" AS t1_r0, "users"."created_at" AS t1_r1, "users"."updated_at" AS t1_r2, "users"."type" AS t1_r3 FROM "resources_users" LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id" AND "users"."type" IN ('Teacher') WHERE "users"."type" IN ('Teacher') AND "resources_users"."resource_id" IN (1, 2, 3, 4);
user_id | resource_id | t0_r0 | t0_r1 | t1_r0 | t1_r1 | t1_r2 | t1_r3
---------+-------------+-------+-------+-------+----------------------------+----------------------------+---------
13 | 1 | 13 | 1 | 13 | 2014-10-13 08:24:01.086192 | 2014-10-13 08:24:01.086192 | Teacher
2 | 2 | 2 | 2 | 2 | 2014-10-13 08:23:59.950349 | 2014-10-16 08:46:02.531245 | Teacher
12 | 3 | 12 | 3 | 12 | 2014-10-13 08:24:00.984957 | 2014-10-13 08:24:00.984957 | Teacher
2 | 4 | 2 | 4 | 2 | 2014-10-13 08:23:59.950349 | 2014-10-16 08:46:02.531245 | Teacher
(4 rows)
Did anyone faced such problem before? I can't understand what's going on here.
以前有人遇到过这样的问题吗?我无法理解这里发生了什么。
P.S. If you do Resource.all.includes(:teachers).map { |r| r.reload.teachers }
the result is correct. However it removes sense from include
at all and provides N+1 problem.
附:如果你做Resource.all.includes(:教师).map {| r | r.reload.teachers}结果是正确的。然而,它从包含中消除了感觉,并提供了N + 1问题。
UPDATE: One more finding worth mentioning. If I remove STI everything works fine.
更新:还有一个值得一提的发现。如果我删除STI一切正常。
2 个解决方案
#1
0
I recreated those ActiveRecord
models and database records in Rails 4.1.6 with pg
gem and saw the correct behavior:
我用pg gem在Rails 4.1.6中重新创建了那些ActiveRecord模型和数据库记录,并看到了正确的行为:
irb(main):017:0> Resource.all.includes(:teachers).map(&:teachers).map(&:to_a) Resource Load (0.6ms) SELECT "resources".* FROM "resources" SQL (6.9ms) SELECT "resources_users".*, "resources_users"."id" AS t0_r0, "resources_users"."resource_id" AS t0_r1, "resources_users"."user_id" AS t0_r2, "users"."id" AS t1_r0, "users"."type" AS t1_r1, "users"."created_at" AS t1_r2, "users"."updated_at" AS t1_r3 FROM "resources_users" LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id" AND "users"."type" IN ('Teacher') WHERE "users"."type" IN ('Teacher') AND "resources_users"."resource_id" IN (1, 2, 3, 4) => [[#<Teacher id: 13, type: "Teacher", created_at: "2015-11-05 07:02:59", updated_at: "2015-11-05 07:02:59">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05 07:02:20", updated_at: "2015-11-05 07:02:32">], [#<Teacher id: 12, type: "Teacher", created_at: "2015-11-05 07:03:50", updated_at: "2015-11-05 07:03:50">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05 07:02:20", updated_at: "2015-11-05 07:02:32">]]
irb(main):017:0> Resource.all.includes(:teachers).map(&:teachers).map(&:to_a)Resource Load(0.6ms)SELECT“resources”。* FROM“resources”SQL( 6.9ms)SELECT“resources_users”。*,“resources_users”。“id”AS t0_r0,“resources_users”。“resource_id”AS t0_r1,“resources_users”。“user_id”AS t0_r2,“users”。“id”AS t1_r0, “users”。“type”AS t1_r1,“users”。“created_at”AS t1_r2,“users”。“updated_at”AS t1_r3 FROM“resources_users”LEFT OUTER JOIN“users”ON“users”。“id”=“resources_users “。”user_id“AND”users“。”type“IN('Teacher')WHERE”users“。”type“IN('Teacher')AND”resources_users“。”resource_id“IN(1,2,3,4 )=> [[# <教师id:13,输入:“老师”,created_at:“2015-11-05 07:02:59”,updated_at:“2015-11-05 07:02:59”> ], [# <教师id:2,输入:“老师”,created_at:“2015-11-05 07:02:20”,updated_at:“2015-11-05 07:02:32”> ],[# <老师id:12,输入:“teacher”,created_at:“2015-11-05 07:03:50”,updated_at:“2015-11-05 07:03:50”> ],[# <教师id:2,键入:“老师”,created_at:“2015-11-05 07:02:20“,updated_at:”2015-11-05 07:02:32“> ]]
#2
0
Same bug here with Rails 4.1.6 and pg
, but I can get the correct behavior by not removing the resources_users
id
field in the migration:
与Rails 4.1.6和pg相同的错误,但我可以通过不删除迁移中的resources_users id字段来获得正确的行为:
def change
#create_table :resources_users, id: false do |t|
create_table :resources_users do |t|
t.integer :resource_id
t.integer :user_id
end
end
Also eager_load
works in both cases (with or without id
on the join table) and you have the benefits of a single SQL query:
此外,eager_load在两种情况下都有效(在连接表上有或没有id),并且您可以获得单个SQL查询的好处:
Resource.eager_load(:teachers).map(&:teachers).map(&:to_a)
output:
irb(main):002:0> Resource.eager_load(:teachers).map(&:teachers).map(&:to_a)
SQL (1.0ms) SELECT "resources"."id" AS t0_r0, "resources"."created_at"
AS t0_r1, "resources"."updated_at" AS t0_r2, "users"."id" AS t1_r0,
"users"."type" AS t1_r1, "users"."created_at" AS t1_r2,
"users"."updated_at" AS t1_r3 FROM "resources" LEFT OUTER JOIN
"resources_users" ON "resources_users"."resource_id" = "resources"."id"
LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id"
AND "users"."type" IN ('Teacher')
=> [[#<Teacher id: 1, type: "Teacher", created_at: "2015-11-05
15:02:33", updated_at: "2015-11-05 15:02:33">], [#<Teacher id: 2, type:
"Teacher", created_at: "2015-11-05 15:02:33", updated_at: "2015-11-05
15:02:33">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05
15:02:33", updated_at: "2015-11-05 15:02:33">], [#<Teacher id: 3, type:
"Teacher", created_at: "2015-11-05 15:02:33", updated_at: "2015-11-05
15:02:33">]]
#1
0
I recreated those ActiveRecord
models and database records in Rails 4.1.6 with pg
gem and saw the correct behavior:
我用pg gem在Rails 4.1.6中重新创建了那些ActiveRecord模型和数据库记录,并看到了正确的行为:
irb(main):017:0> Resource.all.includes(:teachers).map(&:teachers).map(&:to_a) Resource Load (0.6ms) SELECT "resources".* FROM "resources" SQL (6.9ms) SELECT "resources_users".*, "resources_users"."id" AS t0_r0, "resources_users"."resource_id" AS t0_r1, "resources_users"."user_id" AS t0_r2, "users"."id" AS t1_r0, "users"."type" AS t1_r1, "users"."created_at" AS t1_r2, "users"."updated_at" AS t1_r3 FROM "resources_users" LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id" AND "users"."type" IN ('Teacher') WHERE "users"."type" IN ('Teacher') AND "resources_users"."resource_id" IN (1, 2, 3, 4) => [[#<Teacher id: 13, type: "Teacher", created_at: "2015-11-05 07:02:59", updated_at: "2015-11-05 07:02:59">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05 07:02:20", updated_at: "2015-11-05 07:02:32">], [#<Teacher id: 12, type: "Teacher", created_at: "2015-11-05 07:03:50", updated_at: "2015-11-05 07:03:50">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05 07:02:20", updated_at: "2015-11-05 07:02:32">]]
irb(main):017:0> Resource.all.includes(:teachers).map(&:teachers).map(&:to_a)Resource Load(0.6ms)SELECT“resources”。* FROM“resources”SQL( 6.9ms)SELECT“resources_users”。*,“resources_users”。“id”AS t0_r0,“resources_users”。“resource_id”AS t0_r1,“resources_users”。“user_id”AS t0_r2,“users”。“id”AS t1_r0, “users”。“type”AS t1_r1,“users”。“created_at”AS t1_r2,“users”。“updated_at”AS t1_r3 FROM“resources_users”LEFT OUTER JOIN“users”ON“users”。“id”=“resources_users “。”user_id“AND”users“。”type“IN('Teacher')WHERE”users“。”type“IN('Teacher')AND”resources_users“。”resource_id“IN(1,2,3,4 )=> [[# <教师id:13,输入:“老师”,created_at:“2015-11-05 07:02:59”,updated_at:“2015-11-05 07:02:59”> ], [# <教师id:2,输入:“老师”,created_at:“2015-11-05 07:02:20”,updated_at:“2015-11-05 07:02:32”> ],[# <老师id:12,输入:“teacher”,created_at:“2015-11-05 07:03:50”,updated_at:“2015-11-05 07:03:50”> ],[# <教师id:2,键入:“老师”,created_at:“2015-11-05 07:02:20“,updated_at:”2015-11-05 07:02:32“> ]]
#2
0
Same bug here with Rails 4.1.6 and pg
, but I can get the correct behavior by not removing the resources_users
id
field in the migration:
与Rails 4.1.6和pg相同的错误,但我可以通过不删除迁移中的resources_users id字段来获得正确的行为:
def change
#create_table :resources_users, id: false do |t|
create_table :resources_users do |t|
t.integer :resource_id
t.integer :user_id
end
end
Also eager_load
works in both cases (with or without id
on the join table) and you have the benefits of a single SQL query:
此外,eager_load在两种情况下都有效(在连接表上有或没有id),并且您可以获得单个SQL查询的好处:
Resource.eager_load(:teachers).map(&:teachers).map(&:to_a)
output:
irb(main):002:0> Resource.eager_load(:teachers).map(&:teachers).map(&:to_a)
SQL (1.0ms) SELECT "resources"."id" AS t0_r0, "resources"."created_at"
AS t0_r1, "resources"."updated_at" AS t0_r2, "users"."id" AS t1_r0,
"users"."type" AS t1_r1, "users"."created_at" AS t1_r2,
"users"."updated_at" AS t1_r3 FROM "resources" LEFT OUTER JOIN
"resources_users" ON "resources_users"."resource_id" = "resources"."id"
LEFT OUTER JOIN "users" ON "users"."id" = "resources_users"."user_id"
AND "users"."type" IN ('Teacher')
=> [[#<Teacher id: 1, type: "Teacher", created_at: "2015-11-05
15:02:33", updated_at: "2015-11-05 15:02:33">], [#<Teacher id: 2, type:
"Teacher", created_at: "2015-11-05 15:02:33", updated_at: "2015-11-05
15:02:33">], [#<Teacher id: 2, type: "Teacher", created_at: "2015-11-05
15:02:33", updated_at: "2015-11-05 15:02:33">], [#<Teacher id: 3, type:
"Teacher", created_at: "2015-11-05 15:02:33", updated_at: "2015-11-05
15:02:33">]]