我能够创建具有相同名称和电子邮件的用户,即使我要求这些字段是唯一的。
查看数据库,可能会出现问题的条目被删除,然后创建一个新条目。
模型:
class User
include Mongoid::Document
extend Rolify
rolify
include Mongoid::Timestamps
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
## Database authenticatable
field :email, :type => String, :default => ""
field :encrypted_password, :type => String, :default => ""
validates_presence_of :email
# validates_presence_of :encrypted_password
# override Devise method
# no password is required when the account is created; validate password when the user sets one
validates_confirmation_of :password
def password_required?
if !persisted?
!(password != "")
else
!password.nil? || !password_confirmation.nil?
end
end
# override Devise method
def confirmation_required?
false
end
# override Devise method
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
## Recoverable
field :reset_password_token, :type => String
field :reset_password_sent_at, :type => Time
## Rememberable
field :remember_created_at, :type => Time
## Trackable
field :sign_in_count, :type => Integer, :default => 0
field :current_sign_in_at, :type => Time
field :last_sign_in_at, :type => Time
field :current_sign_in_ip, :type => String
field :last_sign_in_ip, :type => String
# Confirmable
field :confirmation_token, :type => String
field :confirmed_at, :type => Time
field :confirmation_sent_at, :type => Time
field :unconfirmed_email, :type => String # Only if using reconfirmable
## Lockable
# field :failed_attempts, :type => Integer, :default => 0 # Only if lock strategy is :failed_attempts
# field :unlock_token, :type => String # Only if unlock strategy is :email or :both
# field :locked_at, :type => Time
#invitable
field :invitation_token, :type => String
field :invitation_sent_at, :type => Time
field :invitation_accepted_at, :type => Time
field :invitation_limit, :type => Integer
field :invited_by_id, :type => String
field :invited_by_type, :type => String
## Token authenticatable
# field :authentication_token, :type => String
# run 'rake db:mongoid:create_indexes' to create indexes
index({ email: 1 }, { unique: true, background: true })
field :name, :type => String
validates_presence_of :name
validates_uniqueness_of :name, :email, :case_sensitive => false
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :created_at, :updated_at
# add a welcome email sender
after_create :send_welcome_email
def attempt_set_password(params)
p = {}
p[:password] = params[:password]
p[:password_confirmation] = params[:password_confirmation]
update_attributes(p)
end
# new function to determine whether a password has been set
def has_no_password?
self.encrypted_password.blank?
end
# new function to provide access to protected method pending_any_confirmation
def only_if_unconfirmed
pending_any_confirmation {yield}
end
private
def send_welcome_email
unless self.email.include?('@example.com') && Rails.env != 'test'
UserMailer.welcome_email(self).deliver
UserMailer.newusernotify(self).deliver
end
end
end
服务器日志:(一个名为“Bob”的用户已经存在,发送电子邮件至 test@test.com。在我请求创建一个名为“Sam”和 test@test.com 的新用户后,我得到的是:
Started POST "/users" for 127.0.0.1 at 2012-11-14 14:06:54 -0500
Processing by RegistrationsController#create as */*
Parameters: {"user"=>{"email"=>"test@test.com", "name"=>"Sam"}}
MOPED: 127.0.0.1:27017 COMMAND database=admin command={:ismaster=>1} (6.2630ms)
MOPED: 127.0.0.1:27017 QUERY database=app_development collection=users selector={"$query"=>{"email"=>"test@test.com", "encrypted_password"=>""}, "$orderby"=>{:_id=>1}} flags=[:slave_ok] limit=-1 skip=0 fields=nil (5.0299ms)
MOPED: 127.0.0.1:27017 DELETE database=app_development collection=users selector={"_id"=>"50a3c974b8e764fc0d000005"} flags=[:remove_first] (0.1829ms)
before build resource
after build resource
MOPED: 127.0.0.1:27017 QUERY database=app_development collection=users selector={"email"=>"test@test.com"} flags=[:slave_ok] limit=1 skip=0 fields={:_id=>1} (1.9441ms)
MOPED: 127.0.0.1:27017 QUERY database=app_development collection=users selector={"name"=>/\ASam$/i} flags=[:slave_ok] limit=1 skip=0 fields={:_id=>1} (0.8898ms)
MOPED: 127.0.0.1:27017 QUERY database=app_development collection=users selector={"email"=>/\Atest@test\.com$/i} flags=[:slave_ok] limit=1 skip=0 fields={:_id=>1} (0.3989ms)
MOPED: 127.0.0.1:27017 INSERT database=app_development collection=users documents=[{"_id"=>"50a3ebceb8e764fc0d000006", "role_ids"=>[], "email"=>"test@test.com", "encrypted_password"=>"", "sign_in_count"=>0, "name"=>"Sam", "updated_at"=>2012-11-14 19:06:54 UTC, "created_at"=>2012-11-14 19:06:54 UTC}] flags=[] (1.3258ms)
Rendered user_mailer/welcome_email.html.erb (0.0ms)
Rendered user_mailer/welcome_email.text.erb (0.0ms)
另请注意:模型的 rspec 测试(如下)通过。为什么要设计删除用户,然后插入新用户?
require 'spec_helper'
describe User do
before(:each) do
@attr = {
:name => "Example User",
:email => "user@example.com",
:password => "foobar",
:password_confirmation => "foobar"
}
end
it "should create a new instance given a valid attribute" do
User.create!(@attr)
end
it "should require an email address" do
no_email_user = User.new(@attr.merge(:email => ""))
no_email_user.should_not be_valid
end
it "should accept valid email addresses" do
addresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp]
addresses.each do |address|
valid_email_user = User.new(@attr.merge(:email => address))
valid_email_user.should be_valid
end
end
it "should reject invalid email addresses" do
addresses = %w[user@foo,com user_at_foo.org example.user@foo.]
addresses.each do |address|
invalid_email_user = User.new(@attr.merge(:email => address))
invalid_email_user.should_not be_valid
end
end
it "should reject duplicate email addresses" do
User.create!(@attr)
user_with_duplicate_email = User.new(@attr)
user_with_duplicate_email.should_not be_valid
end
it "should reject email addresses identical up to case" do
upcased_email = @attr[:email].upcase
User.create!(@attr.merge(:email => upcased_email))
user_with_duplicate_email = User.new(@attr)
user_with_duplicate_email.should_not be_valid
end
describe "passwords" do
before(:each) do
@user = User.new(@attr)
end
it "should have a password attribute" do
@user.should respond_to(:password)
end
it "should have a password confirmation attribute" do
@user.should respond_to(:password_confirmation)
end
end
describe "password validations" do
it "should require a password" do
User.new(@attr.merge(:password => "", :password_confirmation => "")).
should_not be_valid
end
it "should require a matching password confirmation" do
User.new(@attr.merge(:password_confirmation => "invalid")).
should_not be_valid
end
it "should reject short passwords" do
short = "a" * 5
hash = @attr.merge(:password => short, :password_confirmation => short)
User.new(hash).should_not be_valid
end
end
describe "password encryption" do
before(:each) do
@user = User.create!(@attr)
end
it "should have an encrypted password attribute" do
@user.should respond_to(:encrypted_password)
end
it "should set the encrypted password attribute" do
@user.encrypted_password.should_not be_blank
end
end
end
更新:我在 Registrations 控制器中插入了一些调试语句。非常有趣的是,它们都没有被触发——只有“构建资源之前”和“构建资源之后”,而不是“FIRST IF”、“SECOND IF”或“THIRD IF”。为什么会这样?
class RegistrationsController < Devise::RegistrationsController
# override #create to respond to AJAX with a partial
def create
logger.debug "before build resource"
build_resource
logger.debug "after build resource"
if resource.save
if resource.active_for_authentication?
sign_in(resource_name, resource)
(render(:partial => 'thankyou', :layout => false) && return) if request.xhr?
logger.debug "FIRST IF"
respond_with resource, :location => after_sign_up_path_for(resource)
else
expire_session_data_after_sign_in!
(render(:partial => 'thankyou', :layout => false) && return) if request.xhr?
logger.debug "SECOND IF"
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
logger.debug "THIRD IF"
clean_up_passwords resource
render :action => :new, :layout => !request.xhr?
end
end
protected
def after_inactive_sign_up_path_for(resource)
# the page prelaunch visitors will see after they request an invitation
'/thankyou.html'
end
def after_sign_up_path_for(resource)
# the page new users will see after sign up (after launch, when no invitation is needed)
# '/thankyou.html'
redirect_to root_path
end
end