为什么存储我的ActiveRecord关系在一个记忆变量不工作的方式,我预期的?



编辑:我使用Ruby 2.3.3和Rails 5.2.4.3,供参考。

我有一个名为Event的ActiveRecord模型,在我的数据库中有超过700万条记录。当我在Rails控制台中输入以下命令时,Rails记录器告诉我发生了I/O查找,这大约需要12.0ms:

irb(main):006:0> @events = Event.where("id > 0")
Event Load (12.0ms)  SELECT  `events`.* FROM `events` WHERE (id > 0) LIMIT 11

我的期望是,如果我使用||=操作(如@events ||= 'foobar')有条件地将@events重置为另一个值,我将看到记录到屏幕上的第二个Event Load语句(因为@events已经存在,所以||=意味着不需要计算表达式的后半部分)。然而,如果实际上看到第二次查找发生:

irb(main):007:0> @events ||= 'foobar'
Event Load (0.5ms)  SELECT  `events`.* FROM `events` WHERE (id > 0) LIMIT 11

当然,查找要快得多(0.5msvs12.0ms),但是I/O发生的事实让我感到困惑。我觉得我误解了一些关于ActiveRecord如何对待||=语句的基本知识,但我不确定那是什么。

我的目标是在实例变量中缓存第一个ActiveRecord查询的结果,这样对该实例变量的后续引用将不会调用任何类型的额外I/O调用,因此将节省本来用于此类I/O调用的时间。

编辑:以下是我输入到Rails控制台的完整命令序列的类似版本(这次使用的是我的应用程序的Role模型),以及删节的结果:

irb(main):001:0> @roles = Role.where("id > ?", 0)
(3.9ms)  SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci,  @@SESSION.sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
Role Load (22.0ms)  SELECT  `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>, 
......

irb(main):002:0> @roles ||= :foobar
Role Load (0.4ms)  SELECT  `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>, 
......

编辑:我猜测,也许在底层,Ruby解释器读取x ||= yx = x || y的方式之间可能存在细微的差异(至少对我来说),所以我也尝试了@roles = @roles || :foobar,但我仍然看到了一个记录到REPL的SQL查询。

我认为你所看到的行为与主机有关。如果您在Rails应用程序的上下文中这样做,它会像预期的那样工作。例如,我有一个Client模型,我编写了一个' get_them_all'方法,如下所示:

def self.get_them_all
@clients = Client.where("id > 52000")
puts "got them"
@clients ||= "foobar"
puts "still have them?"
@clients
end

当我在Rails控制台中运行Client.get_them_all时,我看到对数据库的单个查询。同样有趣的是,在两个puts语句之后运行单个查询。Rails只在实际需要使用结果时才访问数据库。在此之前,它只是在@clients变量中有一个我称之为新生的查询。

此行为意味着您可以将Client#get_them_all方法与其他查询片段链接起来,因为它是ActiveRecord::Relation。因此,在rails控制台

$> Client.get_them_all.class.name #=> ActiveRecord::Relation, not Array
$> Client.get_them_all.where(lastName: 'Escobar') # I can append 'where'

负载(和块)应该能帮你解决这个问题。

如果还没有加载记录,则导致从数据库加载记录。如果出于某种原因需要在实际使用之前显式加载一些记录,则可以使用此方法。返回值是关系本身,而不是记录。

https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Relation.html method-i-load

@events = Event.where("id > 0").load
@events ||= 'foobar'

相关内容

  • 没有找到相关文章

最新更新