查找 vs 查找_by vs where

177

我刚接触Rails,发现有很多方法可以查找记录:

  1. find_by_<columnname>(<columnvalue>)
  2. find(:first, :conditions => { <columnname> => <columnvalue> }
  3. where(<columnname> => <columnvalue>).first

它们看起来生成的SQL完全相同。同时,我认为查找多条记录也是同样的情况:

  1. find_all_by_<columnname>(<columnvalue>)
  2. find(:all, :conditions => { <columnname> => <columnvalue> }
  3. where(<columnname> => <columnvalue>)

有没有一些经验法则或建议指导我们选择其中的一个呢?

13个回答

155

where会返回ActiveRecord::Relation。

现在看一下find_by的实现:

def find_by
  where(*args).take
end

正如你所看到的,find_bywhere 是相同的,但是它只返回一个记录。这个方法应该用于获取一个记录,而where 应该用于获取满足某些条件的所有记录。


3
find_by 返回一个对象,而 where 返回一个集合。 - Kick Buttowski
当查询值超出范围时,find_by将从where(*args)中救援::RangeError并返回nil。 - Fangxing

137

编辑: 这个回答非常老了,自这篇文章发表以来出现了其他更好的答案。我建议查看下面@ Hossam Khamis发布的答案以获取更多详细信息。

使用您觉得最适合您需求的任何一个。

find 方法通常用于通过 ID 检索行:

Model.find(1)

值得注意的是,如果您提供的属性未找到项目,则find将抛出异常。使用下面描述的where(如果未找到属性,则返回空数组)来避免抛出异常。

find的其他用途通常被替换为类似以下内容的东西:

Model.all
Model.first

find_by 用于在列中搜索信息时作为帮助程序,并根据命名约定进行映射。例如,如果您的数据库中有一个名为name的列,则可以使用以下语法:

Model.find_by(name: "Bob")

.where更像是一个万能的方法,当传统的帮助方法无法满足时,可以使用更复杂的逻辑,它返回匹配您条件的项目数组(否则返回空数组)。


65
find_by 方法并未被废弃,但语法稍作改变。从 find_by_name("Bob") 改为 find_by(:name, "Bob") - Brian Morearty
69
@BrianMorearty 我相信你的意思是 find_by(name: "Bob") - MCB
1
@BrianMorearty 我找不到 find_by_... 被弃用的任何证据,你有来源吗?在 Rails 4 中,似乎 find_byfind_by_... 都仍然受支持。 - Dennis
4
@ Dennis,你说得对,它没有被弃用,这也是我所说的。但是当我说“但语法正在发生一些变化”时,我可能可以更清楚一些。我的意思是,“现在也有一种新的语法可用”。请参见MCB对新语法的更正。 - Brian Morearty
3
这是在Rails 4.0发布中提到的内容:“除了find_by_...和find_by_...!之外,所有动态方法都已被弃用”。更多详情请参见http://edgeguides.rubyonrails.org/4_0_release_notes.html#active-record-deprecations。 - Mukesh Singh Rathaur
显示剩余2条评论

72

Model.find

1- 参数:要查找的对象的ID。

2- 如果找到:它返回该对象(仅一个对象)。

3- 如果未找到:抛出ActiveRecord :: RecordNotFound异常。

Model.find_by

1- 参数:键/值

示例:

User.find_by name: 'John', email: 'john@doe.com'

2- 如果找到:它返回对象。

3- 如果未找到:返回nil

注意:如果想要抛出ActiveRecord::RecordNotFound,请使用find_by!

Model.where

1- 参数:与find_by相同

2- 如果找到:它返回包含一个或多个与参数匹配的记录的ActiveRecord::Relation

3- 如果未找到:它返回一个空的ActiveRecord::Relation


37

findfind_by之间有一个区别,即find在找不到记录时会返回错误,而find_by会返回null。

有时,如果你有像find_by email: "haha"这样的方法,阅读起来会更容易,而不是使用.where(email: some_params).first


18

从Rails 4开始,你可以这样做:

User.find_by(name: 'Bob')

在Rails 3中,等效于find_by_name的是什么。

#find#find_by不足以满足需求时,请使用#where


2
Agis,我同意你的观点,但是我一直在网上搜索为什么我们应该使用find_by而不是find_by_<column_name>。我需要这个答案来回复某人。 - Kashif Umair Liaqat
Agis,你有任何来源可以支持你的说法吗?即我们在Rails 4中不再应该使用find_by_name。据我所知,[它还没有被弃用](https://github.com/rails/activerecord-deprecated_finders)。 - Dennis
有没有一种简单的方式来做到这一点,但是名称中带有通配符,例如 find_by(name: "Rob*") - Batman
1
@Dennis 可以同时使用两种语法,它们都是有效的。我更喜欢新的语法,因为在我看来 API 更直观。这就是我自己设计的方式 :) - Agis

8

通常被接受的答案已经涵盖了所有内容,但是我想补充一些内容,以防你计划以某种方式与模型一起工作,例如更新,并且你正在检索单个记录(其id你不知道),那么find_by是正确的方法,因为它检索记录并不将其放入数组中。

irb(main):037:0> @kit = Kit.find_by(number: "3456")
  Kit Load (0.9ms)  SELECT "kits".* FROM "kits" WHERE "kits"."number" = 
 '3456' LIMIT 1
=> #<Kit id: 1, number: "3456", created_at: "2015-05-12 06:10:56",   
updated_at: "2015-05-12 06:10:56", job_id: nil>

irb(main):038:0> @kit.update(job_id: 2)
(0.2ms)  BEGIN Kit Exists (0.4ms)  SELECT 1 AS one FROM "kits" WHERE  
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.5ms)   
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE  "kits"."id" = 
1  [["job_id", 2], ["updated_at", Tue, 12 May 2015 07:16:58 UTC +00:00]] 
(0.6ms)  COMMIT => true

但是,如果您使用 where,则无法直接更新它。

irb(main):039:0> @kit = Kit.where(number: "3456")
Kit Load (1.2ms)  SELECT "kits".* FROM "kits" WHERE "kits"."number" =  
'3456' => #<ActiveRecord::Relation [#<Kit id: 1, number: "3456", 
created_at: "2015-05-12 06:10:56", updated_at: "2015-05-12 07:16:58", 
job_id: 2>]>

irb(main):040:0> @kit.update(job_id: 3)
ArgumentError: wrong number of arguments (1 for 2)

在这种情况下,您需要像这样指定它。
irb(main):043:0> @kit[0].update(job_id: 3)
(0.2ms)  BEGIN Kit Exists (0.6ms)  SELECT 1 AS one FROM "kits" WHERE 
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.6ms)   
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" = 1  
[["job_id", 3], ["updated_at", Tue, 12 May 2015 07:28:04 UTC +00:00]]
(0.5ms)  COMMIT => true

正是我所期待的。 - Paul Brunache
2
@kit = Kit.where(number: "3456").first这样可以直接更新它,而且更安全,因为它经受住了弃用的考验。 - Paul Brunache

8
到目前为止,所给出的答案都是正确的。然而,有一个有趣的区别:`Model.find` 通过id进行查找;如果找到,它将返回一个 `Model` 对象(即单个记录),否则会抛出 `ActiveRecord::RecordNotFound` 异常。`Model.find_by`与`Model.find`非常相似,并允许您在数据库中搜索任何列或一组列,但如果没有记录与搜索匹配,则返回`nil`。另一方面,`Model.where` 返回一个 `Model::ActiveRecord_Relation` 对象,就像包含所有匹配搜索的记录的数组一样。如果未找到记录,则返回一个空的 `Model::ActiveRecord_Relation` 对象。希望这些内容能帮助您在任何时候决定使用哪种方法。

8
除了被接受的答案之外,以下也是有效的: Model.find()可以接受ID数组,将返回所有匹配的记录。 Model.find_by_id(123) 也可以接受数组,但只处理数组中第一个 ID 值。
Model.find([1,2,3])
Model.find_by_id([1,2,3])

7

假设我有一个模型User

  1. User.find(id)

返回主键等于id的行。返回类型将是User对象。

  1. User.find_by(email:"abc@xyz.com")

返回第一行匹配属性或电子邮件的值。返回类型也将是User对象。

注意: User.find_by(email: "abc@xyz.com") 类似于User.find_by_email("abc@xyz.com")

  1. User.where(project_id:1)

返回用户表中所有属性匹配的用户

返回类型将是ActiveRecord :: Relation 对象。 ActiveRecord :: Relation 类包括Ruby的 Enumerable 模块,因此您可以像使用数组一样对其进行遍历。


5

2
优秀的帖子不存在。 - Nithin
1
https://web.archive.org/web/20150206131559/http://m.onkey.org/active-record-query-interface - Samet Gunaydin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接