我认为应该有更好的方法来扩展 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
,因此我们可以检索最后一个将其转换为数组并获取最后一个元素的关系。我使用它是为了获取 sqlbitwise_and(i, j)
以便在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_and
并且bitmask
在其他模型中您不需要抽象它们,那么您可能最好使用类似于您的scope
,像这样的东西:
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 如何工作的最佳方式(这就是我花时间回答您的问题的原因:因为我对 Rails 对 Arel 关系的管理感到好奇 :-))。