0

我有这堂课:

class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation

  attr_accessor :password
  before_save :encrypt_password

  validates_confirmation_of :password
  validates_presence_of :password, :on => :create
  validates_presence_of :email
  validates_uniqueness_of :email

  def encrypt_password
    if password.present?
        self.password_salt = BCrypt::Engine.generate_salt
        self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end

  def self.authenticate(email, password)
    user = find_by_email(email)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end
end

我是一名 C# 开发人员,并且知道这个类与 ActiveRecord 和 BCrypt 紧密耦合。如果我使用 C#,我会将 BCrypt 的使用提取到另一个类中,并通过依赖注入传递新类。至于 ActiveRecord 的使用,我会定义一个接受这个类作为参数的类来持久化。

我应该尝试对 Ruby 采取相同的路线,还是有更好的方法来消除对 ActiveRecord 和 BCrypt 的依赖?

4

2 回答 2

0

我知道您的问题更针对编码风格,但我想回答可能使这一切变得更容易的问题 - 并建议您查看Rails 3.1 中引入的has_secure_password方法。这将抽象出encrypt_passwordandauthenticate方法。

现在 - 关于你原来的问题 - 你可以使用 Ruby 中的模块来分离功能。对于您的特定用例,include和的组合extend是必要的,我用一个included钩子来结束。

module PasswordEncryption
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def authenticate(email, password)
      user = find_by_email(email)
      if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
        user
      else
        nil
      end
    end
  end

  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end
end

class User < ActiveRecord::Base
  include PasswordEncryption
end
于 2013-04-23T03:44:12.927 回答
0

要打破 BCrypt 的耦合,然后尝试创建一个模块并将其混合到您的模型类中。如果您需要,网上有很多关于 Ruby mixins 的信息。

恐怕脱离 ActiveRecord 可能会更加困难。您可能会考虑在这里使用相同的方法,通过使用您的业务逻辑创建一个模块,然后将其混合到 AR 类中。

您可以向您的模块添加回调,这些模块在包含或扩展时被调用,这是您获得依赖项注入的地方。请参阅http://ruby-doc.org/core-1.9.3/Module.html#method-i-included

回应有关测试的评论

Ruby 使仅存根方法或包含另一个模块变得容易,该模块将在测试期间覆盖加密模块中的方法。这似乎有点“脏”。

假设您已将加密逻辑分离到一个名为Encryption::BCrypt. 您希望包含该模块,除非在测试环境中运行。一个干净的方法是创建一个初始化程序,例如 config/initializers/encryption.rb,它有这样一行

User.send(:include, Encryption::BCrypt) unless Rails.env.test?

然后在您的测试代码中,您可能希望删除适当的方法或将测试加密模块包含到您的 User 类中。

于 2013-04-23T03:46:27.217 回答