初めまして、3月からクラウドワークスの開発Div.にjoinした山本です。
好きなお酒は芋焼酎で割り方はお湯割一択です!
さて、私は入社してから社内の業務支援系のシステムの新規開発に携わってます。
今回の内容は社内ツール開発時に管理画面をRails Engineで作った時の話をしたいと思います。
(筆者は今回初めてRails Engineを触ったので、生暖かい目で読んでいただけると助かります)
本記事はRails Guidesの内容を参考にさせてもらってますがまとめとして残そうと思います。
そもそもRails Engineってどんな機能?
アプリケーションのミニチュアのようなものであり、ホストアプリケーションに機能を提供します。
full/mountableの2種類がありrails plugin new
するときにそれぞれ --full(省略可)
、--mountable
オプションをつけて作成します。
--mountable
オプションをつけるとRailsアプリのスケルトン構成を自動的に作ってくれます。
今回は(慣れている作りで)比較的時間をかけずに実装したいということで--mountable
オプションをつけて作ることにしました。
注)ここより先は読みやすいように
--mountable
オプションを使ったRails Engine → Mountable Engine
として説明していきます。
Mountable Engineの特徴
ホストアプリケーション内に別アプリを入れ子して機能単位で管理できる仕組みという認識です。
認証機能を提供するdeviseや管理画面を分離する機能を提供するRailsAdminなどが有名かと思います。
特徴としては
- Railsアプリと同等のスケルトン構造である。
- ホストアプリケーションにマウントすることで容易に機能の拡張ができる。
- 名前空間分離されているためホストアプリケーションとの衝突を回避できる。
- ダミーのテスト用アプリケーションがデフォルトで
test/dummy
に配置されており個別でテスト等の管理ができる。
今回の経緯
- 管理画面の機能を設計から任された。
- やるなら何か新しいことにチャレンジしてみよう。
- あれこれ探しているうちにRails Engineを使って管理画面を作っているエントリを見つける。*1
- これでいこう!
はい、今思い返すと圧倒的に安易な考えですね。
というわけで管理画面を作った訳ですが、結論から言うと今回の仕様の関係で幾つかの問題があり、導入はできませんでした。
振返りの意味も含めて以下でまとめたいと思います。
注)ここより先は読みやすいように
ホストアプリケーション → MainApp
Mountable Engine側のアプリケーション → EngineApp で書いていきます
今回導入できなかった理由について
- 管理画面でやりたいことがMainAppのマスタやユーザ管理が主だったためEngineAppに対する依存が大きくなりそうだった。
- そのため、EngineAppの独立性を保ち続けるのが辛い。
- EngineApp側からのMainApp側に依存する形で試作を作ってしまったので個別にテストが書けなかった。
- どこまでコストがかかるか想定できず、それ以上踏み込んでいけなかった。
はい、「作る前に気付こうね(#^ω^)ピキピキ」な内容です。。。一番の要因としては
仕組みの理解が浅くMainAppとEngineAppを相互依存する形で作ってしまったため、つらくなることが容易に想像できたことでした。
以下、Rails Guidesより
アプリケーションは いかなる場合も エンジンよりも優先されます。ある環境において、 最終的な決定権を持つのはアプリケーション自身です。エンジンはアプリケーションの動作を大幅に変更するものではなく、 アプリケーションを単に拡張するものです。
あくまでMainAppに機能を拡張するものであり、EngineApp側に責任を押し付けすぎるというのは本末転倒でした。。。
まとめると完全に理解が足りてなかった。
さらに気づいたこと
反省点
- 今回は社内ツールの新規開発なのにそもそもRails Engineとして切出しを検討するのは早すぎた。(まだその段階ではなかった。)
- 知見不足のために、スピード感を持って柔軟に対応ができなかった。
- 検討する時間を十分に取れないのに、半ば思いつきで走ってしまった。
やってよかった点
- パワフルな機能なのでこの先いろいろ所で使えそうな希望が見えた。
- 個人的にはあまり触らない機能を知る取っ掛かりができた。
- 何より知らないことに取り組むのはモチベーションにつながる。
その後
Mountable Engineでの管理画面はできませんでしたが、何かしら作ってみたいということで
Rails Guidesを参考に掲示板(bulletin_board)の機能の作りました。
まずはアプリケーションの雛形とusersを作成。
rails new main_app --skip-bundle cd main_app bundle install --path vendor/bundle bundle exec rails g scaffold user name:string bundle exec rake db:create bundle exec rake db:migrate bundle exec rails s
動作確認ができたら早速mountable engineをアプリ内にplugin newコマンドで作成します。
$ bundle exec rails plugin new bulletin_board --mountable --skip-bundle
デフォルトではTest::Unitを使う仕組み(engine_app直下にtest/dummy)が作成されますが、rspecを利用したい場合はオプションをつけて作成します。
(-T: Test::Unitを使う仕組みをskipする --dummy-path: rspecで使うdummy_appを作成)
また、MainApp直下が気に入らない場合は以下のように指定のディレクトリに作成も可能です。
$ bundle exec rails plugin new vendor/engines/bulletin_board --mountable --skip-bundle -T --dummy-path=spec/dummy
ディレクトリの構成を見てみると以下のようになってます。
基本的にはrails new
で作成したものと同じ構成になっています。
engineでも通常のアプリと同じようにscaffoldが使えるのでboard(掲示板)Classを作ります。
$ cd bulletin_board $ bundle exec rails g scaffold board title:string
作成されたモデルを見てみるとClass名がBulletinBoard::Board
となっていると思います。
MainApp側との衝突を避けるためにnamespaceが自動で付加さるようです。
$ bundle exec rake db:create $ bundle exec rake db:migrate == 20160516094903 CreateBulletinBoardBoards: migrating ======================== -- create_table(:bulletin_board_boards) -> 0.0011s == 20160516094903 CreateBulletinBoardBoards: migrated (0.0011s) ===============
作成されたマイグレーションにはbulletin_board
のscopeが付いていることが確認できます。
ここまで出来た段階でbulletin_boardがちゃんと動くか動作を確認してみます。
Engineではtest/dummy
に移動することでrails s
が使えます。
$ cd test/dummy $ bundle exec rails s
http://localhost:3000/bulletin_board/boards/
問題なく見れますね。
次に掲示板のコメント機能を作ります。
$ cd ../../ $ bundle exec rails g model comment text:string board_id:integer $ bundle exec rake db:migrate == 20160516100103 CreateBulletinBoardComments: migrating ====================== -- create_table(:bulletin_board_comments) -> 0.0010s == 20160516100103 CreateBulletinBoardComments: migrated (0.0011s) =============
ここまででEngine側の作り方はある程度イメージができたかと思います。
ここから先はいつも通りの作り方なので省略します。
出来上がりは下のような感じ。
掲示板にコメントを追加していくイメージです。
EngineAppができたので次にMainApp側から使えるように以下を追記してエンジンをマウントします。
Gemfile
gem 'bulletin_board', path: 'bulletin_board'
config/routes.rb
mount BulletinBoard::Engine, at: 'bulletin_board'
更にDBを共有するためMainApp側にEngineApp側で作成したマイグレーションファイルをコピーします。
cd main_app/ $ bundle exec rake bulletin_board:install:migrations Copied migration 20160515141241_create_bulletin_board_boards.bulletin_board.rb from bulletin_board Copied migration 20160515141242_create_bulletin_board_comments.bulletin_board.rb from bulletin_board $ $ bundle exec rake db:migrate == 20160515141241 CreateBulletinBoardBoards: migrating ======================== -- create_table(:bulletin_board_boards) -> 0.0010s == 20160515141241 CreateBulletinBoardBoards: migrated (0.0011s) =============== == 20160515141242 CreateBulletinBoardComments: migrating ====================== -- create_table(:bulletin_board_comments) -> 0.0006s == 20160515141242 CreateBulletinBoardComments: migrated (0.0006s) =============
ここまでで準備は終わりです。
最後にuserの詳細画面からEngineApp側の掲示板へ遷移できるようにしてみます。
app/views/users/show.html.erb
<p id="notice"><%= notice %></p> <p> <strong>Name:</strong> <%= @user.name %> </p> + <%= link_to 'BulletinBoard', bulletin_board.boards_path %> | <%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>
それとは逆にEngineAppからMainAppへの遷移も以下のようにpathを指定すれば可能です。
bulletin_board/app/views/bulletin_board/boards/index.html.erb
<p id="notice"><%= notice %></p> <h1>Listing Boards</h1> -- 省略 -- <br> + <%= link_to 'Back MainApp', main_app.root_path %> <%= link_to 'New Board', new_board_path %>
使ってみて良かったと思う点
これから試すこと
- rspecでテストを書く。(今回ここまでやろうとしてましたが、間に合いませんでした。。。)
- デプロイ時にテストと静的解析ができるようにCIとの連携を考える。
- Apiを提供するタイプの切出しを模索してみる。
最後に
いかがでしたでしょうか。私の感想としては思ったよりも柔軟にできて、いろいろな場面での利用ができそうな印象を持ちました。
学習コストは多少ありますが、モノリシックなアプリケーションや、複数アプリケーションで汎用的な機能をコピーして使いまわしているなどの場面で威力を発揮してくれるはずです。
「あれ?これは。。。」となった時は是非試してみて下さい。
この記事を見て少しでも興味を持って頂ければ幸いです。
クラウドソーシングのクラウドワークスではチャレンジングな環境でサービスと一緒に成長していきたいエンジニアを募集しています!