Railsのプラグインは、コアフレームワークの拡張や変更に使われます。プラグインは以下の機能を提供します。
このガイドの内容:
本ガイドでは以下を理解するために、テスト駆動方式によるプラグイン開発方法を解説します。
acts_as
プラグインと同様の手法でApplicationRecord
にメソッドを追加するここからは説明上、自分のことを熱烈なバードウォッチャーであるとお考えください。 あなたは鳥の中でも特に Yaffle(ヨーロッパアオゲラ)が大好きで、この鳥の素晴らしさを他の開発者と共有するプラグインを作成したいと考えています。
Railsのプラグインはgem化されています。gem形式を採用したことで、必要に応じてプラグインをRubygemsとBundlerでさまざまなRailsアプリケーションと共有することも可能です。
RailsにはあらゆるRails拡張機能の開発用スケルトンを作成するrails plugin new
というコマンドが用意されています。これで作成したスケルトンはダミーのRailsアプリケーションを使用して結合テストを実行することもできます。プラグインを作成するには以下のコマンドを実行します。
$ rails plugin new yaffle
利用法とオプションは以下の方法で表示できます。
$ rails plugin new --help
プラグインを作成したディレクトリに移動してyaffle.gemspec
ファイルを編集し、値にTODO
がある行をすべて以下の要領で置き換えます。
spec.homepage = "http://example.com" spec.summary = "(Yaffleの概要)" spec.description = "(Yaffleの説明)" ... spec.metadata["source_code_uri"] = "http://example.com" spec.metadata["changelog_uri"] = "http://example.com"
終わったらbundle install
コマンドを実行します。
これで、自動生成されたテストをbin/test
コマンドで実行すると以下のような結果が出力されるはずです。
$ bin/test ... 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
生成が無事完了し、いつでも機能を追加できる状態であることがわかります。
このセクションでは、Railsアプリケーションのどこでも利用できるメソッドをStringクラスに追加する方法を解説します。
この例では、to_squawk
(ガーガー鳴くの意)という名前のメソッドをStringクラスに追加します。最初に、テストファイルを1つ作成してそこにアサーションをいくつか追加しましょう。
# 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
bin/test
を実行してテストします。to_squawk
は実装されていないので、当然テストは失敗します。
$ bin/test E Error: CoreExtTest#test_to_squawk_prepends_the_word_squawk: NoMethodError: undefined method `to_squawk' for "Hello World":String bin/test /path/to/yaffle/test/core_ext_test.rb:4 . Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s. 2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
ここまで準備できれば、いよいよコーディング開始です。
lib/yaffle.rb
にrequire "yaffle/core_ext"
を追加します。
# yaffle/lib/yaffle.rb require "yaffle/version" require "yaffle/railtie" require "yaffle/core_ext" module Yaffle # ここにコードを書く end
最後にcore_ext.rb
ファイルを作成してto_squawk
メソッドを追加します。
# yaffle/lib/yaffle/core_ext.rb class String def to_squawk "squawk! #{self}".strip end end
プラグインのあるディレクトリでbin/test
テストを実行して、メソッドがテストにパスすることを確認します。
$ bin/test ... 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
最後にメソッドを実際に使ってみましょう。test/dummy
ディレクトリに移動してbin/rails console
を実行し、鳴き声(squawk)を出してみましょう。
irb> "Hello World".to_squawk => "squawk! Hello World"
プラグインでは、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/version" require "yaffle/railtie" 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
# yaffle/lib/yaffle/acts_as_yaffle.rb module Yaffle module ActsAsYaffle # ここにコードを書く end end
このプラグインはモデルにlast_squawk
という名前のメソッドが追加されていることを前提にしています。しかし、プラグインがインストールされた環境には、そのモデルに目的の異なるlast_squawk
という名前のメソッドが既にあるかもしれません。そこで、このプラグインではyaffle_text_field
という名前のクラスメソッドを1つ追加することによって名前を変更できるようにしたいと思います。
最初に、以下のように振る舞う、失敗するテストを1つ作成します。
# 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
bin/test
を実行すると以下が出力されます。
$ bin/test # Running: ..E Error: ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NameError: uninitialized constant ActsAsYaffleTest::Wickwall bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 E Error: ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NameError: uninitialized constant ActsAsYaffleTest::Hickwall bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s. 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
この結果から、そもそもテストで必要なモデル(Hickwallと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 < ApplicationRecord acts_as_yaffle end
# test/dummy/app/models/wickwall.rb class Wickwall < ApplicationRecord 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 class_methods do def acts_as_yaffle(options = {}) end end end end
# test/dummy/app/models/application_record.rb class ApplicationRecord < ActiveRecord::Base include Yaffle::ActsAsYaffle self.abstract_class = true end
終わったらcd ../..
を実行してプラグインのルートディレクトリに戻り、bin/test
を実行してテストを再実行します。
$ bin/test # Running: .E Error: ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974ebbe9d8> bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 E Error: ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974eb8cfc8> bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 . Finished in 0.008263s, 484.0999 runs/s, 242.0500 assertions/s. 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 class_methods do def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s end end end end
# test/dummy/app/models/application_record.rb class ApplicationRecord < ActiveRecord::Base include Yaffle::ActsAsYaffle self.abstract_class = true end
bin/test
を実行すると、今度のテストはすべてパスします。
$ bin/test ... 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
今度はこのプラグインに'squawk'というメソッドを追加して、acts_as_yaffle
を呼び出すすべてのActive Recordオブジェクトに追加しましょう。'squawk'メソッドはデータベースのフィールドにある値のいずれか1つを設定するだけのシンプルなものです。
最初に、以下のように振る舞う、失敗するテストを1つ作成します。
# 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 def squawk(string) write_attribute(self.class.yaffle_text_field, string.to_squawk) end end class_methods do def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s end end end end
# test/dummy/app/models/application_record.rb class ApplicationRecord < ActiveRecord::Base include Yaffle::ActsAsYaffle self.abstract_class = true end
最後にbin/test
を実行すると以下の結果が表示されます。
$ bin/test ... 6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
上のコードではモデルのフィールドへの書き込みをwrite_attribute
で行っていますが、これはあくまでプラグインからモデルとやりとりする際の書き方を示すための一例にすぎません。このメソッドを使うのが適切とは限らないこともあるのでご注意ください。たとえば同じコードを以下のように書くこともできます。
send("#{self.class.yaffle_text_field}=", string.to_squawk)
gemにジェネレータを追加するには、単にジェネレータを作成してプラグインのlib/generators
ディレクトリに置くだけでもかまいません。ジェネレータの作成方法について詳しくはRails ジェネレータとテンプレート入門を参照してください。
開発中のgemでもGitリポジトリで簡単に共有できます。今回のYaffle gemを他の開発者と共有するには、コードをGitHubなどのGitリポジトリにコミットしておき、gemを使いたいアプリケーションのGemfile
に一行書くだけでできます。
gem "yaffle", git: "https://github.com/rails/yaffle.git"
後はbundle install
を実行すればgemの機能をアプリケーションで利用できるようになります。
gemを正式なリリースとして一般公開するのであればRubyGemsで公開します。
あるいは、Bundlerのrakeタスクも便利に使えます。以下に完全なリストを示します。
$ bundle exec rake -T $ bundle exec rake build # yaffle-0.1.0.gemをビルドしてpkgディレクトリに置く $ bundle exec rake install # yaffle-0.1.0.gemをビルドしてsystem gemとしてインストールする $ bundle exec rake release # tagをv0.1.0に設定してyaffle-0.1.0.gemをRubygemsにプッシュする
RubyGemsサイトでgemを公開する方法について詳しくは、はじめてのRuby Gem作成・パブリッシュ方法(英語)を参照してください。
プラグインの開発が終わってデプロイする段階になったら、プラグインの利用者のためにちゃんとしたドキュメントを作成しましょう。幸い、プラグインのドキュメント作成は簡単です。
最初に、プラグインの使い方をREADMEファイルに詳しく記載します。以下の項目は忘れずに記入してください。
READMEの内容が固まってきたら、コードをひととおりチェックしてすべてのメソッドにRDoc形式のコメントを追加します。このコメントは開発者にとって役立つ情報となります。パブリックAPIにしたくないメソッドには# :nodoc:
というコメントを追加します。
コメントを付け終わったらプラグインのルートディレクトリに移動して以下を実行します。
$ bundle exec rake rdoc
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。