🏭

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標準のモデルジェネレータで実行するので、標準のモデルジェネレータと同じ引数を受け取り、カスタムファイルのテンプレートを生成する処理を書いています。
このファイルのパブリックメソッドはジェネレータ実行時に上から実行されます。

プライベートメソッドでは、テンプレートがカラムのタイプに応じて適切なダミーデータを生成するための処理を定義しています。

lib/generators/dev_seed/dev_seed_generator.rb
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 するコードを生成するものです。

lib/generators/dev_seed/templates/dev_seed.rb.erb
# 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 を継承しています。

また、それに伴い requiresource_root もRails標準のものを参照するように調整しています。

そして、この標準ジェネレータを拡張したClassの最後に先ほど追加したカスタムジェネレータを引数と共に呼び出すメソッド invoke_dev_seed を追加します。

lib/generators/model/model_generator.rb
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