Rails Engineメモ

# 背景
Rails Engineを触ってみたのでメモ。

Engineとは何かなどは http://guides.rubyonrails.org/engines.html

# サンプル
適当なサンプルということで、発生した例外をDBに保存するような仕組みを考える。

Engineの役目は、エラーの詳細を見るための画面や、例外保存処理をホストアプリに提供することである。

# 生成
```
rails plugin new error_catcher -T --dummy-path=spec/dummy --mountable
```

`-T`で`test`関連の生成をスキップ。使い慣れているのがrspecなもので。。。

`--dummy-path=spec/dummy`は、engineの動作確認用のdummyのrailsアプリが生成されるパス。rspecを使うので、dummyアプリの置き場所も`spec`以下にする。

`--mountable`で、独立した名前空間を持ったskeltonが生成される。（正直ここはふわっとした理解）

# rspecの準備
以下を追記。

```rb:error_catcher.gemspec
s.test_files = Dir["spec/**/*"]
s.add_development_dependency "rspec-rails"
```

```rb:lib/error_cathcer/engine.rb
config.generators do |g|
  g.test_framework :rspec
end
```

helperファイルを生成

```
bundle install
bin/rails g rspec:install
```

spec_helper.rbとかrails_helper.rbが生成される。

enviromentの読み込み先をdummyアプリに変える

```rb:spec/rails_helper.rb
require File.expand_path('../dummy/config/environment', __FILE__)
```

# モデルを作成
```
bin/rails g scaffold error name:string description:string stacktrace:text fired_at:datetime
bundle exec rake db:migrate
```

# ダミーアプリを起動
```
cd spec/dummy
bin/rails s

http://localhost:3000/error_catcher/errors
```

よくあるIndex画面が出るはず。

# specを書いてみる
scaffoldで生成していれば、spec/models/error_cathcer/error_spec.rbが存在するはず。これを以下のように修正してみる。

```rb:spec/models/error_catcher/error_spec.rb
require 'rails_helper'

describe ErrorCatcher::Error do
  describe 'Validation' do
    it 'nameは必須' do
      error = described_class.new
      expect(error.valid?).to be_falsey
    end
  end
end
```

実装も直す。

```rb:app/models/error_catcher/error.rb
module ErrorCatcher
  class Error < ActiveRecord::Base
    validates :name, presence: true
  end
end
```

```
bundle exec rspec spec/models/error_cathcer/error_spec.rb
```
でテストが通るはず。

# ホストアプリ側から呼び出すメソッドを実装する
ホストアプリ側からは、直接Modelを呼ぶのではなく例外保存用のインタフェースを呼ぶ。モデルを直接ホストアプリから呼ぶこともできるが、アプリとgemの依存関係が強くなってしまうので、別途外向けのインタフェースを用意する。

```rb:lib/error_catcher/catcher.rb
module ErrorCatcher
  module Catcher
    def catch e
      error = ::ErrorCatcher::Error.new
      error.name = e.class.name
      error.description = e.message
      error.stacktrace = e.backtrace.join("\n")
      error.fired_at = Time.now
      error.save!
    end
  end
end
```

# アプリへ組み込み
error_catcherを利用する適当なアプリを作る。今回はerror_catcherと同じ階層に作る。

```
rails g new error_thrower
cd error_thrower
bin/rails g scafold item name:string description:string price:integer
```

```:Gemfile
gem 'error_catcher', path: '../error_catcher'
```

```
bundle install
```

`error_catcher`が提供するmigrationファイルを取得&migration。

```
bundle exec rake error_catcher:install:migrations
bundle exec rake db:migrate
```

コントローラで予期せぬ例外が起きたら全てキャッチして保存する。

```rb:error_thrower/app/controller/application_controller.rb
include ErrorCatcher::Catcher
rescue_from Exception, with: :error_handler

def error_handler e
  catch(e)
  render file: "#{Rails.root}/public/500.html", layout: false, status: 500
end
```

エラーが起きた時の動作確認のために、Validationをかける。

```rb:error_thrower/app/models/item.rb
validates :name, presence: true
```

`error_thrower/app/controller/items_controller`内の`@item.save`を全て`@item.save!`に変えて、画面から空入力で保存するとValidationErrorが起きるようにする。

```
bin/rails s
```

http://localhost:3000/items/new から空で保存すると、エラー画面が表示され、http://localhost:3000/error_catcher/errors を見れば発生したエラーが保存されている。

これで、例外保存、閲覧の仕組みをRailsアプリに組み込むEngineができたことになる

作ったものはこちら https://github.com/hokuma/error_catcher