控制器/ ActiveRecord .where(... NOT IN ...),可能为空

时间:2017-02-01 20:05:50

标签: ruby-on-rails activerecord rails-activerecord

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-linux]
$ rails -v
Rails 4.2.0
$ grep DESCR /etc/lsb-release 
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"

我试图在我的控制器中构建和公开记录子集,如下所示:

@assigned_roles = @user.user_roles.select{|x| x.is_required }.map{ |ur| ur.role }

if @assigned_roles.blank?
  @optional_roles = Role.all
else
  @optional_roles = Role.where('id NOT IN (?)',@assigned_roles.map{ |r| r.id})
end

它有效,但我正在寻找更优雅的东西。

我有角色和用户的模型,@assigned_roles彼此通过have many带有user_roles属性。

对不起我总共n00b所以我不知道还有什么要包括在内。非常感谢!

1 个答案:

答案 0 :(得分:0)

您的问题是ActiveRecord不知道字符串'id NOT IN (?)'的含义,因此它替换?占位符的方法很简单。 AR没有解析SQL片段以查看它应该如何处理占位符,它只知道它应该替换为空数组的占位符,因此它会产生看起来很傻的SQL:

where id not in (null)

Issue when retrieving records with empty array中对此进行了更多讨论。

但是,如果你让ActiveRecord生成SQL,那么它可以做一些合理的事情,因为它理解它正在做什么而不是盲目地甩掉字符串。这曾经是一个丑陋的NOT IN,但现在很容易,因为你可以说.where.not(...)

Role.where.not(:id => @assigned_roles.map(&:id))

如果有@assigned_roles

,那将产生类似的结果
where id not in (6, 11, 23, 42)
如果没有任何@assigned_roles

等等

where 1 = 1

1 = 1只是AR建立一个始终为真的WHERE子句的方式(即它等同于Role.all)。

此外,如果您可以安排@assigned_roles成为关系(即@assigned_roles = SomeModel.where(...).where(...)),那么您可以通过以下方式让ActiveRecord使用子查询:

Role.where.not(:id => @assigned_roles.select(:role_id))

将导致SQL如下:

where id not in (
  select role_id
  from some_models
  where ...
)

将查询逻辑保留在数据库中往往比在数据库和Ruby代码之间进行大量聊天更快。