有一个关于从类似的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
您可以看到hash
和eql?
仅评估class
和id
。
这意味着只有当两个元素中的任何对象具有相同对象的 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
: 1Bob
的 id 来自users_with_multiple_emails
: nil