我正在现有数据库上构建此RoR站点。数据库上的用户模型有一个名为“secret”的列,它是一个按位整数,用于保存用户设置为secret(名字,姓氏等)的列的信息。
变量是2的幂,例如:姓氏= 1<< 1 = 2,名字= 1<< 2 = 4,电子邮件== 1<< 3 = 8等等。所以如果用户设置了名字&电子邮件为秘密,列值变为4 + 8 = 12.
现在,我正在尝试找到一种将这些虚拟列实现为Rails模型的通用方法。所以,我可以做(只是一个虚拟的例子,重点是,我想检索和存储状态):
if user.secret_email?
user.secret_name_last = true
user.secret_name_first = false
end
如何将这些虚拟列整齐地实现到模型(不修改现有数据库)?目前我已经跟随了。它有效,但它并不整洁。由于我有20个秘密列,代码看起来非常丑陋。
SECRET_NAME_LAST = (1 << 1) # 2
attr_accessible :secret_name_last
def secret_name_last; secret & SECRET_NAME_LAST > 0 unless secret.nil?; end
def secret_name_last=(value); secret_set_value(SECRET_NAME_LAST, value); end
SECRET_NAME_FIRST = (1 << 2) # 4
attr_accessible :secret_name_first
def secret_name_first; secret & SECRET_NAME_FIRST > 0 unless secret.nil?; end
def secret_name_first=(value); secret_set_value(SECRET_NAME_FIRST, value); end
SECRET_EMAIL = (1 << 3) # 8
attr_accessible :secret_email
def secret_email; secret & SECRET_EMAIL > 0 unless secret.nil?; end
def secret_email=(value); secret_set_value(SECRET_EMAIL, value); end
***snip (17 more)***
private
def secret_set_value(item, value)
if self.secret.nil?
self.secret = 0
end
if value == "1" || value == true || value == 1
# Add item to secret column (if it doesn't exist)
if self.secret & item == 0
self.secret += item
end
else
# Remove item from secret column (if it exists)
if self.secret & item > 0
self.secret -= item
end
end
end
我可以做的事情很好:
as_bitwise :secret_name_first, :column=>'secret', :value=>4
as_bitwise :secret_name_last, :column=>'secret', :value=>2
甚至,
as_bitwise :secret, { :secret_name_last=>4, :secret_name_first=>2 }
修改
根据Brandan的优秀答案,这就是我目前所拥有的:
module BitwiseColumn
extend ActiveSupport::Concern
module ClassMethods
def bitwise_column(*args)
mapping = args.extract_options!
column_name = args.shift
real_column_name = args.shift
logger.debug "Initializing bitwisecolumn, column: " + column_name.to_s
mapping.each_pair do |attribute, offset|
logger.debug "\tSetting a pair: offset: " + offset.to_s + ", " + attribute.to_s
mask = 2 ** offset
class_eval %{
attr_accessible :#{column_name}_#{attribute}
def #{column_name}_#{attribute}?
#{real_column_name} & #{mask} > 0 unless #{real_column_name}.nil?
end
def #{column_name}_#{attribute}=(value)
if self.#{real_column_name}.nil?
self.#{real_column_name} = 0
end
if value == "1" || value == true || value == 1
if self.#{real_column_name} & #{mask} == 0
self.#{real_column_name} += #{mask}
end
else
if self.#{real_column_name} & #{mask} > 0
self.#{real_column_name} -= #{mask}
end
end
end
}
end
end
end
end
这允许我使用:
bitwise_column :secret, :realsecretcolumnatdatabase, :name_last=>1, :name_first=>2, :email=>3, :picture=>5, :dob=>6, :place=>12
之后,我可以调用User.first.secret_name_last?等
答案 0 :(得分:3)
您可以使用class_eval
来干预您的代码。我还建议将此行为分解为与User
类分开的某种模块,以便您可以彻底地测试它并与其他User
特定行为分开。
与您一样,我倾向于使用所需的API启动这些类型的任务并向后工作。我在我的模型中开始使用它:
class User < ActiveRecord::Base
include BitwiseColumn
bitwise_column :secret, :first_name => 1, :last_name => 2
end
传递给bitwise_column
的哈希将虚拟属性名称映射为其掩码值作为指数。我觉得这比自己记住2的力量更容易管理: - )
然后我创建了mixin:
module BitwiseColumn
extend ActiveSupport::Concern
module ClassMethods
def bitwise_column(*args)
mapping = args.extract_options!
column_name = args.shift
mapping.each_pair do |attribute, offset|
mask = 2 ** offset
class_eval %{
def secret_#{attribute}?
#{column_name} & #{mask} > 0 unless #{column_name}.nil?
end
def secret_#{attribute}=(value)
if self.#{column_name}.nil?
self.#{column_name} = 0
end
if value == "1" || value == true || value == 1
if self.#{column_name} & #{mask} == 0
self.#{column_name} += #{mask}
end
else
if self.#{column_name} & #{mask} > 0
self.#{column_name} -= #{mask}
end
end
end
}
end
end
end
end
这个mixin为每个虚拟属性创建两个实例方法,一个用?
,一个用=
,因为这似乎就是你所追求的。我使用现有的逻辑进行按位运算,这似乎完美无缺。