なぜかレビューを通っていた下記のようなmigrationがあったのですが、
unique indexが貼られていなくて(当たり前ですが)先日ひどい目にあいました。
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.integer :shop_id, unique: true
end
end
end
はい。 ActiveRecord::ConnectionAdapters::TableDefinition
の column
メソッドの opthions
に unique: true
を渡してもunique indexは貼られませんね。
create_table でカラムを定義するのと同時に unique index を貼るやつの結論
上記のオプションの渡し方の正解は下記ですね。
t.integer :shop_id, index: { unique: true }
下記でいけるので。
t.type :column_name, index: { unique: true }
下記はだめ。
t.type :column_name, unique: true
そんなオプションの渡し方はない。
ActiveRecord::ConnectionAdapters::SchemaStatements の create_table メソッドのコードを読む
まあ、以上なのですが、せっかくなのでこの部分が実際にどういう実装になっているかを知るために ActiveRecord::ConnectionAdapters::SchemaStatements
の create_table
メソッドについて Active Recordのコードを読んでみますか。
読んだ環境はRails 5.2.2 (GitHub - rails/rails at v5.2.2)です。
create_table
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L290
def create_table(table_name, comment: nil, **options)
td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
yield td if block_given?
end
yield
に渡しているのは td
なので、普段 t.integer
みたいに書いている t
は create_table_definition
で定義されているようだ。
create_table_definition table_name
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L1278
def create_table_definition(*args)
TableDefinition.new(*args)
end
td
は TableDefinition
のインスタンスだった。
TableDefinition
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L257
class TableDefinition
include ColumnMethods
end
ColumnMethods
をincludeしている。
ColumnMethods
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L201
module ColumnMethods
[
:bigint,
:binary,
:boolean,
:date,
:datetime,
:decimal,
:float,
:integer,
:json,
:string,
:text,
:time,
:timestamp,
:virtual,
].each do |column_type|
module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{column_type}(*args, **options)
args.each { |name| column(name, :#{column_type}, options) }
end
CODE
end
alias_method :numeric, :decimal
end
普段使っているシンタックスシュガーがメタプロで定義されている。
t.integer
だと column(name, :integer, options) }
なので、結局 column
メソッドに options
引数が渡されて実行されている。
column
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L355
def column(name, type, options = {})
name = name.to_s
type = type.to_sym if type
options = options.dup
index_options = options.delete(:index)
index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
@columns_hash[name] = new_column_definition(name, type, options)
self
end
options
の index
キーの中身は index
メソッドに引数として渡されている。
index
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L380
def index(column_name, options = {})
indexes << [column_name, options]
end
コメントの通りですが、 indexes
に options
を追加している。
create_tableに戻る
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L314
def create_table(table_name, comment: nil, **options)
td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
unless supports_indexes_in_create?
td.indexes.each do |column_name, index_options|
add_index(table_name, column_name, index_options)
end
end
end
td.indexes
のそれぞれの要素の2つ目の index_options
は add_index
メソッドに index_options
として渡している。
結局 add_index
が呼ばれていますね。
--
ActiveRecordのコードはこれ以上追いませんが、結局
t.type :column_name, index: { key: value }
の index
部分のオプションは
add_index(table_name, column_name, {key: value})
のように、 add_index
にオプションとして渡されるのでした。
そして add_index
は unique indexを作成するときは オプションとして unique: true
を渡す必要ので、 create_table
の中の t.type
でunique indexを貼りたいときはオプションとして index
をキーにして、
t.type :column_name, index: { unique: true }
としないといけないのでした。
コードを読むのは疲れますが、実際の処理の中身がよく分かるのでこれからも適宜読んでいきたいですね。
こちらからは以上です。
補足
上記で見た、ActiveRecord::ConnectionAdapters::TableDefinition
の column
メソッドのオプションでunique indexを貼る方法が以外の方法は下記がある。
ActiveRecord::ConnectionAdapters::SchemaStatements
の add_index
メソッド
テーブル作成後にindexを追加するやつ。
create_table :products do |t|
t.integer :shop_id
end
add_index :products, :shop_id, unique: true, name: 'products_shop_id_index'
ActiveRecord::ConnectionAdapters::TableDefinition
の index
メソッド
create_table
の中でindexを追加するやつ。
create_table :products do |t|
t.integer :shop_id
t.index :category_id, unique: true, name: 'products_shop_id_index'
end
ActiveRecord::ConnectionAdapters::Table
の index
メソッド
change_table
でindexを追加するやつ。
create_table
内で利用する1つ上と形は一緒ですね。
change_table :products do |t|
t.index :shop_id, unique: true, name: 'products_shop_id_index'
end
参考