基本上所有的 Web 应用程序都会需要某种登录和用户验证系统。所以 Web 框架大都有很多验证系统的实现方案,Rails 当然也不例外。用户验证及授权系统有很多,包括 Clearance、Authlogic、Devise 和 CanCan(还有一些不是专门针对 Rails 的基于 OpenID 和 OAuth 开发的系统)。所以你肯定就会问,为什么还要重复制造轮子,为什么不直接用现有的解决方案,而要自己开发呢?
首先,实践已经证明,大多数网站的用户验证系统都要对第三方代码库做一些定制和修改,这往往比重新开发一个验证系统的工作量还大。再者,现有的方案就像一个“黑盒”,你无法了解其中到底有些什么功能,而自己开发的话就能更好的理解实现的过程。而且,Rails 最近的更新,使开发验证系统变得很简单。最后,如果后续开发要用第三方代码库的话,因为自己开发过,所以你可以更好的理解其实现过程,便于定制功能。
创建“测试数据库”的正确命令:
$ bundle exec rake test:prepare
为某一列建立索引就需要改变数据库模型,在Rails中,这可以通过数据库迁移来实现。
let 方法
我们可以使用 RSpec 提供的 let 方法便捷的在测试中定义局部变量。let 方法的句法看起来有点怪,不过和变量赋值语句的作用是一样的。let 方法的参数是一个 Symbol,后面可以跟着一个块,块中代码的返回值会赋给名为 Symbol 代表的局部变量。也就是说:
let(:found_user) { User.find_by(email: @user.email) }
定义了一个名为 found_user 的变量,其值等于 find_by 的返回值。在这个测试用例的任何一个 before 或 it 块中都可以使用这个变量。使用 let 方法定义变量的一个好处是,它可以记住(memoize)变量的值。(memoize 是个行业术语,不是“memorize”的误拼写。)对上面的代码而言,因为 let 的备忘功能,found_user 的值会被记住,因此不管调用多少次 User 模型测试,find_by 方法只会运行一次。
最终的用户模型为:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :name, presence: true, length: { maximum: 50}
validates :email, presence:true,
format: {with: VALID_EMAIL_REGEX},
uniqueness: {case_sensitive: false}
has_secure_password
validates :password, length: {minimum: 6}
end
而测试文件为:
require 'spec_helper'
describe User do
before do
@user = User.new(name: "haha",
email: "haha@gmail.com",
password: "foobar",
password_confirmation: "foobar")
end
subject { @user }
it {should respond_to(:name) }
it {should respond_to(:email) }
it {should respond_to(:password_digest)}
it {should respond_to(:password)}
it {should respond_to(:password_confirmation)}
it {should respond_to(:authenticate)}
it {should be_valid}
describe "when name is not present" do
before {@user.name = " "}
it {should_not be_valid}
end
describe "when email is not present" do
before {@user.email = " "}
it {should_not be_valid}
end
describe "when name is too long" do
before { @user.name = 'a'*51 }
it {should_not be_valid}
end
describe "when email format is invalid" do
it "should be invalid" do
addresses = %w[user@foo,com user_at_foo.org example.user@foo.
foo@bar_baz.com foo@bar+baz.com]
addresses.each do |invalid_address|
@user.email = invalid_address
expect(@user).not_to be_valid
end
end
end
describe "when email format is valid" do
it "should be valid" do
addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
addresses.each do |valid_address|
@user.email = valid_address
expect(@user).to be_valid
end
end
end
describe "when email address is already taken" do
before do
user_with_same_email = @user.dup
user_with_same_email.save
end
it { should_not be_valid }
end
describe "when password is not present" do
before do
@user = User.new(name: "Example User", email: "user@example.com",
password: " ", password_confirmation: " ")
end
it { should_not be_valid }
end
describe "when password doesn't match confirmation" do
before { @user.password_confirmation = "mismatch" }
it { should_not be_valid }
end
describe "with a password that's too short" do
before { @user.password = @user.password_confirmation = "a" * 5 }
it { should be_invalid }
end
describe "return value of authenticate method" do
before { @user.save }
let(:found_user) { User.find_by(email: @user.email) }
describe "with valid password" do
it { should eq found_user.authenticate(@user.password) }
end
describe "with invalid password" do
let(:user_for_invalid_password) { found_user.authenticate("invalid") }
it { should_not eq user_for_invalid_password }
specify { expect(user_for_invalid_password).to be_false }
end
end
end