Active Record has_many生成带有外键IS NULL的sql

lud*_*dde 5 activerecord ruby-on-rails foreign-keys has-many relationship

我不希望使用NULL作为外键的模型属于任何东西!

我有以下rails应用程序,模拟蚂蚁和蚂蚁山(灵感来自Jozef).

$ rails -v
Rails 3.2.8
$ rails new ant_hill
$ cd ant_hill
Run Code Online (Sandbox Code Playgroud)

创建蚂蚁山和蚂蚁模型.蚂蚁可以属于蚂蚁山,蚂蚁山可以有许多蚂蚁.

$ rails generate model AntHill name:string
$ rails generate model Ant name:string ant_hill_id:integer
$ vim app/models/ant.rb
$ cat app/models/ant.rb
class Ant < ActiveRecord::Base
  belongs_to :ant_hill
end
$ vim app/models/ant_hill.rb
$ cat app/models/ant_hill.rb
class AntHill < ActiveRecord::Base
  has_many :ants
end
$ rake db:migrate
==  CreateAntHills: migrating =================================================
-- create_table(:ant_hills)
   -> 0.0013s
==  CreateAntHills: migrated (0.0016s) ========================================

==  CreateAnts: migrating =====================================================
-- create_table(:ants)
   -> 0.0035s
==  CreateAnts: migrated (0.0037s) ============================================
Run Code Online (Sandbox Code Playgroud)

在控制台中运行以下代码.

$ rails c
Loading development environment (Rails 3.2.8)
Run Code Online (Sandbox Code Playgroud)

创造一些蚂蚁,坚持,不属于任何蚂蚁山.

1.9.2-p290 :001 > Ant.create! name: "August"
 => #<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06">
1.9.2-p290 :002 > Ant.create! name: "Bertil"
 => #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13">
Run Code Online (Sandbox Code Playgroud)

现在实例化一个蚂蚁山,但不要保存它.

1.9.2-p290 :003 > ant_hill = AntHill.new name: "Storkullen"
 => #<AntHill id: nil, name: "Storkullen", created_at: nil, updated_at: nil>
Run Code Online (Sandbox Code Playgroud)

我希望这个蚂蚁山没有任何蚂蚁,它没有.

1.9.2-p290 :004 > ant_hill.ants
 => []
Run Code Online (Sandbox Code Playgroud)

我仍然期望蚂蚁山没有任何蚂蚁,但现在有两只蚂蚁.

1.9.2-p290 :005 > ant_hill.ants.count
   (0.1ms)  SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" IS NULL
 => 2
Run Code Online (Sandbox Code Playgroud)

同样,在处理外键时,它永远不应生成包含"IS NULL"的查询.我的意思是"belongs_to NULL"不能属于任何东西吧?

1.9.2-p290 :006 > ant_hill.ants.all
  Ant Load (0.4ms)  SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" IS NULL
 => [#<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06">, #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13">]
Run Code Online (Sandbox Code Playgroud)

持久化后,它的行为与预期一致.

1.9.2-p290 :007 > ant_hill.save!
 => true
1.9.2-p290 :008 > ant_hill.ants.count
   (0.4ms)  SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" = 1
 => 0
1.9.2-p290 :009 > ant_hill.ants.all
  Ant Load (0.4ms)  SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" = 1
 => []
Run Code Online (Sandbox Code Playgroud)

任何见解?这是预期的行为吗?

Pet*_*own 1

虽然这似乎违反直觉,但我认为鉴于您的示例,这种行为是有道理的。举ant_hill.ants.count个例子。Count 是一种访问数据库的 ActiveRecord 查询方法,您实际上是要求 ActiveRecord 为您提供所有不属于蚁丘的蚂蚁。Rails 只是让你做一些你不应该做的事情,并且不抱怨它。这应该引发异常吗?可能吧。

如果你真的想知道有多少只蚂蚁属于这个 ant_hill 对象,你应该使用 size。它在未持久保存或关联已加载时查询对象,否则查询数据库。

ant_hill.ants.size
Run Code Online (Sandbox Code Playgroud)

解决这个奇怪问题的一种方法是通过验证 ant_hill_id 的存在来使其成为必填字段。

TL;DR 如果父对象未持久化到数据库,请避免使用 ActiveRecord 查询接口。