【Gem作成】rails generatorを使ったテンプレートのgemを作る

  • このエントリーをはてなブックマークに追加

railsアプリケーションをある程度作成した後、gem化して他のリポジトリに流用していきたいと考えたりしませんでしょうか?または、deviseやrspecといったgemのように、rails generate devise:installのようなコマンドを作ってみたいと思ったりしませんでしょうか?

今回はsafariparkという名前のgemをフォルダ準備から作成していき、rails generate safaripark:installのようなコマンドで自動でファイル生成ができるように実装します。

Contents

ゴールと想定読者

rails generate xxx:installのようなコマンドで自動でファイル生成をするアプリを一から作ります。

  • gemを一から作る人
  • generatorの使い方を学ぶ人

最終コードが見たい人は記事の最後の記しています。

私は先日Railsで簡易的なCMSを作りました。他の案件でも流用できるようなファイルが多いのでgemにしてみようと思い、railsのgeneratorを学んでみようと思いました。調べた内容をもとに備忘録として残します。

テンプレートのgemを作る

テンプレートのgemを作る

今回はgemを作成していきますので、rails newではなくrails plugin newというコマンドを使用していきます。

まずはフォルダを作成します。

rails plugin new safaripark

このコマンドで、以下ようなディレクトリ構造のgemを作る雛形が作成されました。

├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin
│   └── test
├── lib
│   ├── safaripark
│   │   ├── railtie.rb
│   │   └── version.rb
│   ├── safaripark.rb
│   └── tasks
│       └── safaripark_tasks.rake
├── safaripark.gemspec
└── test
    ├── dummy
        └── 省略

gemは通常のrailsアプリケーションと異なり、アプリ名.gemspecというファイルが生成されます。このファイルはこのgemの説明をするための重要なファイルです。gemとして動作させるためにはTODOと書かれている場所を編集しなければなりません。今回は以下のように記述を変えていきます。

$:.push File.expand_path("lib", __dir__)

# Maintain your gem's version:
require "safaripark/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |spec|
  spec.name        = "safaripark"
  spec.version     = Safaripark::VERSION
  spec.authors     = ["ryucoding"]
  spec.email       = ["ryucoding@example.com"]
  # spec.homepage    = "TODO" # gemの公開URLを入れる。現在はとりあえずコメントアウト
  spec.summary     = "The simple generator application"
  spec.description = "The simple generator application. Make it HeadlessCMS template in the future."
  spec.license     = "MIT"

  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
  # to allow pushing to a single host or delete this section to allow pushing to any host.
  if spec.respond_to?(:metadata)
    spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
  else
    raise "RubyGems 2.0 or newer is required to protect against " \
      "public gem pushes."
  end

  spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]

  spec.add_dependency "rails", "~> 6.0.3", ">= 6.0.3.5"

  spec.add_development_dependency "sqlite3"
end

Generatorを作ってみる

gemで最初に読まれるファイルはlib/アプリ名.rbです。gemの中身を作っていく場合は今回であれば、lib/safaripark.rbの中にコードを書いていきます。

require "safaripark/railtie"

module Safaripark
  # Your code goes here...
end

しかし、今回作成するのはrailsのgeneratorです。generatorとは、以下のようなコマンドで動作し、自動でファイルを生成したりしてくれる機能です。

rails generate model user
rails generate rspec:install
rails generate devise user

Railsのアプリケーションで新しくオリジナルのgeneratorを作る場合は以下のようなコマンドで作成することができます。

rails generate generator ジェネレータ名

一方で、gemで新しくオリジナルのgeneratorを作る場合は自分でディレクトリとファイルを作ります。

generatorsディレクトリとsample_generator.rbを作ってみます。

├── lib
│   ├── generators
│   │   └── sample_generator.rb
│   ├── safaripark
│   │   ├── railtie.rb
│   │   └── version.rb
│   ├── safaripark.rb
│   └── tasks
│       └── safaripark_tasks.rake

generatorsディレクトリに入れているとrailsが命名規則に従って自動でgeneratorだと判断し認識してくれます。

sample_generator.rbには以下のように記述していきましょう。

require 'rails/generators/base'

class SampleGenerator < Rails::Generators::Base
  desc "create file"

  def create_model_file
    create_file "app/models/test.rb", "create from my gem"
  end

  def create_controller_file
    create_file "app/controller/tests_controller.rb", "create from my gem"
  end
end

generatorはRails::Generators::Baseを継承して作成します。クラスの中に定義したインスタンスメソッドが実際に行われる処理です。上から順に自動で呼び出されていくので、create_model_fileメソッド、create_controller_fileメソッドの順で実行されていきます。

create_fileメソッドはthorというgemで提供されているメソッドです。新しいファイルを作成します。

https://github.com/erikhuda/thor

他のメソッドについてはまた別の機会に紹介できればいいなと思ってます。

(追記)他のメソッドの紹介記事を書きました。こちらもよかったら一目見ていってください。

また、クラス名の部分が小文字になってそのままコマンドになります。

rails generate sample

コマンドを実行して動作を確認する

まずは作ったコードがgemとして動作するように変換しなければなりません。

rake buildコマンドを実行します。

rake build
# => safaripark 0.1.0 built to pkg/safaripark-0.1.0.gem.

通常であれば、他のアプリケーションを用意してgemをインストールしていきます。rails plugin newコマンドで作成した雛形はtest/dummyというディレクトリの中にRailsのアプリケーションが用意されているので、開発中はこのアプリケーションを使います。

Gemfileなどが用意されているわけでもなく、gemはすでに読み込まれた状態になっているので、test/dummyディレクトリに移動し、コマンドを実行してみます。

safaripark % cd test/dummy
s/t/dummy % rails generate sample
# =>  create  app/models/test.rb
# =>  create  app/controller/tests_controller.rb

test/dummyディレクトリの中のモデルとコントローラに一つずつファイルが作成されればうまく実装できています。

ここまでのまとめ(gemとgeneratorを作る)

  • gemはrails plugin newコマンドで作る
  • まずはgemspecを編集する
  • Rails::Generators::Baseを継承したクラスはインスタンスメソッドが順に実行される

では次に、rails generate safaripark:installのようなコマンドで実行できるようにしてみます。

rails generate xxx:installのコマンドを作る

rails generate xxx:installのようなコマンドにするためには、ディレクトリ構造とファイル名を以下のようにする必要があります。

├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin
│   └── test
├── lib
│   ├── generators
│   │   ├── safaripark
│   │   │   └── install
│   │   │       └── install_generator.rb
│   │   └── sample_generator.rb
│   ├── safaripark
│   │   ├── railtie.rb
│   │   └── version.rb
│   ├── safaripark.rb
│   └── tasks
│       └── safaripark_tasks.rake
├── pkg
│   └── safaripark-0.1.0.gem
├── safaripark.gemspec
└── test
    ├── dummy
        └── 省略

lib/generators/アプリ名/コマンド名/コマンド名_generator.rbのようにすれば認識してくれます。installというコマンド名であればinstall_generator.rbになります。

install_generator.rbには以下のように記述します。

require 'rails/generators/base'

module Safaripark
  class InstallGenerator < Rails::Generators::Base
    desc "create file"
    def test1
      create_file "config/initializer/safaripark.rb", "create from my gem"
    end
  end
end

注目するべき点は、3行目のmodule Safariparkとしている部分です。

safaripark:installのようなコマンドにするためには必要な記述になります。

install_generator.rbのモジュール名はlib/アプリ名.rbの中のモジュール名と同じにしておきましょう。

require "safaripark/railtie"

module Safaripark
  # Your code goes here...
end

これで準備ができました。ではgemとして使える形に変換して動作確認をしていきます。gemを更新する場合には次の節のようにバージョンを変えていきます。

gemのバージョンを変更する

gemに追加実装をして変化を加えた場合はバージョンをアップして変換していくのがいいと思います。

lib/アプリ名/version.rbを編集します。

module Safaripark
  VERSION = '0.1.1' # 0.1.0 -> 0.1.1
end

rake buildで変換して動作確認します。

safaripark % rake build
# => safaripark 0.1.1 built to pkg/safaripark-0.1.1.gem.
safaripark % cd test/dummy
s/t/dummy % rails generate safaripark:install
# =>    create  config/initializer/safaripark.rb

まとめ

  • rails generate xxx:installコマンドを作るにはディレクトリ構造をlib/generators/アプリ名/コマンド名/コマンド名_generator.rbにする
  • 追加実装したらgemのバージョンを変更する

最終コード

require 'rails/generators/base'

module Safaripark
  class InstallGenerator < Rails::Generators::Base
    desc "create file"
    def test1
      create_file "config/initializer/safaripark.rb", "create from my gem"
    end
  end
end
  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

スポンサードリンク

コメント

コメントを残す

*