如何从Ruby on Rails模型类中删除依赖项?

时间:2013-04-23 02:56:32

标签: ruby-on-rails coding-style ruby-on-rails-3.2

我有这堂课:

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上的依赖项?

2 个答案:

答案 0 :(得分:0)

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

现在 - 关于你原来的问题 - 你可以使用Ruby中的模块来分离功能。对于您的特定用例,需要includeextend的组合,我用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

答案 1 :(得分: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类中包含测试加密模块。