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を作成していきますので、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
コメント