- SQLiteとMySQLは文字列のエラーメッセージしか持ってないので無理そう
- PostgreSQLはいけるかもしれないが未確認
rails4.2-stable
https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/errors.rb#L98
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
class RecordNotUnique < WrappedDatabaseException
end
# Defunct wrapper class kept for compatibility.
# +StatementInvalid+ wraps the original exception now.
class WrappedDatabaseException < StatementInvalid
end
# Superclass for all database execution errors.
#
# Wraps the underlying database error as +original_exception+.
class StatementInvalid < ActiveRecordError
attr_reader :original_exception
def initialize(message, original_exception = nil)
super(message)
@original_exception = original_exception
end
end
# = Active Record Errors
#
# Generic Active Record exception class.
class ActiveRecordError < StandardError
end
- まとめると以下の継承関係になっている
StandardError <- ActiveRecordError <- StatementInvalid <- WrappedDatabaseException <- RecordNotUnique
def translate_exception(exception, message)
case error_number(exception)
when 1062
RecordNotUnique.new(message, exception)
when 1452
InvalidForeignKey.new(message, exception)
else
super
end
end
- うまくやればエラーを起こしたカラム情報まで取れるかも知れないが、実際に追ってみないと分からない。
StatementInvalid
のoriginal_exception
に入ってるerrorにここでrescueしたerrorが入ってるはず。
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
when UNIQUE_VIOLATION
RecordNotUnique.new(message, exception)
when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message, exception)
else
super
end
end
- mysqlとほぼ同じような感じだが
exception.result.error_field
というメソッドを呼んでいるのでカラムが特定できるかも知れない - あとstring定数freezeしなきゃ案件だ。
def translate_exception(exception, message)
case exception.message
# SQLite 3.8.2 returns a newly formatted error message:
# UNIQUE constraint failed: *table_name*.*column_name*
# Older versions of SQLite return:
# column *column_name* is not unique
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
RecordNotUnique.new(message, exception)
else
super
end
end
- なかなか泥臭いことをしておる。
-
ActiveRecord::ConnectionAdapters::AbstractAdapter
の#log
def translate_exception_class(e, sql)
begin
message = "#{e.class.name}: #{e.message}: #{sql}"
rescue Encoding::CompatibilityError
message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}"
end
exception = translate_exception(e, message)
exception.set_backtrace e.backtrace
exception
end
def log(sql, name = "SQL", binds = [], statement_name = nil)
@instrumenter.instrument(
"sql.active_record",
:sql => sql,
:name => name,
:connection_id => object_id,
:statement_name => statement_name,
:binds => binds) { yield }
rescue => e
raise translate_exception_class(e, sql)
end
def translate_exception(exception, message)
# override in derived class
ActiveRecord::StatementInvalid.new(message, exception)
end
雑にrails appを書いて実際にActiveRecord::RecordNotUnique
をraiseさせてみる
class TodosController < ApplicationController
def index
render json: (Todo.all).to_json
end
def create
Todo.create!(content: params[:content])
render json: {}
rescue ActiveRecord::RecordNotUnique => e
puts e.class
puts e.cause.class
puts e.cause.message
end
end
CREATE TABLE `todos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content` varchar(255) DEFAULT NULL,
`string` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `index_todos_on_content` (`content`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
ActiveRecord::RecordNotUnique
SQLite3::ConstraintException
UNIQUE constraint failed: todos.content
ActiveRecord::RecordNotUnique
Mysql2::Error
Duplicate entry 'test' for key 'index_todos_on_content'
ところで Mysql2::Error
はこれ https://github.com/brianmario/mysql2/blob/1d4de8963c71ce88772a0e27883066160c3dbbd4/lib/mysql2/error.rb
module Mysql2
class Error < StandardError
ENCODE_OPTS = {
:undef => :replace,
:invalid => :replace,
:replace => '?'.freeze,
}.freeze
attr_reader :error_number, :sql_state
# Mysql gem compatibility
alias_method :errno, :error_number
alias_method :error, :message
...
end
end
やはりエラーになったカラム情報はもってないようだ。文字列パースするぐらいしかないかな。