在替换整个数组时,向postgres数组字段添加元素失败

时间:2014-08-05 15:22:44

标签: ruby postgresql ruby-on-rails-4 cancan

我有一个具有一系列角色的用户模型。

来自我的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,以表示预期。我在谷歌上做了很多挖掘,但没有帮助。

2 个答案:

答案 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