有条件地为活动记录中的多对多关系创建新记录

Conditionally create new records for many to many relationship in activerecords

我正在与 ActiveRecord 一起从事 RoR 项目,我对这些技术还很陌生。

我有两个模型:

User.rb

class User < ApplicationRecord
...
has_and_belongs_to_many :roles
...

Role.rb

class Role < ApplicationRecord
...
has_and_belongs_to_many :users
...

所以现在我正在创建一个新角色:

Role.where(name: 'my_new_role').first_or_create!

并且我想创建一个 rake 任务,它将为已经拥有 admin 角色的用户和所有其他没有 unfit_role 角色的用户添加这个角色。

如果我使用纯 SQL 执行此操作,我很可能会执行 select 来收集我要更新的所有用户 ID,然后对我的所有 ID 执行另一个更新查询'已收集。

但是我觉得 ActiveRecord 有一些细节我还不太理解,但我决定尝试其他方法。

目前我有这个代码:

new_role = Role.where(name: :my_new_role).first

User.all.each do |entity|
  entity.roles << new_role if entity.has_role?('admin')
  entity.roles << new_role if !entity.has_role?('unfit_role')
end

has_role 是简单的辅助方法,它 select 所有角色一次,并使用 include? 检查给定角色是否在 selected 角色列表中.

基于 1-2 次试运行,似乎这确实有效,但我真的不喜欢这个实现。主要是我检查某个用户(实体)是否具有给定角色的方式。我觉得这是执行相对简单任务的一种非常低效的方法,所以我想知道如何以不同的方式实现它以便更快。

您可以使用 Postgres aggregate functions 来提高查询效率:

new_role = Role.where(name: 'my_new_role').first!
admin_role = Role.where(name: 'admin').first!
unfit_role = Role.where(name: 'unfit_role').first!

User.joins(:roles).group(:id).having("bool_or(roles.id = #{admin_role.id}) or not bool_or(roles.id = #{unfit_role.id})").each do |entity|
  entity.roles << new_role
end