1 設定
以前と異なり、現在Railsのプラグインはgemとしてビルドします。gem形式を取っているので、必要であればRubygemsとBunderを使用してプラグインを他のRailsアプリケーションと共有することもできます。
1.1 gem形式のプラグインを生成する
RailsにはあらゆるRails拡張機能の開発用スケルトンを作成するrails plugin newというコマンドが最初から装備されています。これで作成したスケルトンはダミーのRailsアプリケーションを使用して結合テストを実行することもできます。プラグインを作成するには以下のコマンドを実行します。
$ bin/rails plugin new yaffle
使用法とオプションは以下の方法で表示できます。
$ bin/rails plugin new --help
2 新しく生成したプラグインをテストする
プラグインを作成したディレクトリに移動してbundle installコマンドを実行し、自動生成されたテストをrakeコマンドで実行します。
実行結果は以下のようになります。
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
生成が無事完了し、いつでも機能を追加できる状態であることがわかります。
3 コアクラスを拡張する
このセクションでは、Railsアプリケーションのどこでも利用できるメソッドをStringクラスに追加する方法を解説します。
この例では、to_squawk(ガーガー鳴くの意)という名前のメソッドをStringクラスに追加します。最初に、テストファイルをひとつ作成してそこにアサーションをいくつか追加しましょう。
# yaffle/test/core_ext_test.rb
require 'test_helper'
class CoreExtTest < ActiveSupport::TestCase
def test_to_squawk_prepends_the_word_squawk
assert_equal "squawk! Hello World", "Hello World".to_squawk
end
end
rakeを実行してテストします。to_squawkは実装されていないので、当然テストは失敗します。
1) Error:
CoreExtTest#test_to_squawk_prepends_the_word_squawk:
NoMethodError: undefined method `to_squawk' for "Hello World":String
/path/to/yaffle/test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'
ここまで準備できれば、いよいよコーディング開始です。
lib/yaffle.rbにrequire 'yaffle/core_ext'を追加します。
# yaffle/lib/yaffle.rb require 'yaffle/core_ext' module Yaffle end
最後にcore_ext.rbファイルを作成してto_squawkメソッドを追加します。
# yaffle/lib/yaffle/core_ext.rb
String.class_eval do
def to_squawk
"squawk! #{self}".strip
end
end
プラグインのあるディレクトリでrakeテストを実行して、メソッドがテストにパスすることを確認します。
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
最後にメソッドを実際に使ってみましょう。test/dummyディレクトリに移動してガーガー鳴いてみましょう(squawk)。
$ bin/rails console >> "Hello World".to_squawk => "squawk! Hello World"
4 "acts_as"メソッドをActive Recordに追加する
プラグインでは、acts_as_何とかという名前のメソッドをモデルに追加することがよく行われます。この例ではそれにならってacts_as_yaffleというメソッドを追加してみます。これはsquawkメソッドを自分のActive Recordモデルに追加するメソッドです。
最初に以下のファイルを準備します。
# yaffle/test/acts_as_yaffle_test.rb require 'test_helper' class ActsAsYaffleTest < ActiveSupport::TestCase end
# yaffle/lib/yaffle.rb require 'yaffle/core_ext' require 'yaffle/acts_as_yaffle' module Yaffle end
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
# ここにコードを書く
end
end
4.1 クラスメソッドを追加する
このプラグインはモデルにlast_squawkという名前のメソッドが追加されていることを前提にしています。しかし、プラグインがインストールされた環境には、そのモデルに目的の異なるlast_squawkという名前のメソッドが既にあるかもしれません。そこで、このプラグインではyaffle_text_fieldという名前のクラスメソッドをひとつ追加することによって名前を変更できるようにしたいと思います。
最初に、以下のように振る舞う、失敗するテストをひとつ作成します。
# yaffle/test/acts_as_yaffle_test.rb
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
end
rakeを実行すると以下が出力されます。
1) Error:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NameError: uninitialized constant ActsAsYaffleTest::Hickwall
/path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
2) Error:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NameError: uninitialized constant ActsAsYaffleTest::Wickwall
/path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
この結果から、テストの対象となるモデル (Hickwall and Wickwall) がそもそもないことがわかります。必要なモデルはダミーのRailsアプリケーションで簡単に作成できます。test/dummyディレクトリに移動して以下のコマンドを実行します。
$ cd test/dummy $ bin/rails generate model Hickwall last_squawk:string $ bin/rails generate model Wickwall last_squawk:string last_tweet:string
これで必要なデータベーステーブルをテストデータベース内に作成するための準備が整いました。作成は、ダミーアプリケーションのディレクトリに移動してデータベースのマイグレーションを実行することで行います。最初に以下を実行します。
$ cd test/dummy $ bin/rails db:migrate
続いて、このディレクトリでHickwallモデルとWickwallモデルを変更し、これらのモデルにyafflesとしての振る舞いが期待されていることが伝わるようにします。
# test/dummy/app/models/hickwall.rb class Hickwall < ActiveRecord::Base acts_as_yaffle end # test/dummy/app/models/wickwall.rb class Wickwall < ActiveRecord::Base acts_as_yaffle yaffle_text_field: :last_tweet end
acts_as_yaffleメソッドを定義するコードも追加します。
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
# ここにコードを書く
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
終わったらcd ../..を実行してプラグインのルートディレクトリに戻り、rakeを実行してテストを再実行します。
1) Error:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e3b218>
activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing'
/path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
2) Error:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x007fd105e409c0>
activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing'
/path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
開発がだいぶ進んできました。今度はacts_as_yaffleメソッドを実装し、テストがパスするようにしましょう。
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
rakeを実行すると、今度のテストはすべてパスします。
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
4.2 インスタンスメソッドを追加する
今度はこのプラグインに'squawk'というメソッドを追加して、'acts_as_yaffle'を呼び出すすべてのActive Recordオブジェクトに追加しましょう'squawk'メソッドはデータベースのフィールドにある値のいずれかひとつを設定するだけのシンプルなものです。
最初に、以下のように振る舞う、失敗するテストをひとつ作成します。
# yaffle/test/acts_as_yaffle_test.rb
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
def test_hickwalls_squawk_should_populate_last_squawk
hickwall = Hickwall.new
hickwall.squawk("Hello World")
assert_equal "squawk! Hello World", hickwall.last_squawk
end
def test_wickwalls_squawk_should_populate_last_tweet
wickwall = Wickwall.new
wickwall.squawk("Hello World")
assert_equal "squawk! Hello World", wickwall.last_tweet
end
end
テストを実行して、最後に追加した2つのテストが失敗することを確認します。失敗のメッセージには"NoMethodError: undefined method `squawk'"が含まれているので、'acts_as_yaffle.rb'を以下のように更新します。
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
end
module LocalInstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
最後にrakeを実行すると以下の結果が表示されます。
6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
上のコードではwrite_attributeを使用してモデルのフィールドへの書き出しを行っていますが、これはあくまでプラグインからモデルとやりとりする際の書き方を示すための一例にすぎません。この書き方が適切とは限らないこともあるのでご注意ください。たとえば同じコードを以下のように書くこともできます。
send("#{self.class.yaffle_text_field}=", string.to_squawk)
5 ジェネレータ
gemにジェネレータを含めるには、単にジェネレータを作成してプラグインのlib/generatorsディレクトリに置くだけでもかまいません。ジェネレータの作成方法の詳細についてはRails ジェネレータとテンプレート入門を参照してください。
6 gemを公開する
開発中のgemであってもGitリポジトリで簡単に共有できます。今回のYaffle gemを他の開発者と共有するには、コードをGithubなどのGitリポジトリにコミットしておき、gemを使用したいアプリケーションのGemfileに一行書くだけで済みます。
gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git'
後はbundle installを実行すればgemの機能をアプリケーションで利用できるようになります。
gemを正式なリリースとして一般公開するのであればRubyGemsでパブリッシュします。 RubyGemsサイトでgemを公開する方法の詳細については、はじめてのRuby Gem作成・パブリッシュ方法(英語) を参照してください。
7 RDocドキュメント
プラグインの開発が一段落してデプロイする段階になったら、プラグインの利用者のためにちゃんとしたドキュメントを作成しましょう。幸い、プラグインのドキュメント作成は簡単です。
最初に、プラグインの使用法をREADMEファイルに詳しく記載します。以下の項目は忘れずに記入してください。
- 自分の名前
- インストール方法
- アプリケーションに機能を追加する具体的な方法 (一般的なユースケースもいくつか例として追加)
- 警告、注意点、ヒントなど (ユーザーが無駄な時間を使わずに済むように)
READMEの内容が固まってきたら、コードをひととおりチェックしてすべてのメソッドにrdoc形式のコメントを追加します。このコメントは開発者にとって役立つ情報となります。パブリックAPIにしたくない箇所には'#:nodoc:'というコメントを追加します。
コメントを付け終わったらプラグインのルートディレクトリに移動して以下を実行します。
$ bin/rails rdoc
7.1 参考資料
- Bundlerを使用してRubyGemを開発する(英語)
- gemspecsを意図したとおりに使う(英語)
- Gemspecリファレンス(英語)
- GemPlugin: Railsプラグインの今後の見通し(英語)
フィードバックについて
本ガイドは GitHub上の yasslab/railsguides.jp で管理・公開されております。 本ガイドを読んで気になる文章や間違ったコードを見かけたら、上記リポジトリにてお気軽に Issue を出して頂けると嬉しいです。また、「Pull Request を送りたい!」という場合には、Ruby on Railsガイドのガイドラインと、READMEに記載されている「翻訳の流れ」をご参考にしてください。
なお、原著における間違いを見つけたら、「Ruby on Railsに貢献する方法」に記されているRailsのドキュメントに貢献するを参考にしながら、ぜひRailsコミュニティに貢献してみてしてください :)
本ガイドの品質向上に向けて、皆さまのご協力が得られれば幸いです。よろしくお願い致します。