#author("2017-03-14T14:00:55+09:00","default:wikiwriter","wikiwriter")
#author("2017-03-14T14:07:32+09:00","default:wikiwriter","wikiwriter")
&tag(Rails/プラグイン/チュートリアル);
*目次 [#r8006278]
#contents

*関連ページ [#k731fe49]
-[[Rails/プラグイン]]

*参考情報 [#beef44fa]
-https://railsguides.jp/engines.html
-以下Rails 4.2の場合で説明。railsguidesはその時点でのRails最新版で書き直されているが過去のRailsに対応したバージョンもトップページから辿ることができる。

*エンジンプロジェクトの作成 [#w6819aae]
-以下のコマンドを実行する(rails-factoryディレクトリで実行)。
 bundle exec rails plugin new ~/work/blorgh --mountable --skip-bundle
- "~/work/blorgh"ディレクトリに移動。
-blorgh.gemspecを編集してTODOを削除。
-bundle installを実行
 bundle install --path=vendor/bundle

''ポイント''
-"--mountable"オプションを使用すると、ApplicationControllerやApplicationHelperが名前空間化される。ところが"-full"と違う。
-ダミーのテスト用アプリケーションが test/dummyに配置される。ダミーアプリケーションのルーティングファイルは、test/dummy/config/routes.rbとなる。



**エンジンの構成 [#c9d1c344]
-blorgh.gemspec。エンジンのルートディレクトリに存在。利用側から以下のように使う。
#pre{{
gem 'blorgh', path: "vendor/engines/blorgh"
}}
-アプリケーションあらはlib/blorgh.rbが読み込まれる。ここにプラグインごとの設定を記述するのもよい。
#pre{{
require "blorgh/engine"
 
module Blorgh
end
}}
-エンジンの基本ファイルはlib/blorgh/engine.rbのなかにある。
#pre{{
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
  end
end
}}
-これによってエンジンがRailsアプリにマウントされる。
-isolate_namespaceは名前空間の分離に重要。
-appディレクトリは通常のRailsアプリと同じ構成。
-testディレクトリはエンジンのテストを行うための場所。
-test/dummyにテスト用のRailsアプリが存在。このアプリはtest/dummy/config/routes.rbで以下のようにマウントしている。
#pre{{
Rails.application.routes.draw do
  mount Blorgh::Engine => "/blorgh"
end
}}
-test/integrationに統合テスト。test/modelsを作成しても良い。



*エンジン機能を提供する [#wf8f5949]
**Articleリソースの作成 [#n9ae1e08]
-scaffoldを実行。ネームスペース"blorgh"が追加されていることに注意。
#pre{{
$ bundle exec rails generate scaffold article title:string text:text
      invoke  active_record
      create    db/migrate/20170313042107_create_blorgh_articles.rb
      create    app/models/blorgh/article.rb
      invoke    test_unit
      create      test/models/blorgh/article_test.rb
      create      test/fixtures/blorgh/articles.yml
      invoke  resource_route
       route    resources :articles
      invoke  scaffold_controller
      create    app/controllers/blorgh/articles_controller.rb
      invoke    erb
      create      app/views/blorgh/articles
      create      app/views/blorgh/articles/index.html.erb
      create      app/views/blorgh/articles/edit.html.erb
      create      app/views/blorgh/articles/show.html.erb
      create      app/views/blorgh/articles/new.html.erb
      create      app/views/blorgh/articles/_form.html.erb
      invoke    test_unit
      create      test/controllers/blorgh/articles_controller_test.rb
      invoke    helper
      create      app/helpers/blorgh/articles_helper.rb
      invoke      test_unit
      invoke  assets
      invoke    js
      create      app/assets/javascripts/blorgh/articles.js
      invoke    css
      create      app/assets/stylesheets/blorgh/articles.css
      invoke  css
      create    app/assets/stylesheets/scaffold.css
}}
-config/routes.rbが以下のように変更となる。
#pre{{
Blorgh::Engine.routes.draw do
  resources :articles
end
}}
-コントローラー、ビュー、ヘルパーなども名前空間に分離されている。
-scaffoldで作成されたリソースファイルは、エンジンに適用されない。
-マイグレーション実行(Webサイトのようにbin/rails ではなくrakeでは?)
 bundle exec rake db:migrate
-test/dummyディレクトリでrailsアプリが起動できるようになる。
 bundle exec rails s
-次のURLにアクセス。http://localhost:3000/blorgh/articles
 -エンジンルート/config/routes.rbに以下を追加
 root to: "articles#index"
-すると http://localhost:3000/blorgh/ でアクセスできる。

**commentsリソースを作成する [#h34a456a]

***モデルの作成 [#dc6d7ff1]
-エンジンのルートディレクトリでモデルのジェネレータを実行する。
#pre{{
$ bundle exec bin/rails generate model Comment article_id:integer text:text
      invoke  active_record
      create    db/migrate/20170313044114_create_blorgh_comments.rb
      create    app/models/blorgh/comment.rb
      invoke    test_unit
      create      test/models/blorgh/comment_test.rb
      create      test/fixtures/blorgh/comments.yml
}}
-マイグレーション実行。
 bundle exec rake db:migrate
-ビューの変更。app/views/blorgh/articles/show.html.erbを編集。Editリンクの前に以下を追加。
#pre{{
<h3>Comments</h3>
<%= render @article.comments %>
}}
-app/models/blorgh/article.rbに追加。
#pre{{
has_many :comments
}}
-記事を作成するためのフォームを作成。app/views/blorgh/articles/show.html.erbのrender @article.comments呼び出しの直後に以下の行を追加。
#pre{{
<%= render "blorgh/comments/form" %>
}}
-app/views/blorgh/commentsディレクトリに_form.html.erbを作成。
#pre{{
<h3>New comment</h3>
<%= form_for [@article, @article.comments.build] do |f| %>
  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>
  <%= f.submit %>
<% end %>
}}
-コメント用のルーティングを追加。config/routes.rbを編集。
#pre{{
resources :articles do
  resources :comments
end
}}

***コントローラーの作成 [#l31af821]
-scaffoldの実行。
#pre{{
$ bundle exec rails g controller comments
Expected string default value for '--helper'; got true (boolean)
Expected string default value for '--assets'; got true (boolean)
      create  app/controllers/blorgh/comments_controller.rb
      invoke  erb
       exist    app/views/blorgh/comments
      invoke  test_unit
      create    test/controllers/blorgh/comments_controller_test.rb
      invoke  helper
      create    app/helpers/blorgh/comments_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    js
      create      app/assets/javascripts/blorgh/comments.js
      invoke    css
      create      app/assets/stylesheets/blorgh/comments.css
}}
-app/controllers/blorgh/comments_controller.rbを編集。ネストリソースの場合メインオブジェクトのidがパラメータとして(article_idとして)渡されてくる。
#pre{{
require_dependency "blorgh/application_controller"

module Blorgh
  class CommentsController < ApplicationController
    def create
      @article = Article.find(params[:article_id])
      @comment = @article.comments.create(comment_params)
      flash[:notice] = "Comment has been created!"
      redirect_to articles_path
    end

    private
    def comment_params
      params.require(:comment).permit(:text)
    end
  end
end
}}
-app/views/blorgh/comments/_comment.html.erbを作成。 render @article.commentの呼び出しによって。_commet.html.erbが自動で呼び出される。comment_counterはコレクションテンプレートの場合暗黙的に作られる。
#pre{{
<%= comment_counter + 1 %>. <%= comment.text %>
}}
-結局これによってコメントの番号とコメントのテキストが一覧表示されることになる。



*エンジンの作り込み[#l98d448e]

**mountable型の作成 [#h0335811]
***プロジェクトの作成 [#k3f40a79]
-以下のコマンドを実行
 bundle exec rails plugin new ~/work/blorgh --mountable --skip-bundle
-blorgh.gemspecを編集してTODOを削除したあと、bundle installの実行
  bundle install --path=vendor/bundle

***Articleリソースの作成 [#d01f6ff6]
-以下のコマンドを実行
 bundle exec bin/rails generate scaffold article title:string text:text
-モデルやコントローラーにネームスペースが付与されていることに注意。
#pre{{
$ bundle exec bin/rails generate scaffold article title:string text:text
      invoke  active_record
      create    db/migrate/20161025072006_create_blorgh_articles.rb
      create    app/models/blorgh/article.rb
      invoke    test_unit
      create      test/models/blorgh/article_test.rb
      create      test/fixtures/blorgh/articles.yml
      invoke  resource_route
       route    resources :articles
      invoke  scaffold_controller
      create    app/controllers/blorgh/articles_controller.rb
      invoke    erb
      create      app/views/blorgh/articles
      create      app/views/blorgh/articles/index.html.erb
      create      app/views/blorgh/articles/edit.html.erb
      create      app/views/blorgh/articles/show.html.erb
      create      app/views/blorgh/articles/new.html.erb
      create      app/views/blorgh/articles/_form.html.erb
      invoke    test_unit
      create      test/controllers/blorgh/articles_controller_test.rb
      invoke    helper
      create      app/helpers/blorgh/articles_helper.rb
      invoke      test_unit
      invoke  assets
      invoke    js
      create      app/assets/javascripts/blorgh/articles.js
      invoke    css
      create      app/assets/stylesheets/blorgh/articles.css
      invoke  css
      create    app/assets/stylesheets/scaffold.css
}}
-miration実行
 bundle exec rake db:migrate
-test/dummyディレクトリで実行。http://localhost:3000/blorgh/articlesでアクセスできる。
 bundle exec rails s

***Commentリソースの生成 [#t6b759ed]
-記事1に対して複数のコメントがつけられる。
-以下のコマンドを実行。
#pre{{
$ bundle exec bin/rails generate model Comment article_id:integer text:text
      invoke  active_record
      create    db/migrate/20161025073042_create_blorgh_comments.rb
      create    app/models/blorgh/comment.rb
      invoke    test_unit
      create      test/models/blorgh/comment_test.rb
      create      test/fixtures/blorgh/comments.yml
}}
-migration実行
 bundle exec rake db:migrate
-以下の概略。app/views/blorgh/articles/show.html.erbにコメント一覧とコメント追加用のフォームを追加する。

*エンジンを使用する [#h6c29c33]
**エンジンを使用するプロジェクトの作成 [#z12ddf05]
-以下のコマンドで作成
 bundle exec rails new ~/work/unicorn --skip-bundle
-Gemfileを編集
#pre{{
gem 'devise'
gem 'blorgh', path: "/Users/sora/work/blorgh"
}}
-bundle installを実行
 bundle install --path=vendor/bundle
-config/routes.rbを編集。
 mount Blorgh::Engine, at: "/blog"


**エンジンの設定 [#v675394d]
-マイグレーションファイルのインストール
#pre{{
 $ bundle exec rake railties:install:migrations
Copied migration 20170313063027_create_blorgh_articles.blorgh.rb from blorgh
Copied migration 20170313063028_create_blorgh_comments.blorgh.rb from blorgh
}}
-マイグレーション実行。
  bundle exec rake db:migrate
-実行。http://localhost:3000/blog にアクセスする。
 bundle exec rails s

**アプリケーションが提供するクラスを使用する [#sdfec224]
-アプリケーション側のクラスをエンジンから使いたい場合がある。例えばUser。ただしクラス名は固定したくない。
-とりあえずアプリケーション側でモデルクラスを作成。
#pre{{
 bundle exec rails g model user name:string
 bundle exec rake db:migrate
}}
-エンジン側ではauther_nameテキストフィールドを利用して、ユーザー名として保存する。エンジンのapp/views/blorgh/articles/_form.html.erbのタイトルフィールドの下に以下を追加。
#pre{{
<div class="field">
  <%= f.label :author_name %><br>
  <%= f.text_field :author_name %>
</div>
}}
-※そのままだとauthor_nameの初期値が表示されないので以下のようにしないとだめかも?
#pre{{
        <%= f.text_field :author_name, :value => @article && @article.author ? @article.author.name: '' %>
}}

-エンジンのarticles_controller.rbを修正。
#pre{{
def article_params
  params.require(:article).permit(:title, :text, :author_name)
end
}}
-app/models/blorgh/article.rbにしかるべく修正を行う。
#pre{{
attr_accessor :author_name
belongs_to :author, class_name: "User"
 
before_validation :set_author
 
private
  def set_author
    self.author = User.find_or_create_by(name: author_name)
  end
}}
-関連付けに必要になるauther_idをblorgh_articlesテーブルに追加。エンジンのディレクトリで実行
#pre{{
$ bundle exec rails g migration add_author_id_to_blorgh_articles author_id:integer
# 以下は必要ない?
# bundle exec rails db:migrate
}}
-今度はアプリ側でマイグレーション実行。
#pre{{
$ bundle exec rake blorgh:install:migrations
$ bundle exec rake db:migrate
}}
-作者名を記事のページに表示する。エンジンのapp/views/blorgh/articles/show.html.erbのTitleの上に追加。
#pre{{
<p>
  <b>Author:</b>
  <%= @article.author %>
</p>
}}
-アプリケーション側でUserクラスにto_sを追加。
#pre{{
def to_s
  name
end
}}

*エンジンを設定する [#tdc9ee93]
-エンジンのlib/blorgh.rbに以下のメソッドを追加(mattr_accessorはrails独自の拡張でモジュールにセッターとゲッターを追加する).
 mattr_accessor :author_class
-app/models/blorgh/article.rbのbelongs_toを変更
#pre{{
 belongs_to :author, class_name: Blorgh.author_class
 
 self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)
}}
-constantizeが呼び出されるようにしてもよい。
#pre{{
def self.author_class
  @@author_class.constantize
end
self.author = Blorgh.author_class.find_or_create_by(name: author_name)
}}
この場合belongs_toは次のようになる
#pre{{
belongs_to :author, class_name: Blorgh.author_class.to_s
}}
-アプリケーション側ではconfig/initializers/blorgh.rbにイニシャライザを作成。クラス自身を渡すとテーブル参照が発生するのでクラスは文字列で渡す。
 Blorgh.author_class = "User"

*エンジンをテストする [#q5ed1b9d]
-test/dummyディレクトリの下にダミーアプリケーションが作成される。
-このアプリを使ってテストする。
-その他testディレクトリで通常のRailsのテスト同様単体テスト、機能テスト、結合テストを実施することができる。

**機能テスト [#l8721296]
-機能テストはエンジンではなく、test/dummyアプリで実行される(?)。これはコントローラーをテストするにはエンジンをホストするアプリケーションが必要なため。
-例えば以下のようにすることで、エンジンテストが可能?
#pre{{
get :index, use_route: :blorgh
setup do
  @routes = Engine.routes
end
}}

*エンジンの機能を改良する [#q4b84168]
-エンジンのモデルクラス、コントローラークラスは通常のオープンクラスとしてアプリ側から自由に拡張できる。
-エンジン側を以下のようにする。
#pre{{
# lib/blorgh/engine.rb
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
 
    config.to_prepare do
      Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
        require_dependency(c)
      end
    end
  end
end
}}
-decoratorではinstance_evalを使う。そのほかActiveSupport::Concernを使う手もあり。
-ビューは、最初にapp/views/blorgh/articles/index.html.erbが探されてそのあとエンジン側が検索される。そのためアプリ側で自由にカスタマイズできる。
-ルーティングはレンダリング対象によって変わる場合があるので、以下のように明示することもできる。
#pre{{
<%= link_to "Blog articles", blorgh.articles_path %>
<%= link_to "Home", main_app.root_path %>
}}
-アセットも名前空間で分離されている。





トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS