ruby-on-rails - 具有取决于数据库的条件范围的 Rails 模型

标签 ruby-on-rails ruby oracle scopes

我正在为基于 this railscast 的模型实现范围.作用域中的条件使用二进制 AND 运算符 &,如下所示:

scope :with_role, lambda { |role| 
  {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0"} 
}

由于我目前针对的数据库是 Oracle,它使用 BITAND 函数而不是 & 运算符,因此我重写了这样的条件:

{:conditions => "BITAND(roles_mask, #{2**ROLES.index(role.to_s)}) > 0"}

我的问题是,我想让我的代码尽可能与数据库无关,因为将来我们计划以其他数据库为目标。我目前的解决方案是检查我是否在使用 Oracle 并相应地定义范围,如下所示(using_oracle 是我在别处计算的 bool 值):

if using_oracle 
  scope :with_role, lambda { |role| 
    {:conditions => "BITAND(roles_mask, #{2**ROLES.index(role.to_s)}) > 0"}
  }
else
  scope :with_role, lambda { |role| 
    {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0"}
  }
end

这行得通,但并没有给我留下特别优雅或类似 ruby​​/rails 的印象。谁能建议更好的选择?

最佳答案

我认为应该有更好的方法来扩展 Arel:我努力达到这个结果。

无论如何;此解决方案使用 Model#extending :

module BitOperations
  def bitwise_and_sql
    @bitwise_and_sql ||=
      case connection.adapter_name
      when 'Oracle' # probably wrong!
        "BITAND(%s, %s)"
      else
        "%s & %s"
      end
  end
  def bitwise_and(i, j)
    where(bitwise_and_sql % [i, j])
  end
  def bitmask(i, j)
    where('%s > 0' % scoped.bitwise_and(i, j).wheres.to_a.last.to_sql)
  end
end

p User.scoped.extending(BitOperations).bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

.wheres 包含 Arel 关系;它包括 Enumerable,因此我们可以检索最后一个关系,将其转换为数组并获取最后一个元素。我用它来获取 bitwise_and(i, j) 的 sql,以便在 bitmask(i, j) 中使用它。我想知道是否有更好的方法从哪里获取 sql...

.wheres 引发关于 wheres 弃用的警告,目前可以忽略(它也适用于 Rails 4 beta)。

您可以为 User 定义类方法:

class User
  def self.scope_with_bit_operations
    @scope_with_bit_operations ||= scoped.extending(BitOperations)    
  end
  def self.bitwise_and(i, j)
    scope_with_bit_operations.bitwise_and(i, j)
  end
  def self.bitmask(i, j)
    scope_with_bit_operations.bitmask(i, j)
  end
end

p User.bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

或者对于你所有的模型:

class ActiveRecord::Base
  def self.scope_with_bit_operations
    @scope_with_bit_operations ||= scoped.extending(BitOperations)    
  end
  def self.bitwise_and(i, j)
    scope_with_bit_operations.bitwise_and(i, j)
  end
  def self.bitmask(i, j)
    scope_with_bit_operations.bitmask(i, j)
  end
end

p Post.bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"posts\".* FROM \"posts\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

最后你可以实现一个更优雅的 with_role 作用域:

class User < ActiveRecord::Base
  ROLES = %w[admin moderator author]

  scope :with_role, ->(role) do
    # I'm a fan of quoting everything :-P
    bitmask connection.quote_column_name(:roles_mask),
            connection.quote(2**ROLES.index(role.to_s))
  end
end

p User.with_role('admin').to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE ((\"roles_mask\" & 1) > 0)"

我必须说 IMO 这更像是一个概念验证:如果您不打算在其他模型中重用 bitwise_andbitmask需要对它们进行抽象,所以您最好使用类似于您的 scope 的东西,f.e.像这样:

class User < ActiveRecord::Base
  ROLES = %w[admin moderator author]

  BITMASK_SQL =
    case connection.adapter_name
    when 'Oracle' # probably wrong!
      "BITAND(%s, %s) > 0"
    else
      "%s & %s > 0"
    end

  scope :with_role, ->(role) do
    where BITMASK_SQL % [ connection.quote_column_name(:roles_mask), 
                          connection.quote(2**ROLES.index(role.to_s)) ]
  end
end

p User.with_role('admin').to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (\"roles_mask\" & 1 > 0)"

我认为规则是在需要时添加抽象,不需要时不添加(我不知道这句话的英文是否正确:-))

我想说另外一件事:由于您是 Ruby/Rails 的新手,我建议您阅读大量的 Rails & c。代码; IMO 这是了解 Rails 工作原理的最佳方式(这就是我花时间回答您的问题的原因:因为我对 Arel 关系的 Rails 管理感到好奇:-))。

关于ruby-on-rails - 具有取决于数据库的条件范围的 Rails 模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15304979/

相关文章:

oracle - PL/SQL 数组到 CLOB

ruby-on-rails - counter_cache 在 after_create 钩子(Hook)中是陈旧的

ruby-on-rails - 运行 rake db :create in test environment 时,字符串无法强制转换为整数

ruby-on-rails - 如何从 format.xml 中删除 <hash></hash>

ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试?

sql - 为什么 SQL 成本会因为简单的 "or"而爆炸?

mysql - docker rails无法连接mysql

ruby - 你能在 Ruby 中使用分号吗?

ruby-on-rails - Ruby 导入项目要求 Puma

sql - CASE和COALESCE短路评估适用于PL/SQL中的序列,但不适用于SQL中的序列