rails generate modelにカスタムファイルのジェネレータを追加する
Rails標準のモデルジェネレータでプロジェクトの環境に合わせたカスタムファイルを作りたいことはありませんか?
例えば、開発環境データを整備するSeedやプロジェクト固有のディレクトリに合わせたファイルなどです。
この記事ではRails標準のモデルジェネレータを例に、標準の挙動はそのままにカスタムファイルを追加で生成する方法を紹介します。
カスタムジェネレータを作成する
まず、カスタムファイルを生成するためのジェネレータを作成します。
bin/rails generate generator コマンドで dev_seed という名前のジェネレータを生成します。名前は適宜変えても大丈夫です。
$ bin/rails generate generator dev_seed
create lib/generators/dev_seed
create lib/generators/dev_seed/dev_seed_generator.rb
create lib/generators/dev_seed/USAGE
create lib/generators/dev_seed/templates
invoke rspec
create spec/generator/dev_seed_generator_spec.rb
生成されたファイルのうち dev_seed_generator.rb がジェネレータの実行ファイルになるので、これを次のようにします。
これはRails標準のモデルジェネレータで実行するので、標準のモデルジェネレータと同じ引数を受け取り、カスタムファイルのテンプレートを生成する処理を書いています。
このファイルのパブリックメソッドはジェネレータ実行時に上から実行されます。
プライベートメソッドでは、テンプレートがカラムのタイプに応じて適切なダミーデータを生成するための処理を定義しています。
class DevSeedGenerator < Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
argument :attributes, type: :array, default: [], banner: 'field[:type][:index] field[:type][:index]'
def create_dev_seed_file
template 'dev_seed.rb.erb', File.join('db/seeds/development', "#{singular_table_name}.rb")
end
private
def dev_seed_attributes
return [] if attributes.empty?
sample_attributes = {}
attributes.each do |attribute|
sample_attributes[attribute.name] = case attribute.type
when :string, :text
"'Sample #{attribute.name.humanize}'"
when :integer
'1'
when :decimal, :float
'100.0'
when :boolean
'false'
when :datetime, :timestamp
'Time.zone.local(2025, 1, 1, 12, 0, 0)'
when :date
"'2025-01-01'"
else
'nil'
end
end
sample_attributes
end
end
カスタムファイルのテンプレートは生成したいコードをerb形式で書きます。
次の例はModelのレコードを1つ 適切な引数と共にcreate するコードを生成するものです。
# TODO: 適切な値に調整する
<%= class_name %>.create!(
<%- dev_seed_attributes.each do |attr_name, attr_value| -%>
<%= attr_name %>: <%= attr_value %>,
<%- end -%>
)
ここまでで次のようなカスタムジェネレータコマンドを実行すると、カスタムファイルが作成できるようになります。
$ bin/rails generate dev_seed test name:string
標準ジェネレータを拡張する
前述で作成したカスタムジェネレータをRails標準のモデルジェネレータ実行時に呼び出すようにします。
lib/generators/model/ のディレクトリに model_generator という標準と同じ名前のgeneratorを置くことで、プロジェクト固有のものが優先されます。
また、このファイルでは、Rails::Generators::NamedBase を継承するのではなく、標準のActiveRecord::Generators::ModelGenerator を継承しています。
また、それに伴い require や source_root もRails標準のものを参照するように調整しています。
そして、この標準ジェネレータを拡張したClassの最後に先ほど追加したカスタムジェネレータを引数と共に呼び出すメソッド invoke_dev_seed を追加します。
require 'rails/generators/active_record/model/model_generator'
class ModelGenerator < ActiveRecord::Generators::ModelGenerator
source_root ActiveRecord::Generators::ModelGenerator.source_root
def invoke_dev_seed
invoke 'dev_seed', [name] + attributes.map { |attr| "#{attr.name}:#{attr.type}" }
end
end
これで以下のようにモデルジェネレータを実行すると、Rails標準のファイルに加えて、カスタムジェネレータが最後に呼び出されてカスタムファイルが生成されます。
$ bin/rails generate model test string:string
create db/migrate/20251001000000_create_tests.rb
create app/models/test.rb
invoke rspec
create spec/models/test_spec.rb
create db/seeds/development/test.rb
Discussion