Модель Rails с условной областью в зависимости от базы данных
Я реализую область для модели, основанной на этом 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
логическое значение, которое я вычисляю в другом месте):
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
Это работает, но не кажется мне особенно элегантным или похожим на рубин / рельсы. Может ли кто-нибудь любезно предложить лучшие альтернативы?
1 ответ
Я думаю, что должен быть лучший способ расширить Арель: я изо всех сил пытался достичь этого результата.
Тем не мение; это решение использует Model # extension:
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
содержит отношения арела; это включает Enumerable
Таким образом, мы можем получить последнее отношение, преобразовав его в массив и получив последний элемент. Я использовал его, чтобы получить sql bitwise_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:-)).