设计模型多次运行before_save?

我的客户希望加密所有用户数据,所以我创建了一个before_saveafter_find回调,它将使用Gibberish加密某些属性:

  # user.rb before_save UserEncryptor.new after_find UserEncryptor.new # user_encryptor.rb class UserEncryptor def initialize @cipher = Gibberish::AES.new("password") end def before_save(user) user.first_name = encrypt(user.first_name) user.last_name = encrypt(user.last_name) user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email end def after_find(user) user.first_name = decrypt(user.first_name) user.last_name = decrypt(user.last_name) user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email end private def encrypt(value) @cipher.enc(value) end def decrypt(value) @cipher.dec(value) end end 

好吧,当用户首次使用Devise ,模型看起来应该是这样的。 但是,一旦用户确认,如果我检查用户,则first_namelast_name属性看起来已经多次加密。 所以我在before_save方法中放了一个断点,然后单击确认链接,我看到它连续执行了三次。 结果是加密值再次加密,然后再次加密,因此下次我们检索记录时,每次我们获得两次加密值。

现在,为什么会发生这种情况呢? 对于执行相同逻辑的其他非设计模型,不会发生这种情况。 Devise是否将current_user缓存在几个不同的位置,并将用户保存在每个位置? 如何在执行下一个before_find之前调用before_save回调3次?

而且,更重要的是,当我使用Devise时,如何成功加密我的用户数据? 我也遇到了attr_encrypteddevise_aes_encryptable问题,所以如果我得到很多这些建议,那么我想我还有一些问题需要发布:-)

我在一位同事的帮助下解决了我的问题。

对于加密名字和姓氏,只需在模型中添加一个标志即表明它是否已加密。 这样,如果发生多次保存,模型就知道它已经加密,可以跳过该步骤:

  def before_update(user) unless user.encrypted user.first_name = encrypt(user.first_name) user.last_name = encrypt(user.last_name) user.encrypted = true end end def after_find(user) if user.encrypted user.first_name = decrypt(user.first_name) user.last_name = decrypt(user.last_name) user.encrypted = false end end 

对于电子邮件地址,这还不够。 通过重置缓存值,Devise正在做一些非常奇怪的事情,因此电子邮件地址仍然是双重加密的。 因此,我们不是挂钩回调来加密电子邮件地址,而是覆盖了用户模型上的一些方法:

  def email_before_type_cast super.present? ? AES.decrypt(super, KEY) : "" end def email return "" unless self[:email] @email ||= AES.decrypt(self[:email], KEY) end def email=(provided_email) self[:email] = encrypted_email(provided_email) @email = provided_email end def self.find_for_authentication(conditions={}) conditions[:email] = encrypted_email(conditions[:email]) super end def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email] super end def self.encrypted_email decrypted_email AES.encrypt(decrypted_email, KEY, {:iv => IV}) end 

这让我们大部分都在那里。 但是,我的Devise模型是可重新配置的,所以当我更改用户的电子邮件地址并尝试保存时,可重新配置的模块遇到了一些时髦的事情,记录被保存了大约一百次左右,然后我得到了堆栈溢出和回滚。 我们发现我们需要在用户模型上覆盖另外一个方法才能实现这一目的:

  def email_was super.present? ? AES.decrypt(super, KEY) : "" end 

现在我们所有的个人身份信息都已加密! 好极了!