我有一个具有一系列角色的用户模型。
来自我的schema.db:
create_table "users", force: true do |t|
t.string "roles", array: true
我的模型看起来像这样:
class User < ActiveRecord::Base
ROLES = %w(superadmin sysadmin secretary)
validate :allowed_roles
after_initialize :initialize_roles, if: :new_record?
private
def allowed_roles
roles.each do |role|
errors.add(:roles, :invalid) unless ROLES.include?(role)
end
end
def initialize_roles
write_attribute(:roles, []) if read_attribute(:roles).blank?
end
问题是当我尝试从控制台添加另一个角色时,例如user.roles&lt;&lt; “new_role”然后user.save!说是真的,并要求user.roles给我我想要的输出。但是当我问User.find(user_id).roles然后我得到之前没有“new_role”的状态。
对于前。
user.roles
=> ["superadmin"]
user.roles << "secretary"
=> ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin"]
当替换整个数组时,它可以按我的意愿工作:
user.roles
=> ["superadmin"]
user.roles = ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin", "secretary"]
我正在使用rails 4和postgresql,角色是针对cancancan gem。
更改其他字段,例如user.name,以表示预期。我在谷歌上做了很多挖掘,但没有帮助。
答案 0 :(得分:5)
Active Record会跟踪哪些列已更改,并仅将这些列保存到数据库中。此更改跟踪通过挂钩到setter方法来工作 - 未检测到原位对象变异。例如
user.roles << "superuser"
不会被检测为更改。
有两种解决方法。一个是永远不会改变任何Active Record对象属性。在你的情况下,这将意味着轻微的笨拙
user.roles += ["superuser"]
如果您无法做到这一点,那么您必须告诉Active Record你做了什么,例如
user.roles.gsub!(...)
user.roles_will_change!
让Active Record知道roles属性已更改并需要更新。
如果Active Record能更好地处理这个问题会更好 - 当阵列列中的更改跟踪不受支持时(当时mysql占据了大部分注意力)
另一种方法是将这些列标记为始终需要保存(非常类似于序列化属性所发生的那样),但是您需要为此修补补丁活动记录。
答案 1 :(得分:1)
Frederick的回答让我感到兴奋,我写了一个简单的gem deep_dirty,通过将当前属性值与来自*_before_type_cast
的重组值进行比较,提供深度脏检查。要在ActiveRecord模型上自动执行此操作,gem会设置before_validation
回调。
用法
gem 'deep_dirty'
class User < ActiveRecord::Base
include DeepDirty
end
user.roles << 'secretary'
user.changed? # => false
user.valid? # => true
user.changed? # => true
此外,深度检查可以在没有验证的情况下启动:
user.changed? # => false
user.deep_changed? # => true
user.changed? # => true
查看github上的源代码:borgand/deep_dirty