giant robots smashing into other giant robots

Written by thoughtbot

jdclayton

Factory Girl 2.2, your new best friend!

Aside from a handful of internal code clean up, Factory Girl 2.2 brings a handful of awesomeness to the table. Factory Girl 2.1 was released a mere month and a half ago but loads of improvements have been added since then. Some key features:

Attributes from traits can be modified

When we introduced traits, there were a couple of caveats: you couldn’t override traits’ attributes from child factories, and you couldn’t overwrite traits’ attributes when using FactoryGirl.modify. Not the case anymore! We’ve been hammering away at Factory Girl’s internals, cleaning things up and delaying factory resolution as late as possible. This allows for:

FactoryGirl.define do
  factory :user do
    name "Stranger"

    trait :male do
      name   "Jon Snow"
      gender "Male"
    end

    factory :male_user do
      male
    end

    factory :brandon do
      male
      name "Brandon"
    end
  end
end

FactoryGirl.modify do
  factory :brandon do
    name "Brandon Stark"
  end
end

Parent factories can be defined after their children

This may sound a bit odd. Why would you ever want to define a parent factory after a child factory? When you’ve split your factories up across different files and don’t want to prefix file names in order for the files to be loaded in a specific order.

Factory Girl looks in six places by default: factories/, factories.rb, spec/factories/, spec/factories.rb, test/factories/, and test/factories.rb. If you’ve split up your factories within one of the directories, that’s wonderful; however, what if you want a “base” factory file and multiple factory files for the various children? Before 2.2, you’d have to name that base file in order to force it to be loaded before children, and that’s just gross.

Transient attributes have a cleaner syntax

Transient attributes are a relatively new feature allowing attributes to be passed via a hash when creating/building that don’t actually interact with the instance (or hash, when using attributes_for). The old syntax required calling #ignore on individual attribute declarations, which didn’t really look great. Now, you define all your ignored attributes in a block.

FactoryGirl.define do
  factory :user do
    ignore do
      rockstar true
      four     { 2 + 2 }
    end

    name { "John Doe#{" - Rockstar" if rockstar}" }
  end
end

The old ignore syntax has been deprecated and will be removed in 3.0.

Factories now accept blocks and yield the result

Have you ever done this?

let(:old_published_post) do
  create(:post, created_at: 2.years.ago).tap do |post|
    post.publish!
  end
end

Now, you can just pass a block to the syntax methods (create, build, build_stubbed, and attributes_for) and they’ll yield themselves; no more tap! It’s a simple enough change, but four characters fewer is four characters fewer.

What’s next for Factory Girl?

Aside from getting in solid pull requests, we’re going to be reworking the test suite so it’s much more flexible and continue reworking the inner workings of the gem. There’s currently a handful of code surrounding how to evaluate attributes and the order in which we should do so that we’d like to remove completely and have attributes resolve lazily.

We’re also considering a new syntax method, decorate, which would allow for:

FactoryGirl.define do
  factory :post do
    title "My great post!"
    body  "An awesome story"

    decorate :old do
      title "How is this even around still?"
      created_at { 2.years.ago }
    end

    decorate :archived do
      title "This shouldn't be on the homepage"
      archived true

      decorate :unpublished do
        published false
      end
    end
  end
end

This would give us four factories: post, old_post, archived_post, and archived_unpublished_post.

What features would you like to see in Factory Girl? Post them on the GitHub issues page and let us know in the comments!

jdclayton

Getting Sequential: A look into a Factory Girl Anti-pattern

I’ve noticed a handful of what I’d consider Factory Girl anti-patterns, either through pull requests, GitHub issues, or questions spread across Stack Overflow, Google Groups, etc. The more common they are, the more I realize that it will benefit plenty of other developers down the road if a list of anti-patterns can be identified and what to do instead.

The one I’d like to outline today is based off of a request to make Factory Girl sequences reset after every test run. The idea of resetting sequences after every run is that then you can start to program against these names in step definitions. For example, imagine the factory:

FactoryGirl.define do
  factory :category do
    sequence(:name) {|n| "Category #{n}" }
  end

  # ... more factories
end

And the scenario:

Scenario: Create a post under a category
  Given a category exists
  And I am signed in as an admin
  When I go to create a new post
  And I select "Category 1" from "Categories"
  And I press "Create"
  And I go to view all posts
  Then I should see a post with the category "Category 1"

That first step is given to us by Factory Girl (check out the getting started docs in Factory Girl to find out more) and will create a category without overriding any of the defaults provided by that factory.

If all sequences were reset after every run, it’d be safe to assume that the category created as the first step of the scenario will create a category named “Category 1”.

There are two problems with this approach. Firstly, we’re now tightly coupling the scenarios to naming conventions outlined in the factories. If the sequence logic changes, all the scenarios that rely on that naming convention will break. Not fun. Secondly, it’s not at all obvious where that “Category 1” record was created. If I’m a developer who’s looking at this code with a fresh set of eyes, I’d assume that this category was created from a global setup or seed file instead of being assigned in the first step of the scenario.

The solution is to name the category explicitly.

Scenario: Create a post under a category
  Given a category exists with a name of "Category 1"
  And I am signed in as an admin
  When I go to create a new post
  And I select "Category 1" from "Categories"
  And I press "Create"
  And I go to view all posts
  Then I should see a post with the category "Category 1"

The first step is, again, given by Factory Girl and allows us to set the name attribute. It’s now immediately obvious that the category we’re creating is the one we’re using throughout the scenario, we’re not reliant on a naming convention outlined by Factory Girl, and any other developer looking at the code won’t question where this category was created.

Factory Girl’s sequences are a great way to ensure that models with uniqueness don’t run into any naming collisions, and it’s handy to have recognizable data (instead of having every bit of data in tests runs be virtually the same). That said, when writing tests against data where the values matter, don’t leave those values to chance; assign them outright. Give them weight. The intent is much more obvious, less error prone, and requires no patches to Factory Girl.

lolconomy

This week in open source

kumade

It was a refactoring week on kumade, the Heroku deployer. Gabe Berke-Williams and Josh Clayton refactored the Packager class, splitting it out into handlers for Jammit, Less, and no-op packagers (d854184). Then Gabe Berke-Williams (gabebw) went off on his own to introduce a CommandLine class, wrapping Cocaine with some Thor (0c3840d and 72424c0). Meanwhile Joshua Clayton (joshuaclayton) changed should_not have_received to should have_received.never (6652197).

suspenders

Our app making gem, suspenders, saw some updates from Matt Jankowski (mjankowski): Rails 3.1 (10ac09a, 97881fc, 354321d, c8f8c85, and db08366), bourbon (d490b36), flutie (e0bab83), the Hoptoad → Airbrake name change (6def3d7), and tying together the asset pipeline (a4ce3e7 and c760a33).

paul_revere

We have a one-off announcement plugin for Rails, named paul_revere. Nick Quaranto (qrush) released version 0.2.1 (3574b40 and dfbf940).

shoulda-matchers

Our collection of RSpec matchers, shoulda-matchers, is not passing on my laptop. Prem Sichanugrist (sikachu) built out the continuous integration tests more to attempt to clarify the issue (3d94390, 4450c86, 1a3aeec, 6785f59, f1f5e6d).

bourbon

The SCSS gem, bourbon, saw actual words being used by Matt Jankowski (mjankowski) in the documentation (b8afdea), plus Phil LaPier (plapier) updated the docs with resources for investigating browser compatibility (35539c9). He also added this sweet variable, $all-text-inputs, representing all HTML5 textual inputs like color, date, phone, password, URL, and so on (d1def76 and fb299e6). To wrap it all up he released version 0.1.8 (f3046bd).

paperclip

Lots of bug fixes in paperclip, the Rails image uploader plugin gem, this week. Cody Caughlan (ruckus) added S3 support for an HTTP proxy (4661cef). Denis Yagofarov (denyago) preserved the path set for the Cocaine gem (3a35ba9). Aditya Sanghi (asanghi) gave us a warning when two models save files to the same place on the filesystem (8d43e19). Daniel Evans (danielevans) removes the temporary file after it has been uploaded and processed (748332e). Edison (edison) added a feature where you can set the file system path based on a method in the model (996ca87 and 1738f3c).

Prem Sichanugrist (sikachu) then attacked some outstanding bugs: escape the URL (23cb822), handle a space leak where an array was growing by one for each request (d18d814), make the Interpolations.hash class method confirm to the expected signature for hash methods (e526c86), and preserve the filename for S3 attachments when we know the filename (522a53e).

Prem then did some refactoring, splitting the storage tests into individual unit tests (0ca98d1, d204c7d, 31d74d6), stubbing Cocaine (0a77f64), removing some noisy debugging (fd891e0), and cleaning up whitespace (825e1f1 and b2cac53). After all of this he released version 2.4.2 (9edeb01).

factory_girl

Features and refactorings were the name of the game for factory_girl, the fixture replacement for Rails. Joe Ferris and Josh Clayton fixed traits so now you can override attributes of them. They did this by refactoring attributes, introducing an intermediate Declaration object that knows how to compile down to a full factory (a154e64 and f8638b). Joe Ferris (jferris) made callbacks into first-class objects with validations (0f87ca3, ede051f, and 4d30663). Thomas Walpole (twalpole) fixed an inconsistency, ensuring that parent callbacks are called before child callbacks (27c4b21).

Oh man check this: Joel Meador (janxious) updated the ChangeLog (306e51b and 88cf88)! Joshua Clayton (joshuaclayton) went along with this, too (9c95a2) before releasing version 2.1.2 (58e75bc and c1360e).

capybara-webkit

The capybara-webkit test driver, which Joe presented about at Boston Ruby, continues to improve. Gabe Berke-Williams (gabebw) gave us a documentation edit (f493b22), while Matthew Mongeau (halogenandtoast) striped and normalized spaces for consistency with Selenium (6d92f35) and also added support for unknown content types (ff0a6e7, 9257fe3, and 353fe86)

jdclayton

FactoryGirl 2.1.0 Brings the Heat

Factory Girl has seen a handful of changes over the past six weeks since we released 2.0.0. Some of the highlights:

Simple association syntax

factory :user do
  name "John doe"
end

factory :post do
  author :factory => :user
end

Traits

trait :male do
  gender "Male"
end

trait :admin do
  admin true
end

factory :user do
  factory :male_user,       :traits => [:male]
  factory :admin,           :traits => [:admin]
  factory :male_admin_user, :traits => [:male, :admin]
end

Transient attributes

factory :user do
  rockstar(true).ignore
  name { "Johnny#{" Rockstar" if rockstar}" }
end

> FactoryGirl.create(:user).name                     # "Johnny Rockstar"
> FactoryGirl.create(:user, :rockstar => false).name # "Johnny"

Change associations to build instead of create

factory :profile do
  sequence(:username) {|n| "user-#{n}" }
end

factory :user do
  profile :method => :build
end

Factory modification

FactoryGirl.define do
  factory :user do
    name "John Doe"
    sequence(:email) {|n| "user-#{n}@example.com" }
  end
end

FactoryGirl.modify do
  factory :user do
    email { "#{name.downcase.underscore}@example.com" }
  end
end

Factory reloading (handy in a console)

FactoryGirl.reload # reloads all factories, sequences, and traits

Apart from these features, we’ve ensured that Factory Girl processes different attributes (static attributes, dynamic attributes) in a reasonable order, verified it works on Rails 3.1.0, upgraded the test suite to use Mocha + Bourne instead of RR (we at Thoughtbot love Mocha), and a handful of other handy bug fixes.

Grab Factory Girl 2.1.0 and make testing easier!

jdclayton

Never Fear, Traits are Here

I can’t count the number of times I’ve worked on Rails apps where I’m using Factory Girl and want to declare a factory with multiple parents. We’ve all been there, don’t lie.

FactoryGirl.define do
  factory :user do
    name "Friendly User"

    factory :admin do
      admin true
    end

    factory :male_user do
      gender "Male"
      name   "John Doe"

      factory :male_admin_user do
        admin true
      end

      factory :teenage_male_user do
        date_of_birth { 15.years.ago }

        factory :teenage_admin_male_user do
          admin true
        end
      end
    end

    factory :female_user do
      gender "Female"
      name   "Jane Doe"

      factory :female_admin_user do
        admin true
      end

      factory :teenage_female_user do
        date_of_birth { 15.years.ago }

        factory :teenage_admin_female_user do
          admin true
        end
      end
    end
  end
end

Oh, the duplication! This isn’t DRY.

Factory Girl 2.0.4 introduces traits. I’ll cut to the chase:

FactoryGirl.define do
  factory :user do
    name "Friendly User"

    trait :admin do
      admin true
    end

    trait :male do
      gender "Male"
      name   "John Doe"
    end

    trait :female do
      gender "Female"
      name   "Jane Doe"
    end

    trait :teenager do
      date_of_birth { 15.years.ago }
    end

    factory :male_user,                 :traits => [:male]
    factory :female_user,               :traits => [:female]
    factory :teenage_male_user,         :traits => [:teenager, :male]
    factory :teenage_female_user,       :traits => [:teenager, :female]
    factory :male_admin_user,           :traits => [:admin, :male]
    factory :female_admin_user,         :traits => [:admin, :female]
    factory :teenage_admin_male_user,   :traits => [:teenager, :admin, :male]
    factory :teenage_admin_female_user, :traits => [:teenager, :admin, :female]
  end
end

Mix and match however you please. Bonus: you can assign traits like plain-old attributes.

FactoryGirl.define do
  factory :user do
    name "Friendly User"

    trait :admin do
      admin true
    end

    trait :male do
      gender "Male"
      name   "John Doe"
    end

    factory :male_admin_user do
      male
      admin
    end
  end
end

Many props go out to Thomas Walpole, who helped get a lot of this written; thanks Thomas!

So go ahead, get your trait on; you’ll thank me later.