Edited at

mountable engineで管理画面を作る

More than 1 year has passed since last update.

表参道.rb #17 ~管理画面について語ろう~ - connpass の発表資料



自己紹介



最近の仕事

とある社内アプリのリプレイス


  • Ruby 2.1.2 -> 2.3.3

  • Rails 4.0.0 -> 5.0.0.1

  • Resque -> Sidekiq

  • capistrano 2系 -> 3系

  • Bootstrap 2系 -> 3系

  • Debian Lenny 1 -> CentOS 7



最近のトラブル

image


image



Agenda


  • mountable engineとは?

  • 2種類のやり方を紹介


    • 1. アプリ本体の管理画面をengineにする

    • 2. gemに管理画面を内包してアプリ本体から使う





mountable engineとは?


  • Railsアプリに他のRailsアプリをgemとして組み込む仕組み



  • MVC全部gemとして別アプリ提供することができる

  • 長いので以下engineで表記



2種類のやり方を紹介


  1. 普通のRailsアプリの管理画面をengineに切り出す

  2. engine gemに管理画面のengineを内包してアプリ本体から使う


    • engine in engine



Rails 5.0.0.1で確認



1. 普通のRailsアプリの管理画面をengineに切り出す


ユースケース


  • ユーザからのリクエストを受ける本番アプリ(production-app)は複数サーバにデプロイしつつも、本番の管理画面(production-admin)は1サーバだけにデプロイしたい 2

  • しかしproduction-appでは管理画面の機能は組み込みたくないので完全に切り離せるようにしたい

  • 管理画面以外にもデバッグ系の機能をengineに切り出したいというケースもある


    • 開発環境では使いたいがステージングや本番だと使いたくない





やり方


rails plugin new admin

トップディレクトリ( GemfileRalefileがある場所)で下記を実行するとadminディレクトリが作られる

$ ./bin/rails plugin new admin --skip-bundle --mountable

create
create README.md
create Rakefile
create admin.gemspec
create MIT-LICENSE
create .gitignore
create Gemfile
create app
create app/controllers/admin/application_controller.rb
create app/helpers/admin/application_helper.rb
create app/jobs/admin/application_job.rb
create app/mailers/admin/application_mailer.rb
create app/models/admin/application_record.rb
create app/views/layouts/admin/application.html.erb
create app/assets/images/admin
create app/assets/images/admin/.keep
create config/routes.rb
create lib/admin.rb
create lib/tasks/admin_tasks.rake
create lib/admin/version.rb
create lib/admin/engine.rb
create app/assets/config/admin_manifest.js
create app/assets/stylesheets/admin/application.css
create app/assets/javascripts/admin/application.js
create bin/rails
create test/test_helper.rb
create test/admin_test.rb
append Rakefile
create test/integration/navigation_test.rb
vendor_app test/dummy

https://github.com/sue445/admin_example/commit/c18a92890b36495a8a796c3af234badd503d572a



admin/admin.gemspec を編集



親アプリのGemfileに組み込む

本体側の Gemfile に一行追加し、3


Gemfile

group :admin do

gem "admin", path: "admin"
end

routes.rb でmountするだけ。


config/routes.rb

mount Admin::Engine => "/admin"


https://github.com/sue445/admin_example/commit/79b817b34b70a2a5301c238a2a1f6e5f3ab76d9f

./bin/rails routes してadminが見えれば成功

$ ./bin/rails routes

Prefix Verb URI Pattern Controller#Action
admin /admin Admin::Engine

Routes for Admin::Engine:



メリット :ok_woman:


  • 管理画面のみで必要な機能をアプリ本体に含める必要がなくなる


    • admin gemを読まなければ管理系の機能は一切使えなくなるのでコード内でif文書くよりもシンプル





注意点


  • 本体がAPI mode の時にengineに一部悪影響が出ることがある





2. engine gemに管理画面のengineを内包してアプリ本体から使う


ユースケース


  • 1つのgemで「エンドユーザにAPIとして提供するengine」と「管理画面のengine」を提供する


    • 前者でmodelを提供しているとしたら、そのmodelのCRUDを管理画面で提供





やり方


さっきと同じようにengineを作る

rails plugin new engine_example --skip-bundle --mountable

https://github.com/sue445/engine_example/commit/045311540dd0d9561dbe96e86707aea2887f5ba5



gemspecからTODOを削る


  • 同じくそのままだと bundle install でエラーになるので

https://github.com/sue445/engine_example/commit/f3dd906b11937e21465fc35b19c255c53e5f98fa



engines/admin/ を作った後に下記ファイルを作成

module名はいい感じに変更すること


engines/admin/config/routes.rb

EngineExample::Admin::Engine.routes.draw do

end


engines/admin/lib/engine_example/admin/engine.rb

module EngineExample

module Admin
class Engine < ::Rails::Engine
isolate_namespace EngineExample::Admin
end
end
end


engines/admin/lib/engine_example/admin/railtie.rb

require "engine_example/admin/engine"

module EngineExample
module Admin
class Railtie < ::Rails::Railtie
end
end
end


https://github.com/sue445/engine_example/commit/e2a136186473025f510de72b7ee2f401157ac743



engine本体からadminのengineを読み込む

それぞれ下記を追加


engine_example.gemspec

s.require_paths = ["lib", "engines/admin/lib"]



lib/engine_example/railtie.rb

require "engine_example/admin/railtie"



lib/engine_example.rb

require "engine_example/railtie"



test/dummy/config/routes.rb

mount EngineExample::Admin::Engine => "/admin/engine_example"


https://github.com/sue445/engine_example/commit/598eb2ec90e19853a5490a0ffbf888e0ed54125b



動作確認

./bin/rails app:routes してadminが見えれば成功 4

 $ ./bin/rails app:routes

Prefix Verb URI Pattern Controller#Action
engine_example /engine_example EngineExample::Engine
engine_example_admin /admin/engine_example EngineExample::Admin::Engine

Routes for EngineExample::Engine:

Routes for EngineExample::Admin::Engine:



メリット :ok_woman:


  • gem化によって複数アプリでロジックが共通化されていると同時に、管理画面も共通化されていると運用コストが抑えられる


    • 特に複数アプリ運用してる時にCSの学習コストが抑えられる





開発Tips



  • cd test/dummy/bundle exec rails s すればdummyのRailsアプリが起動するのでviewの確認が捗る

  • 1つのgemに複数のengineを含めるか、それぞれgemを分けるかは適材適所


    • 基本的には一緒の方が管理しやすいが、メインのengineが巨大だと分けた方がいい





Adminのみ特定の認証をかけたい時(例:devise)


confg/routes.rb

Rails.application.routes.draw do

mount EngineExample::Engine => "/engine_example"

authenticate :admin_user do
mount EngineExample::Admin::Engine => "/admin/engine_example"
end
end




  • devise だと authenticate で囲むだけ

  • engineの外側に認証を持たせることで、engine側で認証機能を作る必要がなくなる

  • 1つのengine内で複数の認証を持たせるのは大変なので、ユーザに提供するengineと管理画面engineとで分けてしまうのが楽



参考文献





  1. Lennyのサーバなんて古すぎて誰もサワレニー 



  2. ここで書いている production-appやproduction-adminというのはRailsのenvironmentのことではなく、capistranoでデプロイする時のstageのようなものを指しています。 



  3. admin groupを付けているのは、本体とadminを別々に動かす時に本体のデプロイ時にadminを除外するため。( capistrano-bundler なら bundle_without にadminを追加) 



  4. test/dummy/ 配下のrake taskを呼びたい場合は task名の前に app: が付きます。(例:rails routes -> rails app:routes)