Fabricationを使ってみた
※2011/11/08 コメント欄で指摘を頂いた箇所を加筆修正しました。また、割と古い記事ですので最新の情報は Fabrication を参照することをお奨めします。
これを作ってるとき、machinistとmachinist_mongoを使っていたんですが、試しに前々から気になっていたFabricationで書き換えてみました。README書いてあることをいくつか実際にやってみたのでメモしておきます。割と使いやすかったです。
何に使えるの
下記をサポートしてるそうですので、Mongoid使えます。やった!
- Plain old Ruby objects
- ActiveRecord objects
- Mongoid Documents
使ったもの
- Fabrication…本日のメイン
- Faker…嘘データをどんどこ作ってくれるやつ
QuickStart & 使い方
Gemfile
Rails/Mongoid/RSpec/Fabricationで↓のような組み合わせで使ってます。
group :development, :test do gem 'rspec-rails', '>= 2.0.0.beta.22' gem 'mongoid-rspec' gem 'faker' gem 'fabrication' # <= これ gem 'wirble' gem 'spork' end
記述する場所
spec/fabricators.rbに書くか、spec/fabricators/*.rbに複数ファイルにして書いてもいいみたいです。ここに置いておけば、必要になったら勝手に呼ばれます。最初から自分で読み込んでおきたいときはspec/spec_helper.rbなどに書きます。
Fabricatorを書いて使ってみる
spec/fabricators.rbなどにfabricatorを書いていきます。blueprintみたいな感じで。
# Userモデルはこんな感じだとします class User include Mongoid::Document field :screen_name, :type=>String field :description, :type=>String field :site, :type=>String field :gender, :type=>String end # Userモデルのfabricatorを作ります Fabricator(:user) do screen_name { Faker::Internet.user_name } description { Faker::Lorem.paragraph[0..190] } site { ['http://', Faker::Internet.domain_name].join } gender { ['male', 'female'].sort_by{ rand }.first } end
作ったFabricatorをspecの中で好きなときに呼び出します。factorygirlやmachinistと同じように、3通りの呼び出し方があります。
# インスタンスを保存して返す Fabricate(:user) # => #<User _id: 4cb18e0c011c0e5a2c000001, screen_name: "aidan", description: "Vel iste illo beatae maxime earum accusantium. Facilis doloremque nemo dolores perspiciatis. Eum et nisi sunt qui soluta facilis consequatur quasi. Qui sunt omnis beatae sit. Quisquam earum d", site: "http://wintheiserwunsch.com", gender: "female"> # インスタンスを保存せずに返す Fabricate.build(:user) # => #<User _id: 4cb18e32011c0e5a2c000002, screen_name: "darlene", description: "Exercitationem voluptatem voluptatibus officia saepe labore earum. Voluptas ut ipsam soluta. Libero possimus eius illo soluta quo blanditiis quod. Similique ea qui reprehenderit nihil ut cons", site: "http://schiller.uk", gender: "female"> # 属性だけを生成してHashで返す Fabricate.attributes_for(:user) # => {"screen_name"=>"jaylon", "description"=>"Et vitae nobis enim non. Quia sunt ipsum laboriosam. Maxime ut neque voluptatum velit est aliquam sequi qui. Eius dolorem id ut assumenda.", "site"=>"http://wuckert.uk", "gender"=>"male"}
Fabricatorの継承とか
すでに作ったのとはちょっと違うのがほしいときは、すでにあるfabricatorを継承することができます。:fromパラメータに継承元の名前を入れて、変更したいフィールドの定義を記述します。
# 必ずfemaleなひとがほしい Fabricator(:female, :from=>:user) do gender 'female' end
もちろん、継承は使わず呼び出すときに直接指定することもできます。
# いまだけfemaleなひとがほしい Fabricate(:user, :gender=>'female') # => {"screen_name"=>"daron", "description"=>"Aut dolor reprehenderit consequuntur nihil magni odit eveniet. A est magni nam vero praesentium. Praesentium quae perferendis mollitia. Voluptatem harum ex aperiam tempora ab nihil voluptas. ", "site"=>"http://huel.com", "gender"=>"female"}
クラスの明示的な指定
:class_nameパラメータで明示的にクラス名を指定することもできます。色々なパターンでFabricatorをつくりたいときなどはこれで。クラス名はシンボルやklass、文字列で指定できます。
# :class_name は :user, User, 'User' などで指定可 Fabricator(:invalid_user, :class_name=>:user) do screen_name 'hibariya' description 'i have no gender' end
Sequenceで一意にする
MachinistのShamのようなものも用意されています。*1Fabricate.sequenceを使うと、呼び出すたびにインクリメントする数値が得られます。ブロックも渡せるので数値以外の一意な何かもつくることができます。Fakerには大量の単語とかがコード中にドカっと入ってるんですが、普通に重複しますので、データが一意でないといけないときなどはこれを使います。
大きく分けると3つの書き方になるようです。
Fabricate.sequence(:foo_id) # 呼び出すたびに0から始まる連番を返す Fabricate.sequence(:bar_id, 1000) # 呼び出すたびに1000から始まる連番を返す Fabricate.sequence(:future){|n| n.days.since } # 呼び出すたびに1日ずつ未来の日付を返す
さっきのUserモデルの例だとこんな感じになります。
# uriもscreen_nameも重複させたくない! Fabricator(:user) do screen_name { Fabricate.sequence(:screen_name){|i| [Faker::Internet.user_name, i.to_s].join } } # <= ここ description { Faker::Lorem.paragraph[0..190] } site { Fabricate.sequence(:uri){|i| ['http://', i.to_s, Faker::Internet.domain_name].join } } # <= ここ gender { %w(male female).sort_by{ rand }.first } end
on_init, init_with でnewに引数を渡す
newするのに引数が必要なクラスのfabricatorを作るときなどに使えます
class Site def initialize(uri) @uri = uri end end # on_initとinit_withで、initializeに渡す引数を指定できる Fabricator(:site) do on_init{ init_with(['http://', Faker::Internet.domain_name].join) } end
after_build, after_create コールバック
保存する前、保存したあとのコールバック。さっきまで勘違いしていたんですが、Fabricate.buildでもafter_createは呼ばれるようです(でもsaveはされない)これはバグだったそうです(https://github.com/paulelliott/fabrication/pull/36
)。
# User#fetch_recent_entries!がsave前のステップで呼び出される Fabricator(:foo_user, :from=>:user) do after_build {|user| user.fetch_recent_entries! } end # User#fetch_recent_entries!がsave後のステップで呼び出される Fabricator(:bar_user, :from=>:user) do after_create {|user| user.fetch_recent_entries! } end
belongs_to, references_oneな関連を!で表現
belongs_to, references_oneな関係にあるものは!を付けると自動的に作ってくれるらしい。べんり!というのは間違いで、!を付けるか否かで関連が作られるタイミングが変えられるようです(!を付けなければ参照されるまで作られない)。
Fabricator(:umbrella) do color { %w(red green blue pink violet purple).sort_by{ rand }.last } end Fabricator(:girl) do name 'unknown' umbrella! # !を付けると即座に関連が生成される end
Machinist <=> Fabrication 比較表
Machinistとの違いを、よく使う項目だけ表にしてみるとこんな感じでしょうか。*2
Machinist | Fabrication |
---|---|
Model.blueprint do ~ end | Fabricator(:model) do ~ end |
Model.blueprint(:named_blueprint) do ~ end | Fabricator(:named_fabricator, :from=>:model) do ~ end |
Model.make | Fabricate(:model) |
Model.make_unsaved | Fabricate.build(:model) |
Model.plan | Fabricate.attribues_for(:model) |