Rails如何检查数据库中是否存在记录

54

如何高效地检查数据库在处理记录之前是否会返回结果。例如:Truck.where("id = ?", id).select('truck_no').first.truck_no

这个查询可能会返回卡车的信息,也可能不会,具体情况取决于卡车是否存在。那么,在处理这个请求时,如何确保页面不会崩溃呢?如果我使用循环遍历每个卡车并打印出其编号,我该如何在视图和控制器中处理呢?

如果记录不存在,我希望能够打印出一条消息,而不是什么都不显示。


你使用的 Rails 版本是哪个? - Shaunak
2
请查看exists?方法:http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F - cristian
6个回答

115
如果您想检查对象的存在性,为什么不使用exists?
if Truck.exists?(10)
  # your truck exists in the database
else
  # the truck doesn't exist
end

exists? 方法的优点在于不从数据库中选择记录(这意味着比选择记录更快)。 查询如下:

SELECT 1 FROM trucks where trucks.id = 10

您可以在Rails文档中查找更多示例,了解#exists?方法。


4
exists? 不仅局限于对象的主键。你也可以传递这样的查询:Person.exists?(['name LIKE ?', "%#{query}%"]) - Arslan Ali

35

以下是您可以检查的方法。

if Trucks.where(:id => current_truck.id).blank?
  # no truck record for this id
else
  # at least 1 record for this truck
end

where方法返回一个ActiveRecord::Relation对象(类似于包含where结果的数组),它可能为空,但永远不会为nil。


我一直以为它会返回 nil。谢谢你澄清了这一点。 - Bagzli
刚才给它点了踩的人,请留下评论,这样可以帮助理解问题所在。 - Shaunak
8
使用exists?代替blank?。这将执行一个查询来检查对象是否存在于数据库中,而不是检索整个对象。 - Grzegorz Łuszczek
在 Ruby on Rails 中,任何以问号“?”结尾的函数都会返回布尔值,即 true 或 false。无论是 exists? 还是 blank? - Mani
2
@ImranNaqvi 使用 blank? 可能会更糟,因为它会在检查是否存在任何记录之前返回 所有 记录,而 exists? 会向 SQL 语句添加 LIMIT 1,以便最多只加载一条记录。请参见下面 @cristian 的答案。 - jaredsmith

8

OP实际用例解决方案

最简单的解决方案是将您的数据库检查和数据检索合并为一个数据库查询,而不是分别进行数据库调用。您的示例代码接近并传达了您的意图,但在实际语法中有点偏差。

如果你只是简单地执行Truck.where("id = ?", id).select('truck_no').first.truck_no,并且此记录不存在,则在调用truck_no时它将抛出一个nil错误,因为first可能会检索到一个nil记录,如果没有找到与您的条件匹配的记录。

这是因为您的查询将返回与您的条件匹配的对象数组,然后您在该数组上执行first,如果未找到匹配记录,则是nil

一个相当干净的解决方案:

# Note: using Rails 4 / Ruby 2 syntax
first_truck = Truck.select(:truck_no).find_by(id) # => <Truck id: nil, truck_no: "123"> OR nil if no record matches criteria

if first_truck
  truck_number = first_truck.truck_no
  # do some processing...
else
  # record does not exist with that criteria
end

我建议使用干净的语法,它本身就能“注释”,这样其他人就可以确切地知道你想要做什么。

如果你真的想走得更远,你可以为你的Truck类添加一个方法,让它为你完成这个任务并传达你的意图:

# truck.rb model
class Truck < ActiveRecord::Base
  def self.truck_number_if_exists(record_id)
    record = Truck.select(:truck_no).find_by(record_id)
    if record
      record.truck_no
    else
      nil # explicit nil so other developers know exactly what's going on
    end
  end
end

那么你可以这样调用它:
if truck_number = Truck.truck_number_if_exists(id)
  # do processing because record exists and you have the value
else
  # no matching criteria
end
ActiveRecord.find_by 方法将检索与您的条件匹配的第一条记录,否则如果没有符合条件的记录,则返回 nil。请注意,find_bywhere 方法的顺序很重要;您必须在 Truck 模型上调用 select。这是因为当您调用 where 方法时,实际上返回的是一个 ActiveRelation 对象,而这不是您在此处寻找的内容。 请参阅 ActiveRecord API 的 'find_by' 方法 使用 'exists?' 方法的通用解决方案 正如其他贡献者已经提到的那样,exists? 方法专门设计用于检查某些东西是否存在。它不返回值,只确认数据库中是否有与某些条件匹配的记录。
如果您需要验证某些数据的唯一性或准确性,则它非常有用。好处是它允许您使用 ActiveRelation(Record?) where(...) 条件。
例如,如果您有一个带有 email 属性的 User 模型,并且您需要检查电子邮件是否已经存在于数据库中: User.exists?(email: "test@test.com") 使用 exists? 的好处是运行的 SQL 查询是: SELECT 1 AS one FROM "users" WHERE "users"."email" = 'test@test.com' LIMIT 1 这比实际返回数据更有效率。
如果您需要从数据库中实际有条件地检索数据,则不应使用此方法。但是,它非常适用于简单的检查,语法非常清晰,因此其他开发人员确切知道您在做什么。在具有多个开发人员的项目中,使用适当的语法至关重要。编写干净的代码并让代码“自己注释”。

在 Ruby 中,任何返回 true 或 false 的函数(在您的情况下为 truck_number_if_exists(id))都应该以问号结尾。 - Mani
@ImranNaqvi:您是正确的,Ruby 中的约定是将 ? 附加到布尔方法末尾;但是,在这种情况下,truck_number_if_exist(id) 不是布尔方法;如果存在值,则返回一个值,否则返回 nil。因为它不是布尔方法,所以不应在末尾使用 ? - Dan L

4
如果你只是想检查记录是否存在,使用 @cristian 的答案:

Truck.exists?(truck_id) # 返回 true 或 false

但如果卡车存在且您想要访问该卡车,则必须重新查找卡车,这将导致两个数据库查询。如果是这种情况,请使用:

@truck = Truck.find_by(id: truck_id) #returns nil or truck
@truck.nil? #returns true if no truck in db
@truck.present? #returns true if no truck in db

2
您可以直接这样做:
@truck_no = Truck.where("id = ?", id).pluck(:truck_no).first

如果没有找到记录,这将返回nil,否则将返回第一个记录的truck_no

然后在您的视图中,您可以像这样简单地执行:

<%= @truck_no || "There are no truck numbers" %>

如果您想获取并显示多个结果,则在控制器中执行以下操作:
@truck_nos = Truck.where("id = ?", id).pluck(:truck_no)

在您看来:
<% truck_nos.each do |truck_no| %>
  <%= truck_no %>
<% end %>

<%= "No truck numbers to iterate" if truck_nos.blank? %>

谢谢您的建议。我以前不知道pluck方法。 - Bagzli
不用谢。pluck将从您的表中选择单个列。 - Agis

1

Rails有一个persisted?方法,可用于您需要的方式


persisted? 有不同的目的。它用于检查一个对象是否已经被写入数据库。 - Arslan Ali
4
如果卡车曾经被存入数据库,则truck.persisted?会返回true,即使此后它已从数据库中删除。 - Matilda Smeds

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