Rails 3.1 + Devise 1.4.9:为什么 ActiveRecord 无法捕获失败的保存?



更新我使用Devise 1.4.9进行身份验证,当我尝试创建一个电子邮件地址已存在于数据库中的新用户时,我的Devise生成的用户模型似乎没有捕捉到数据库引发的异常。按照ciclon的建议(见下面的回复),我创建新用户的代码是…

class Api::RegistrationsController < Api::BaseController
  respond_to :json
  def create
    user = User.new(params[:user])
    user.ensure_authentication_token! 
    if user.valid?
        user.save
        render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email, :user_id=>user.id), :status=>201
        return
    else
        warden.custom_failure!
        render :json=> user.errors, :status=>422
    end
  end
end

Devise生成的迁移包括电子邮件属性的索引,包括唯一性验证。。。

add_index :users, :email, :unique => true

这是用户模型。。。

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :token_authenticatable
  attr_accessible :email, :password, :password_confirmation, :remember_me, :authentication_token 
  validates_uniqueness_of :email
  def ensure_authentication_token!   
    reset_authentication_token! if authentication_token.blank?   
  end  
  def as_json(options={})
    super(:only => [:email, :authentication_token, :id])
  end
end

如果指定的电子邮件地址已经存在于数据库中,我希望数据库在保存过程中引发错误,让Rails捕获它并执行其他代码块以返回422和问题描述。事实并非如此,相反,我得到了一个SQLException错误和一个崩溃。。。

ActiveRecord::StatementInvalid (SQLite3::ConstraintException: constraint failed: INSERT INTO "users" ("authentication_token", "created_at", "current_sign_in_at", "current_sign_in_ip", "email", "encrypted_password", "last_sign_in_at", "last_sign_in_ip", "remember_created_at", "reset_password_sent_at", "reset_password_token", "sign_in_count", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)):
  app/models/user.rb:33:in `ensure_authentication_token!'
  app/controllers/api/registrations_controller.rb:7:in `create'

如何让我的代码返回一个带有错误描述的422?

提前感谢你的智慧!

我又读了你的帖子,现在我看到那里发生了什么。您有一个数据库索引,而Rails并不知道这一点,所以当您创建@user.save时,数据库会抛出一个未在Rails端捕获的异常。

也就是说,你有两个选择:

1.)捕获Rails:中的异常

begin
  user.save
  render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email, :user_id=>user.id), :status=>201
rescue
  warden.custom_failure!
  render :json=> user.errors, :status=>422
end

2.)将验证添加到您的模型中,并检查用户是否有效?:

关于您的用户模型:

validates_uniqueness_of :email

在您的控制器上:

  if user.valid?
      user.save
      render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email, :user_id=>user.id), :status=>201
      return
  else
      warden.custom_failure!
      render :json=> user.errors, :status=>422
  end

我认为您可能需要将验证添加到您的用户模型中。

validates_uniqueness_of :email

我会将其封装在异常处理程序中。试试这样的东西:

class Api::RegistrationsController < Api::BaseController
  respond_to :json
  def create
    user = User.new(params[:user])
    status = nil
    json   = nil
    begin
      user.ensure_authentication_token!
      user.save!
      status = 201
      json   = {
        :auth_token => user.authentication_token,
        :email      => user.email,
        :user_id    => user.id
      }
    rescue StandardError => e
      case e
      when ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique
        # ActiveRecord::StatementInvalid -> Raised by SQLite Adapter
        # ActiveRecord::RecordNotUnique  -> Raised by Postgres Adapter
        user.errors[:email] = 'already taken'
      end
      warden.custom_failure!
      status = 422
      json   = user.errors
    end
    render :json => json, :status => status
  end
end

最新更新