活动记录集合减法



有一个关于从类似的ActiveRecord集合中减去查询的问题。

假设我有一个查询,如下所示:

all_users = User.all
users_with_adequate_reviews = User.joins(:reviews).select("users.id, count(*) as num_reviews").group(:id).having("num_reviews > 5")

如果我做all_users - users_with_adequate_reviews,我会得到我所期望的,即评论计数少于 5 的用户。即使我只从用户(主要是 id(中选择几个属性,ActiveRecord 关系减法如何知道删除类似的记录。正在寻找有关此的文档,但在任何地方都找不到

法在哪里定义?

活动记录关系上的减法在活动记录::D降级模块上定义。

如果你正在挖掘该源代码,你可以看到该方法是从 Array 类委托的。

因此,我们需要挖掘数组的减法,以了解ActiveRecord关系的减法是如何工作的。


数组减法如何工作?

这取自有关数组减法/差分的文档。

阵列差异

返回一个新数组,该数组是原始数组的副本,删除任何 也显示在other_ary中的项目。订单从 原始阵列。

它使用元素的哈希和 eql? 方法来比较元素的效率。

这意味着减法计算两种方法:从每个对象中hash&&eql?来执行任务。


这些方法如何在活动记录对象上工作?

下面的代码取自 ActiveRecord::Core 模块。

def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
!id.nil? &&
comparison_object.id == id
end
alias :eql? :==
def hash
if id
self.class.hash ^ id.hash
else
super
end
end

您可以看到hasheql?仅评估classid

这意味着只有当两个元素中的任何对象具有相同对象的 id 和对象的类时,all_users - users_with_adequate_reviews才会排除某些对象。


另一个示例

irb(main):001:0> users = User.all
User Load (26.4ms)  SELECT  `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">, 
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">, 
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">, 
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]>
irb(main):002:0> users_with_multiple_emails = User.joins(:user_emails).select("users.id, users.name, count(*) as num_emails").group(:id).having("num_emails > 1")
User Load (2.8ms)  SELECT  users.id, users.name, count(*) as num_emails FROM `users` INNER JOIN `user_emails` ON `user_emails`.`user_id` = `users`.`id` GROUP BY `users`.`id` HAVING (num_emails > 1) LIMIT 11
=> #<ActiveRecord::Relation [#<User id: 1, name: "Bob">]>
irb(main):003:0> users - users_with_multiple_emails
=> [
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">, 
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">, 
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]

如您所见all users - users_with_multiple_emails排除了第一个对象(Bob(。

为什么?这是因为来自两个元素的Bob具有相同的 id 和类(id:1,类:用户(

减法如果是这样,则返回不同的结果

irb(main):001:0> users = User.all
User Load (26.4ms)  SELECT  `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">, 
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">, 
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">, 
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]>
irb(main):002:0> users_with_multiple_emails = User.joins(:user_emails).select("users.name, count(*) as num_emails").group(:id).having("num_emails > 1")
User Load (2.3ms)  SELECT  users.name, count(*) as num_emails FROM `users` INNER JOIN `user_emails` ON `user_emails`.`user_id` = `users`.`id` GROUP BY `users`.`id` HAVING (num_emails > 1) LIMIT 11
=> #<ActiveRecord::Relation [#<User id: nil, name: "Bob">]>
irb(main):003:0> users - users_with_multiple_emails
=> [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">, 
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">, 
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">, 
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]

这次users_with_multiple_emails只选择名称和num_emails。

如您所见all users - users_with_multiple_emails并不排除Bob.

为什么?这是因为来自两个元素Bob具有不同的 id。

  • Bob的 id 从users: 1
  • Bob的 id 来自users_with_multiple_emails: nil

最新更新