簡體   English   中英

在ActiveRecord模型中隱藏屬性

[英]Hiding an attribute in an ActiveRecord model

我對Rails有點陌生,正在使用ActiveRecord設計用戶模型。 在此模型中,我有一個password屬性,該屬性旨在保留用戶密碼的哈希值。

我想直接刪除該屬性的讀取和設置。 但是,我似乎找不到使用Rails控制台刪除訪問器的方法。 到目前為止,唯一可行的解​​決方案是顯式覆蓋密碼的訪問器方法,但我真的不想覆蓋它們,我希望訪問器消失-至少是讀者。

這是我的模型:

class User < ActiveRecord::Base

  // various associations

  def password_correct?(password)
    read_attribute(:password) == hash(password)
  end

  def password=(password)
    write_attribute(:password, hash(password))
  end

  def password
    "get your dirty fingers off this attribute"
  end

  private

  def hash(input)
    Digest::SHA2.new(512).update(input).hexdigest
  end

end

有什么想法可以實現這種方法或該方法有什么缺點?

基於以上答案,我做了一些實驗以獲得所需的結果。 我最終制作了一個“私有” password_hash屬性和一個名為password的虛擬訪問器。

在此過程中,我做了一些觀察:

  • 看來ActiveRecord沒有任何私有屬性的概念。 不能使用諸如private :password, :password=等符號將訪問器方法設為private :password, :password= ,因為在實例化模型時,Rails會拋出NameError: undefined method ,因為模型本身並未定義這兩種方法(它們似乎是繼承自ActiveRecord::Base )。

  • 完全不使用任何內容覆蓋password_hash訪問器對於防止對該屬性的操縱是很有用的,但是這也意味着ActiveRecord本身在更新password_hash屬性時會失敗,因為它正在調用空實現。

因此,將訪問器設為私有是失敗的,因為在實際模型中未定義訪問器。 定義它們也會失敗,因為它會破壞ActiveRecord。 所以,你可以做什么?

我既做了又多了一點。 我將訪問器設為私有,定義了它們,並通過調用super實現它們。 這可以防止控制器(和Rails控制台)通過拋出NoMethodError來訪問它們,但不會拒絕ActiveRecord。

旁注:驗證問題

我的方法遇到的一個問題是驗證無效。 強制設置password_hash的最小長度是不好的,因為任何密碼(甚至什么也沒有)會導致128個字符的SHA512哈希。 因此,驗證散列幾乎沒有意義。 相反,我向虛擬密碼訪問器添加了驗證,並添加了before_save :hash_password回調,該回調檢查是否已設置虛擬訪問器屬性,如果已設置,則對其進行哈希處理並將其寫入password_hash屬性。

最終實施

我的實現以這種方式結束:

class User < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email
  attr_accessor :password
  validates :password, :length => { :minimum => 8 }, :if => :password_changed?
  validates :first_name, :last_name, :email, presence: true
  # Various associations
  before_save :hash_password

  def password_correct?(p)
    if(password.present?)
      password == p
    else
      read_attribute(:password_hash) == hash_string(p)
    end
  end

  def role_symbols
    roles.collect do |r|
      r.name.to_sym
    end
  end

  private

  def hash_string(input)
    Digest::SHA2.new(512).update(input).hexdigest
  end

  def hash_password
    if(password.present?)
      write_attribute(:password_hash, hash_string(password))
      self.password = nil
    end
  end

  def password_changed?
    password.present? or new_record?
  end

  def password_hash
    super
  end

  def password_hash=(p)
    super
  end

end

您可以使用private方法輕松地將訪問器設為私有,在模型中添加以下行:

private :password, :password=

另外,我建議您不要覆蓋密碼字段訪問器。 也許您可以在數據庫中命名字段password_hash並將此列的訪問器設為私有。 然后,按預期方式編寫方法passwordpassword=

如果它不符合您的需求,請您解釋一下為什么? 以及為什么您想要的將是更好的解決方案?

在尋找應用程序的身份驗證解決方案時,我偶然發現了devise之類的東西,最后采用了http://bcrypt-ruby.rubyforge.org/中舉例說明的這種更簡單的方法。

require 'bcrypt'

class Account < ActiveRecord::Base
  attr_accessor :password, :password_confirmation, :role

  def password
    @password ||= BCrypt::Password.new(crypted_password)
  end

  def password=(new_password)
    @password = BCrypt::Password.create(new_password)
    self.crypted_password = @password
  end
end

編輯:演示,使用ActivecRecord和Postgresql在Padrino應用程序中實現

[6] pry(main)> a = Account.new
=> #<Account id: nil, name: nil, email: nil, role: nil, uid: nil, provider: nil, created_at: nil, updated_at: nil, crypted_password: nil>
[7] pry(main)> a.password = "asdf"
=> "asdf"
[8] pry(main)> a.password
=> "$2a$10$9udQKttf5zFqCv7da9ZY0uMsWYlbeGK3apEkIY6x05KND1v3vOkh2"
[9] pry(main)> a.password == "asdf"
=> true

讓我講一個簡短的故事,說明為什么我必須走與您相似的道路。 我曾經不得不“取消訂閱”我模型的許多屬性/列。

問題是我想通過read_attribute方法達到列值。 我當時的任務是幾周后應將這些列從數據庫中刪除,因此我想首先對這些“已棄用”列中的數據進行一次優美的遷移(並將它們移植到新表中),但我仍然想使用整個應用程序中使用相同的舊屬性訪問器方法,但它們將使用新的數據庫結構。

因此,我沒有編寫簡單的逐個方法覆蓋(讀取器和寫入器)的方法,而是簡單地編寫了method_missing來捕獲所有舊列(通過正則表達式)並將它們的操作重定向到新邏輯中。 但是,只有在可以“取消定義”舊的訪問器(“臟”)方法的情況下,我才能完成此操作,否則它們將永遠無法達到method_missing方法。

因此,經過很多努力,我進入了以下解決方案(適應您的情況):

class Account < ActiveRecord::Base

  # ...

  unless attribute_methods_generated?
    define_attribute_methods
    undef_method "password"
    undef_method "password="
  end

  # ...

end

我只需要刪除reader和writter方法,但是您可以根據需要刪除所有其他“臟”方法。

attribute_methods_generated? 如果臟方法已經生成,則類方法返回true。 然后,它強制生成臟方法,然后才刪除所需的方法。 如果您簡單地嘗試直接在類范圍內使用undef_method方法,它將拋出一個異常,告訴您要刪除的方法尚不存在。 這就是為什么您需要在上面這樣進行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM