Factory Girl now has callbacks thanks to Nate Sutton.

There are three callbacks:
after_build
after_create
after_stub
When build(:user) is invoked, the after_build callback runs after building the user. Likewise for create and stub.
These come in handy in a number of common use cases.
Models:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Factories:
factory :article do
body 'password'
factory :article_with_comment do
after_create do |article|
create(:comment, article: article)
end
end
end
factory :comment do
body 'Great article!'
end
Nice. Callbacks let us do this:
article = create(:article_with_comment)
Instead of this:
article = create(:article)
create(:comment, article: article)
The savings get larger when the object graph gets more complex:
Models:
class User < ActiveRecord::Base
has_many :interests, as: :interested
has_many :topics, through: :interests
end
class Interest < ActiveRecord::Base
belongs_to :topic
belongs_to :interested, polymorphic: true
end
Building block factories:
factory :user
email
password 'password'
factory :email_confirmed_user do
email_confirmed { true }
end
end
factory :topic do
name 'topic_name'
end
factory :interest
topic
interested factory: :user
end
factory :music_interest, class: 'Interest' do
topic.association(:topic, name: 'Music')
end
factory :sports_interest, class: 'Interest' do
topic.association(:topic, name: 'Sports')
end
And now the payoff:
factory :musical_user, parent: :email_confirmed_user do
after_create { |user| create(:music_interest, interested: user)
end
factory :sporty_user, parent: :email_confirmed_user do
after_create { |user| create(:sports_interest, interested: user) }
end
Again, factories let us do this:
user = create(:musical_user)
Instead of:
user = create(:email_confirmed_user)
create(:music_interest, interested: user)
More intention-revealing. More encapsulation, protecting us from change.
Fakes are a good approach for testing objects that interface with web services such as geocoding and payment processing.
class User < ActiveRecord::Base
acts_as_mappable
before_validation :geocode_location, if: :location_changed?
def geocode_location
geo = Geokit::Geocoders::MultiGeocoder.geocode(location)
self.lat, self.lng = geo.lat, geo.lng
end
end
factory :user do
factory :boston_user do
location 'Boston, MA'
after_build do |user|
Geokit::Geocoders::FakeGeocoder.locations['Boston, MA'] = [0, 1]
end
end
end
Install:
gem 'factory_girl'
Happy testing.
Written by Dan Croak.
before_filter :authorize
protected
def authorize
unless logged_in?
session[:return_to] = request.request_uri
redirect_to login_url and return false
end
end
You want your before_filter to authorize the current_user unless they are logged in. So why not say it like you mean it?
before_filter :authorize, :unless => logged_in?
protected
def authorize
session[:return_to] = request.request_uri
redirect_to login_url and return false
end
This is more expressive. The conditional logic is no longer hidden in the method.
The authorize method now has a single responsibility: to authorize. The before_filter, responsible for performing some action before your controller action is called, is now smarter about when it is supposed to run.
The When Rails plugin adds :if and :unless modifiers to before_filters, most ActiveRecord callbacks, and validations.
Get it:
piston import https://svn.thoughtbot.com/plugins/when/trunk vendor/plugins/when
Then start improving your controllers:
before_filter :deny_access, :unless => :admin?
protected
def deny_access
flash[:failure] = "You do not have access to that page."
redirect_to home_url
end
def admin?
logged_in? and current_user.admin?
end
Improve your models, too:
before_create :encrypt_password,
:unless => lambda {|user| user.password_confirmation.blank?}
after_save :send_alerts, :if => :alerts?
validate_on_create :add_unsupported_type_error,
:unless => lambda {|document| SUPPORTED_FILE_TYPES.include? document.file_type}
Jared makes a strong argument that this is actually removing conditional logic from your program. I tend to agree.
It reminds me of defining constants in your environments so you don’t write conditional logic that checks the RAILS_ENV. That kind of control flow belongs in the framework.
Whether this is removing conditional logic is moot, though. It just feels right, and isn’t that why we write Ruby?
Update … After releasing this, we found out this is already in Rails trunk. Awesome! It’s a great feature and should be in the framework. So, if you’re not running on Edge, use the when plugin to get this feature now. Then, remove it when you upgrade to the next version of Rails, when you’ll get :ifs and :unlesses “for free.” Happy coding!