RedmineのConsoleからプラグイン用のFactoryBotを呼び出す

## はじめに

### 20200401: 修正

- FactoryGirl -> FactoryBotに修正しています

### Rspecでテストしたい

コツコツとRedmineのプラグインを作っていますが、実はRailsをちゃんと勉強していなかったので、あとから「うわー、こんな書き方しちゃった恥ずかしい...」と思う日々です。

さて、自作プラグインにはなるべくテストを付けていこう、それもRspecでやってみようと思うようになりました。

### FactoryBotも使いたい

ですが、残念ながらRedmine本体はRspecもFactoryBotも利用していません。
そこで、自作プラグインにspec/ディレクトリを作り、おまじないをしながらRspecでテストを書くようにしています。FactoryBot用の定義も、やはり自作プラグインのspec/factories 以下に置いています。

さて、このFactoryBotですが、慣れると欲が出て来ます。
そもそもちゃんと利用できるのか確認したい、なるべく楽をして関連付いたモデルのデータも一緒に作れるように定義したい...と思いますよね。

ただ、配置場所が自作プラグインの下の階層なので、普通にrails consoleしただけでは読み込んでもらえません....。


## 本題: ConsoleからFactoryBotを呼んでみる

ということで、タイトルの通り本題に入ります。
例として利用しているプラグインは、[こちら](https://github.com/akiko-pusu/redmine_issue_badge)です。

### 何もしない場合

まず、普通に何も指定せずに rails consoleを起動します。
そこから、プラグインの下の spec/factories/user.rb を呼び出してみると...。

```
$ bundle exec rails c --sandbox

Loading test environment in sandbox (Rails 4.2.7.1)
Any modifications you make will be rolled back on exit
irb(main):001:0> FactoryBot.build(:user)
ArgumentError: Factory not registered: user
	from /xxxxxxx/vendor/bundle/ruby/2.3.0/gems/factory_girl-4.8.0/lib/factory_girl/registry.rb:24:in `find’
```

案の定、エラーになります><

### definition_file_paths を利用する

なんのことはないのですが、プラグイン用のディレクトリのspec_helperでFactoryBotの読み込みパスを追加設定しているので、そちらをconsoleでも呼んであげるようにしてみます。


```
$ bundle exec rails c --sandbox

Loading test environment in sandbox (Rails 4.2.7.1)
Any modifications you make will be rolled back on exit

irb(main):002:0> FactoryBot.definition_file_paths = %w(plugins/redmine_issue_badge/spec/factories)
=> ["plugins/redmine_issue_badge/spec/factories"]
irb(main):003:0> FactoryBot.find_definitions
=> [“/xxxxxx/redmine-3.3/plugins/redmine_issue_badge/spec/factories”]
```

definition_file_pathsでパスを指定しただけではファイルを見てくれないので、find_definitionsも合わせて実施します。

### パスを追加した結果

それでは、同じように FactoryBot.build(:user)を実行してみます。

```
irb(main):004:0> FactoryBot.build(:user)
=> #<User id: nil, login: "user1", 
hashed_password: “xxxxxxxxxxxxxxxxxxxx", 
firstname: "User1", lastname: "Test1", admin: false, 
status: 1, last_login_on: nil, 
language: "ja", auth_source_id: nil, 
created_on: nil, updated_on: nil, 
type: "User", identity_url: nil, mail_notification: "", 
salt: "7599f9963ec07b5a3b55b354407120c0", 
must_change_passwd: false, passwd_changed_on: nil>

```

こんな感じで上手くできました。

## FactoryBotを便利にする

うまく読み込めそうなので、その他のモデル用のFactoryBotのファイルも作ってみます。

プラグインのテストではチケットトラッキングが利用できるのを前提にしているので、このあたりをまとめて設定できると良いですよね。

個別に作ってあとから関連付けでもいいのですが、トラッカーを作る際に、デフォルトステータスとプロジェクトも一緒に作れるようにしてみます。

```trackers.rb
# トラッカーを作成
FactoryBot.define do
  factory :tracker do
    sequence(:name)     { |n| "tracker-name: #{n}" }
    sequence(:position) { |n| n }
    default_status_id 1

    # トラッカーの定義と一緒にデフォルトステータスも作成
    trait :with_default_status do
      after(:build) do |tracker|
        status = FactoryBot.create(:issue_status)
        tracker.default_status_id = status.id
      end
    end

    # trait :with_project do
    trait :with_default_status do
      after(:build) do |tracker|
        project = FactoryBot.create(:project)
        project.trackers << tracker
      end
    end
    
  end
end

```

```issue_statuses.rb
# トラッカー用のステータスを作成
FactoryBot.define do
  factory :issue_status do
    sequence(:name)     { |n| "status-name: #{n}" }
    sequence(:position) { |n| n }
    is_closed false
  end
end
```

```projects.rb
FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "project-name: #{n}" }
    sequence(:description) { |n| "project-description: #{n}" }
    sequence(:identifier) { |n| "project-#{n}" }
    homepage 'http://ecookbook.somenet.foo/'
    is_public true
  end
end
```

### 実行してみる

では、traitも使って実行してみます。

```
irb(main):036:0> FactoryBot.create(:tracker, :with_default_status, :with_project)
   (0.1ms)  SAVEPOINT active_record_1
  IssueStatus Exists (0.3ms)  SELECT  1 AS one FROM "issue_statuses" WHERE "issue_statuses"."name" = 'status-name: 7' LIMIT 1
  SQL (0.3ms)  INSERT INTO "issue_statuses" ("name", "position") VALUES (?, ?)  [["name", "status-name: 7"], ["position", 7]]
  SQL (0.2ms)  UPDATE "issue_statuses" SET position = position + 1 WHERE (position >= 7 AND id <> 14)


--- [ 中略 ] ---

   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> #<Tracker id: 13, name: "tracker-name: 7", 
is_in_chlog: false, position: 7, 
is_in_roadmap: true, fields_bits: 0, default_status_id: 14>

```

なんとかできたようです。

## まとめ

以上、ちょっとしたメモでした。
RedmineやRedmineのプラグインに限らず、諸事情でデフォルトの spec/ 以下以外にテストコードやfactoriesを配置したことがあったので、今回Qiitaのメモ的な記事としてアップさせていただきました。

Consoleを使うと、テストの前に作った関数が正しい値を返してくれるかどうかのチェックがしやすくなります。
こんな感じでFactoryBotも確認できるので、できるだけfixturesから卒業を進めていきたいと思います。