Skip to navigation

Fixtureless datas with Machinist and Sham

Published October 27, 2008 - Updated October 30, 2008

Not long ago Pete Yandell bestowed upon us an alternative to FactoryGirl and other fixture methods: Machinist.

Unfortunately I hadn’t had a chance to check it out before my Cucumbers and Factory Girls presentation last month at RORO Sydney so I couldn’t say much about it, but I’ve just started using for the 2009 revision of the LGSA Cultural Awards website.

What’s Machinist?

Equipped with a Lathe, Machinist allows you to craft beautifully valid and well-populated ActiveRecord models.

It’s extremely handy if you want an alternative to YML fixtures or you’re sick seeing the ugly save(false).

Machinist also includes Sham: a class allowing to you to create repeatable dummy data for your text fixtures. Sham helps to remove magic strings whilst ensuring your test data is deterministic.

Installing Machinist

It’s a Rails plugin, so simply:

./script/plugin install git://github.com/notahat/machinist.git

Then create your spec/blueprint.rb and require it from your spec/spec_helper.rb like so:

require File.join(File.dirname(__FILE__), 'blueprint')

Similarly for your features/steps/env.rb:

require File.join(RAILS_ROOT, 'spec', 'blueprint')

Defining your blueprints

Machinist extends ActiveRecord::Base with the class methods blueprint, for defining the blueprint:


Post.blueprint do
  title "Hello world" 
  text "<p>Hello there</p>"*3
  slug "some-slug" 
  published_on Date.today
end

and make, for creating objects from the blueprint:


post = Post.make
post.new_record? #=> false
post.title #=> "Hello world" 

Sham on you!

Machinist includes Sham: a class allowing to you to create repeatable dummy data for your text fixtures.

You define your sham’s like so (here we’re using the Faker gem):


Sham.title { Faker::Lorem.sentence }
Sham.body { Faker::Lorem.paragraphs }
Sham.slug { Faker::Lorem.words(1).first.downcase }

Note: shamelessness is also obtainable through the addition of a single letter: uninitialized constant Shame (NameError)

Using your shams in your blueprint is easy:


Post.blueprint do
  title { Sham.title }
  text { Sham.body }
  slug { Sham.slug }
  published_on Date.today
end

You don’t have to use Faker in your sham—to generate a random date you could simply do:


Sham.date do
  Date.civil((1990...2009).to_a.rand,
             (1..12).to_a.rand,
             (1..28).to_a.rand)
end

and use it in your blueprint just like the others:


Post.blueprint do
  title { Sham.title }
  text { Sham.body }
  slug { Sham.slug }
  published_on { Sham.date }
end

Using them with specs and features

To use your blueprints in your specs simply call make:


describe "Blog#to_param" do
  it "returns 'year-slug'" do
    blog = Blog.make
    blog.to_param.should == "#{blog.year}-#{blog.slug}" 
  end
end

It’s exactly the same for features/stories:


Given "there's a blog post" do
  Post.make
end

How does it compare to FactoryGirl?

Machinist’s syntax is definitely cleaner. The equivalent blueprint in FactoryGirl looks like:


Factory.define :post do |p|
  u.title "Hello world" 
  p.text "<p>Hello there</p>"*3
  p.slug "some-slug" 
  p.published_on Date.today
end

or more realistically:


Factory.define :post do |p|
  p.title "Hello world" 
  p.text "<p>Hello there</p>"*3
  p.slug { Factory.next(:slug) }
  p.published_on { Factory.next(:published_on_date) }
end

Factory.sequence(:slug) {|n| "slug-#{n}"}
# ...

To generate sequences in sham you simply accept a block parameter, for example:


Sham.slug {|n| "slug-#{n}" }

Machinist’s ways of defining associations are also much cleaner if everything matches up:


Comment.blueprint do
  post
end

and if you’ve an association with a custom class_name you can call make yourself:


Post.blueprint do
  author { User.make }
end

Contribute or follow development

The machinist github repo is the best place to start.

You could even move webjam’s spec/factories.rb to Machinist and send a pull request if you’re really keen ;)

If you’re a Victorian head to the October Melbourne RORO meetup where Pete will be demonstrating how to lathe Ruby objects with his new contraption.

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. Pete Yandell

    Thanks for the write-up! A few things to add that haven’t made it into the documentation yet:

    A blueprint should take care of creating all the data that your test doesn’t care about, leaving you to define the data that it does care about in the test setup. For example, you might have an editable flag on a comment and want to write some tests for it. In your test you want to be able to write:

    @comment = Comment.make(:editable => true)

    But a comment can’t exist without an author, and some body text, and a post. So you put those in the blueprint:

    Comment.blueprint do
      post { Post.make }
      author { Sham.author_name }
      body { Sham.comment_body }
    end

    You can even abbreviate this to:

    Comment.blueprint do
      post
      author { Sham.author_name }
      body { Sham.comment_body }
    end

    Machinist will automatically do the Post.make for you.

    You mentioned that Machinist doesn’t have sequence generation. Wrong! Sham lets you do this:

    Sham.author_name {|index| "Author number #{index}" }

    There will be some more extensive docs (and probably a screencast) on all of this sometime soon.

  2. Tim Lucas

    thanks Pete.

    That’s rockin, I didn’t know about the sequence generation.

    I’ve updated the Machinist vs FactoryGirl section with new details and examples.

  3. Dr Nic

    We’re using machinist + faker. They make me happy.

  4. Tim Riley

    Hi all, and thanks for the informative post, Tim!

    I’ve been using Faker’s Lorem Ipsum generator to generate seed data in my apps. I got a bit tired of looking at the unrealistic Latin through the app, so I ended up using markov chains & real english source data to generate more realistic looking seed data. This might be worth a look if you’re using Faker for more than just populating models in your tests.

    I wrote up the approach here: http://log.openmonkey.com/post/55783578/using-markov-chains-to-provide-english-language-seed

  5. Tim Riley

    Sorry, here is the link formatted to be click-friendly :)

    Using Markov Chains to provide English language seed data for your Rails application

  6. Sam Livingston-Gray

    Your equivalent FactoryGirl samples all call methods on p, except for the first line (“u.title [...]”). Typo?

  7. Tim Lucas

    @drnic happy is good. Good is happy.

    @timriley nice… interesting about Markov. For sample data that’d be mighty handy.

    @sam: yep, a typo. Fixed and thanks!

  8. John Wright

    Any idea how to use this from IRB? I think it would be convenient for testing out different scenarios in irb as well.

  9. Tim Lucas

    @johnwright: yeah you should be able to simply require 'spec/blueprint' and off you go.

  10. John Wrigt

    Yes, that works assuming your RAILS_ENV=test so that machinists and sham are required and the machinist module is included in ActiveRecord::Base as is done in vendor/plugins/machinists/init.rb.

Articles

toolmantim

I'm Tim Lucas, a user experience designer and developer currently in Sydney Australia.

I run a web application design and development company Agency Rainford, present on various technical topics, snap the occasional photo, tweet my going-ons, share teh codes and post other tid-bits to the tumble.

Most recently I published LAN hacking with Bananajour (June 11, 2009)

Shoot an email to and say hello.

Powered by your inner voice