【问题标题】:Rails 4 has_and_belongs_to_many doesn't work properly with includes statementRails 4 has_and_belongs_to_many 不适用于包含语句
【发布时间】:2014-12-17 21:08:05
【问题描述】:

最近我遇到了一个关于 rails 4 和 HABTM 关系的神秘错误。 首先是我的 Gemfile:

source 'https://rubygems.org'
gem 'rails', '~> 4.1.6'
gem 'pg'

接下来。我的模特:

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

原始数据库数据:

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)

终于出了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">],
[],
[],
[]]

如您所见,集合中只返回了第一组教师。但是 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)

以前有人遇到过这样的问题吗?我不明白这里发生了什么。

附:如果你这样做Resource.all.includes(:teachers).map { |r| r.reload.teachers },结果是正确的。然而,它完全消除了include 的意义,并提供了 N+1 问题。

更新:还有一个值得一提的发现。如果我删除 STI,一切正常。

【问题讨论】:

  • 只想在这里提一下has_and_belongs_to_many 是一种老式的多对多关系方式。更好的选择是has_many :throughSource
  • 只想提一下has_and_belongs_to_many 没有什么“老派”,您应该使用最适合您任务的关联类型。在很多方面,HABTM 都很好。

标签: ruby-on-rails postgresql ruby-on-rails-4 rails-activerecord has-and-belongs-to-many


【解决方案1】:

我在 Rails 4.1.6 中使用 pg gem 重新创建了那些 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">]]

【讨论】:

    【解决方案2】:

    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
    

    eager_load 也适用于这两种情况(在连接表上使用或不使用id),您可以获得单个 SQL 查询的好处:

    Resource.eager_load(:teachers).map(&:teachers).map(&:to_a)
    

    输出:

    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">]]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-21
      • 2014-02-17
      相关资源
      最近更新 更多