ActiveHashのデータをFactoryBotで使う方法

f:id:endoakak:20200727195635j:plain

アクティブハッシュのデータをFactoryBotで使うのにてこずったので、記録に残しておこうかと思います。

事の発端

itemモデルの単体テストコードで使うため、以下のようなFactoryBotを記述しました。
categoryはアクティブハッシュで、itemテーブルと紐づいています。

FactoryBot.define do
  factory :item do
    #(略)
    category_id { 2 }
  end
end

ところがメンターさんに、category_idに直接数値を代入するのではなく、アソシエーションを記述するようにと言われました。

FactoryBotでアソシエーションを記述する方法…。テーブル同士ならこれでOKです。

factory :item do
  association :user
end

categoryについても同じように書いてみました。

factory :item do
  association :category
end

しかしこれではcategory_idは生成されず。どうしたものか。

アクティブハッシュとテーブルのアソシエーション

CategoryモデルとItemモデルはこのようになっていました。

class Category < ActiveHash::Base
 self.data = [
   { id: 1, name: '--' },
   { id: 2, name: 'メンズ' }
  # 略
 ]
 end
class Item < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :category

  #カテゴリーの選択が「--」の時は保存できないようにする
  validates :category_id, numericality: { other_than: 1 } 
end

Itemモデルの中ではbelongs_to_active_hashメソッドでアソシエーションを設定しているんですけど、Categoryモデルにアソシエーションの記述はないんですよね。おそらくそのへんが関係しているのではないかと(細かいことはわからないですが…)。

Categoryモデルをこんな感じに修正しました。

class Category < ActiveHash::Base
  include ActiveHash::Associations
  self.data = [
    { id: 1, name: '--' },
    { id: 2, name: 'メンズ' }
   # 略
  ]
  has_many :items
end

そしてFactoryBotはこのように記述します。

factory :item do
  category { Category.all.sample }
end

これでできました!なるほど、categoryの中からサンプルを一つ取ってくる感じですかね。

ちなみに以下のように書いてはいけないらしいです。

factory :item do
  association :category
end

こうしてしまうと、categoryの新しいレコード(idは最新でnameは空)が生成されてしまいます。

新たな問題発生

解決したかと思いきや。この方法でダミーデータを作ると、category_idが1になることがあるんですよね。category_id: 1では保存できないので、テストに失敗します。さて困った。

解決策

category_id: 1でも保存できるように、categoryの一つ目に入れていた「 -- 」を消しました。

self.data = [
    { id: 1, name: 'メンズ' },
    { id: 2, name: 'レディース' }
   # 略
  ]

そして、Itemモデルの「category_idが1以外」というバリデーションを削除して、FactoryBotの問題は解決です。
その代わり、実際にwebページで入力する際に初めから一つ目の要素が選択された状態になるという問題が生じます。

そこでビューファイルでカテゴリーを選択して入力する部分に、include_blank: trueを追加しました。

<%= f.collection_select(:category_id, Category.all, :id, :name, {include_blank: true}, {class: "select-box"}) %>

これで、ユーザーが選択するまではcategoryは空欄になります。今度こそ解決。

参考

こちらを参考にさせていただきました。

ActiveHash で定義したデータを FactoryBot の association で用いる - Qiita