2

私は Rails の初心者で、ActiveRecord を使用して User モデルの設計に取り組んでいます。このモデルには、ユーザーのパスワードのハッシュを保持することを目的とした 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

これを達成する方法や、このアプローチの欠点はありますか?

4

4 に答える 4

2

上記の回答に基づいて、望ましい結果を得るためにいくつかの実験を行いました。最終的に、「プライベート」な password_hash 属性と password という仮想アクセサーを作成しました。

このプロセスでいくつかの観察を行いました。

  • ActiveRecord にはプライベート属性の概念がないようです。などのシンボルを使用してアクセサメソッドをプライベートにするprivate :password, :password=ことはオプションではありませんNameError: undefined method.Railsはモデルをインスタンス化するときに をスローします.モデル自体にはこれら2つのメソッドが定義されていないためです(これらはから継承されているようですActiveRecord::Base).

  • password_hash アクセサーを純粋なものでオーバーライドすると、属性の操作を防ぐことができますが、password_hash 属性を更新するときに ActiveRecord 自体が失敗することも意味します。これは、空の実装を呼び出しているためです。

したがって、実際のモデルでは未定義であるため、アクセサーを非公開にすることは失敗します。それらの定義も失敗します。ActiveRecord が壊れるためです。それで、あなたは何ができますか?

私は両方ともう少しやりました。アクセサーを非公開にし、それらを定義して、 を呼び出して両方を実装しましたsuper。これにより、コントローラー (および Rails コンソール) が をスローしてそれらにアクセスできなくなりますNoMethodErrorが、ActiveRecord は拒否されません。

補足: 検証の問題

私のアプローチで遭遇した問題の 1 つは、検証が壊れていたことです。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
于 2012-11-22T22:29:54.773 に答える
0

アプリの認証ソリューションを探しているときに、devise のような宝石に出くわしました。

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
于 2012-11-20T01:46:27.260 に答える
0

private メソッドを使用してアクセサーを簡単にプライベートにすることができます。モデルに以下の行を追加します。

private :password, :password=

また、パスワード フィールド アクセサーをオーバーライドしないことをお勧めします。データベース内のフィールドpassword_hashに名前を付けて、この列のアクセサーを非公開にすることができます。password次に、予想どおり、メソッドを記述しますpassword=

あなたのニーズに合わない場合は、その理由を教えてください。そして、なぜあなたが望むものがより良い解決策になるのでしょうか?

于 2012-11-20T00:15:24.170 に答える
0

なぜ私があなたと同じような道を歩まなければならなかったのか、短い話をさせてください。モデルの属性/列の多くを「登録解除」する必要がありました。

read_attribute問題は、メソッドを介して列の値に到達したかったということです。これらの列を数週間後に DB から削除する必要があるため、最初にこれらの「非推奨」列のデータを適切に移行 (およびそれらを新しいテーブルに移植) したかったのですが、それでも使用したかったアプリ全体で同じ古い属性アクセサー メソッドを使用しますが、新しい DB 構造を使用します。

したがって、単純にメソッド (リーダーとライター) ごとに上書きするのではなく、method_missingすべての古い列を (正規表現を介して) キャッチし、それらのアクションを新しいロジックにリダイレクトするように単純に記述しました。しかし、古いアクセサー (「ダーティ」) メソッドを「定義解除」できる場合にのみ、これを達成できました。そうしないと、メソッドに到達できませんmethod_missing

それで、多くの苦労の後、私は次の解決策になりました(あなたのケースに適応します):

class Account < ActiveRecord::Base

  # ...

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

  # ...

end

リーダーとライターのメソッドを削除する必要がありましたが、必要に応じて他のすべての「ダーティ」メソッドを削除できます。

ダーティー メソッドが既に生成されている場合、attribute_methods_generated?クラス メソッドは true を返します。次に、ダーティ メソッドを強制的に生成し、必要なメソッドを削除します。クラススコープでメソッドを直接使用しよundef_methodうとすると、削除しようとしているメソッドが(まだ)存在しないことを示す例外がスローされます。そのため、上記のようにする必要があります。

于 2014-04-17T20:36:49.613 に答える