複数Railsアプリケーションから同一DB（テーブル）を参照する場合にRails Engineを活用してmodelのソースを共有する

色々あって、表題を実装しました。
もしこのようなことをやろうと思ってる方の１つの手段として参考になれば幸いです。

#前提条件
##RubyやらRailsのバージョン関係
Ruby:2.2.3
Rails:4.2.6

##システム構成案
アプリケーションとDBの関係は以下(あくまで例です)
![スクリーンショット 2016-06-15 9.36.31.png](https://qiita-image-store.s3.amazonaws.com/0/70363/93e4a99e-14d0-41ba-e17b-6a4d154e35cb.png)


#やりたいこと
Railsの**model部分**を共有したい。これにつきます。
方法は色々あると思いますが、自身がやったのは
「共通となるModelをRails EngineにてPlugin化し、各アプリケーションでそれを共有／使用する」
です。他にもgitでごにょごにょするとか色々あると思います。

#共通modelのplugin作成
共通となるmodelはRails Engineでpluginとして作成します。
※ここは以降の本題で使用するための前準備なので読み飛ばしても結構です。

```
rails plugin new rails_engine_sample --skip-bundle --mountable -T --dummy-path=spec/dummy
```

これでまずはpluginの雛形を作ってしまいます。ちなみに上記はRspecを使用するために書いたオプションがあるので、Rspec使わない人は `--mountable`以降はいりません。
今回の例だと取引先のmodelを共通化するのでそのmodelを作ります。

```
rails g model customer
```

本来であればこの取引先のmodelの属性をmigrateファイルに追加しますが、ここで追加すると問題があるので今は作成しません。
まずは取引先のmodelを軽く実装します。

```rb:rails_engine_sample/app/models/rails_engine_sample/customer.rb
module RailsEngineSample
  class Customer < ActiveRecord::Base
    def hoge
      'hoge'
    end
    ...(実装)
  end
end
```

後は上位側のアプリケーションのGemfileに以下を追加して`bundle install`すれば追加でき、上記のmodelが上位側のアプリケーションで使用できるということです。

```
gem 'rails_engine_sample', path: '../rails_engine_sample' #or 'https://github.com/chimame/rails_engine_sample.git'
```

#実装上の注意点
本記事の本題はここから
##migrate（DB変更）はどうする？
先ほど、generatorを実行するとmodelsやspec（or test）の下にcustomerモデルに関連するファイルができますが、厄介なのがmigrateです。その理由を以下に記載します。

**今回の構成上でplugin内でmigrateを作成した場合のmigrate実行アプリケーションはどこでしょうか？**

今回はこのpluginが複数のアプリケーションで使用されることを想定しています。pluginが管理しているモデルのmigrateはpluginで書きたくなりますが、実際にそのmigrateが実行されるのは上位のアプリケーションです。上位のアプリケーションでpluginのmigrateを実行するには

```
rake rails_engine_sample:install:migrations
```

を叩き、plugin内のmigrateをアプリケーションにコピーしてきてから`rake db:migrate`を実行するわけですが、
複数アプリケーションで実行すればファイル名が異なり、同一内容のmigrateにも関わらず、各アプリケーションで実行されてしまうことになります。
それはできないので自身がとった内容は
「Railsのmigrate機能は使わず、migrate管理専用のプロジェクトを作成する」
[cookpad開発ブログ](http://techlife.cookpad.com/entry/2014/08/28/194147)でも紹介されている『[Ridgepole](https://github.com/winebarrel/ridgepole)』を使用することにしました。
こうすることによって、migrate管理を一元化することで上記の問題をクリアです。
なのでplugin側で作成されるmigrate関係のファイルは捨てましょう。

##pathとurlヘルパーにpluginのモデルを使用するとmodule(plugin)名が含まれてしまう
pluginで作成するにあたり、モデルの定義は以下のようになるでしょう。

```rb:rails_engine_sample/app/models/rails_engine_sample/customer.rb
module RailsEngineSample
  class Customer < ActiveRecord::Base
    def hoge
      'hoge'
    end
    ...(実装)
  end
end
```

なので上位のアプリケーションで使用するとすれば

```rb:sale_app/config/routes.rb
Rails.application.routes.draw do
  resources :customers
end
```

```rb:sale_app/app/controllers/customers_controller.rb
class CustomersController < ActionController::Base
  def edit
    @customer = RailsEngineSample::Customer.find(params[:id])
  end
end
```

編集画面を開ける場合は、上記のようなコードで編集画面に`@customer`を渡すわけですが、その`@customer`はView側でform_forヘルパー等に渡されることがあります。

```erb
<%= form_for @customer do |f| %>
  ...(入力コントロール等諸々)
<% end %>
```

するとRailsは

```
undefined method `rails_engine_sample_customer_path' for ...
```

のエラーを吐くと思います。これはヘルパー内でmodule名を含めたpathヘルパーを呼んでるためです。module名をroutesに含めれば問題ありませんが、そうしたくない場合は、plugin内で以下を記述します。（自分の場合は既存アプリケーションから一部をplugin化したという背景もあります）

```rb:rails_engine_sample/lib/rails_engine_sample.rb
module RailsEngineSample
  def self.use_relative_model_naming?
    true
  end
end
```

これでmodule名を含めたpathヘルパーではなくなるため、処理できるようになると思います。もしcontrollerやviewもpluginに含める場合はこのような問題は発生しないでしょう。

##上位アプリケーションにてpluginのmodelにアソシエーションの追加等の拡張を行いたい
pluginには各アプリケーションで共通となるmodelを持っていますが、上位側でもmodelを保持するでしょうし、pluginのmodelともアソシエーションも定義したいでしょう。上位アプリケーションのmodelからpluginのmodelへのアソシエーションは以下のように一手間かかりますが書けます。

```rb:sale_app/app/models/sale.rb
class Sale
  belongs_to :customer, class_name: :'RailsEngineSample::Customer'
  ...
end
```

とclass_nameを指定してあげれば問題なく参照が可能です。では逆の場合はどうしたかというとさらに一手間かけます。まずはpluginで拡張できるようにmodelの定義を以下のように変更します。

```rb:rails_engine_sample/app/models/rails_engine_sample/concerns/customer_active_recordable.rb
module RailsEngineSample
  module Concerns
    module CustomerActiveRecordable
      extend ActiveSupport::Concern

      included do |klass|
        has_many :payment_terms

        scope :by_fuga, ->(fuga){where(fuga: fuga)}

        def hoge
          'hoge'
        end
        ..(実装)
      end
    end
  end
end
```

```rb:rails_engine_sample/app/models/rails_engine_sample/customer.rb
module RailsEngineSample
  class Customer < ActiveRecord::Base
    include RailsEngineSample::Concerns::CustomerActiveRecordable
  end
end
```

こんな風に実態をActiveSupport::Concernで外に出します。で上位側でこのmodelを拡張したい場合は

```rb:sale_app/app/models/rails_engine_sample/customer.rb
module RailsEngineSample
  class Customer < ActiveRecord::Base
    include RailsEngineSample::Concerns::CustomerActiveRecordable

    has_many :sales

    #Overrideする場合は普通に再定義
    def hoge
      'hoge2'
    end
  end
end
```

これでplugin内で定義されたmodelの拡張も思いのままです。普通にclassを再オープンするとplugin内の定義が失う（正確には見えなくなる）ので、上記の方法だと失うことなく拡張が可能です。

上記３点（特に1番目と3番目）が自分中でなかなか困ったとこでしたが、すっきりした感じにはなったと思っております。

#番外編：plugin管理のテーブル名にprefixを付けたくない
これは正直番外編です。
少し触れましたが、自身の場合は既存のアプリケーションから一部をpluing化した背景があり、テーブルは既に存在し、本番運用されています。
plugin管理のテーブルは`<plugin名>_<モデル名（複数形）>`になるわけですが、先頭のprefixを外したかったので、

```rb:rails_engine_sample/lib/rails_engine_sample.rb
module RailsEngineSample
  def self.use_relative_model_naming?
    true
  end

  def self.table_name_prefix
  end
end
```

`table_name_prefix`メソッドにて、付与するprefixを無しにすれば問題ありません。

安定の誤字脱字は後に修正するとして、もっといい方法や指摘等あればコメントや編集リクエスト下さい。

参考程度に作ったリポジトリを貼っておきます。
https://github.com/chimame/rails_engine_sample
https://github.com/chimame/rails_engine_use_app_sample