Rails 3.1 + Devise 1.4.9: Почему ActiveRecord не ловит неудачное сохранение? - PullRequest
2 голосов
/ 01 марта 2012

ОБНОВЛЕНО Я использую Devise 1.4.9 для аутентификации, и моя модель пользователя, сгенерированная Devise, похоже, не улавливает исключения, которые выдает БД, когда я пытаюсь создать нового пользователя с адрес электронной почты, который уже существует в БД. Следуя совету Циклона (см. Ответ ниже), мой код для создания нового пользователя ...

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 перехватил его и выполнил блок кода else, чтобы получить 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 с описанием ошибки?

Заранее большое спасибо за вашу мудрость!

Ответы [ 3 ]

3 голосов
/ 02 марта 2012

Я снова прочитал ваш пост, теперь я вижу, что там происходит. У вас есть индекс базы данных, и 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.) Добавьте проверку к вашей модели и проверьте user.valid?:

На вашей модели пользователя:

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
0 голосов
/ 08 марта 2012

Я бы обернул это в обработчики исключений.Попробуйте что-то вроде этого:

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
0 голосов
/ 01 марта 2012

Я думаю, что вам может понадобиться добавить проверку к вашей модели пользователей.

validates_uniqueness_of :email
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...