rake db:seed を何回やっても大丈夫なようにするには auto increment の管理が必要になります。
本番環境で何度も実行することは、ほぼあり得ないし、開発環境であれば直接DBをわさわさしちゃえば間に合うので、まともに実装する必要はないんでしょうが、少し調べてみました。
自前で拡張
sql - Rails way to reset seed on id field - Stack Overflow
reset_pk_sequence
というメソッドを追加していますが、面白いのが PostgreSQL の場合
reset_pk_sequence!
というメソッドを呼ぶだけのようで、Rails自体に実装されています。
しかし、MySQLは考慮さえされておらず(MySQL用に拡張するのは簡単だと述べられていますが)DB格差が生まれています。
PostgreSQL実装
# active_record/connection_adapters/postgresql/schema_statements.rb def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: (略) select_value(<<-end_sql, 'SCHEMA') SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql
MySQLも考慮してみる
config/initializers 以下に格納。
こんな感じでしょうか。
module ActiveRecord class Base def self.reset_pk_sequence case ActiveRecord::Base.connection.adapter_name when "Mysql2" new_max = maximum(primary_key) || 0 update_seq_sql = "alter table #{table_name} auto_increment = 1;" ActiveRecord::Base.connection.execute(update_seq_sql) when "SQLite" new_max = maximum(primary_key) || 0 update_seq_sql = "update sqlite_sequence set seq = #{new_max} where name = '#{table_name}';" ActiveRecord::Base.connection.execute(update_seq_sql) when "PostgreSQL" ActiveRecord::Base.connection.reset_pk_sequence!(table_name) else raise "Task not implemented for this DB adapter" end end end end
database_cleaner
ついでに test でお世話になっている DatabaseCleaner はどういう実装かというと、基本Truncateで処理しSQLiteの場合は sqlite_sequence を delete していました。
# database_cleaner-1.6.1/lib/database_cleaner/active_record/truncation.rb module SQLiteAdapter def delete_table(table_name) execute("DELETE FROM #{quote_table_name(table_name)};") if uses_sequence execute("DELETE FROM sqlite_sequence where name = '#{table_name}';") end end alias truncate_table delete_table