我有这堂课:
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上的依赖项?
答案 0 :(得分:0)
我知道您的问题更多地针对编码风格,但我想回答可能使所有这些变得更容易的问题 - 并建议您查看Rails 3.1中引入的has_secure_password方法。这将抽象出encrypt_password
和authenticate
方法。
现在 - 关于你原来的问题 - 你可以使用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
答案 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类中包含测试加密模块。