-
-
Save billsaysthis/606387 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Rails developers have long had bad experiences with fixtures for | |
# several reasons, including misuse. | |
# | |
# Misuse of fixtures is characterized by having a huge number of them, | |
# requiring the developer to maintain a lot of data and creating dependencies | |
# between tests. In my experience working (and rescuing) many applications, 80% | |
# of fixtures are only used by 20% of tests. | |
# | |
# An example of such tests is one assuring that a given SQL query with | |
# GROUP BY and ORDER BY conditions returns the correct result set. As expected, | |
# a huge amount of data is needed for this test, most of which we won't be used | |
# in other tests. | |
# | |
# For these scenarios factories are a fine solution. They won't clutter up | |
# your database since they are created (and destroyed) during the execution | |
# of specific tests and are easier to maintain as the underlying models change. | |
# | |
# I believe this was the primary reason for the Rails community to strongly | |
# adopt factories builders over the few years. | |
# | |
# However, factories are also misused. Developers commonly create a huge | |
# amount of data with factories before each test in an integration | |
# suite, which causes their test suite to run slowly, where fixtures would | |
# work great for this purpose. | |
# | |
# This is a small attempt to have the best of both worlds. | |
# | |
# For the data used in almost all your tests, simply use fixtures. For all the | |
# other smaller scenarios, use factories. As both fixtures and factories | |
# require valid attributes, this quick solution allows you to create small, | |
# simple factories from the information stored in your fixtures. | |
# | |
# == Examples | |
# | |
# Define your builder inside the Builders module: | |
# | |
# module Builders | |
# build :message do | |
# { :title => "OMG", :queue => queues(:general) } | |
# end | |
# end | |
# | |
# The builder must return a hash. After defining this builder, | |
# create a new message by calling +create_message+ or +new_message+ | |
# in your tests. Both methods accepts an optional options | |
# parameter that gets merged into the given hash. | |
# | |
# == Reusing fixtures | |
# | |
# The great benefit of builders is that you can reuse your fixtures | |
# attributes, avoiding duplication. An explicit way of doing it is: | |
# | |
# build :message do | |
# messages(:fixture_one).attributes.merge( | |
# :title => "Overwritten title" | |
# ) | |
# end | |
# | |
# However, Builders provide an implicit way of doing the same: | |
# | |
# build :message, :like => :fixture_one do | |
# { :title => "Overwritten title" } | |
# end | |
# | |
# == Just Ruby | |
# | |
# Since all Builders are defined inside the Builders module, without | |
# a DSL on top of it, we can use Ruby to meet more complex needs, | |
# like supporting sequences. | |
# | |
# module Builders | |
# @@sequence = 0 | |
# | |
# def sequence | |
# @@sequence += 1 | |
# end | |
# end | |
# | |
## Source code | |
# Put it on test/supports/builders.rb and ensure it is required. | |
# May be released as gem soon. | |
module Builders | |
@@builders = ActiveSupport::OrderedHash.new | |
def self.build(name, options={}, &block) | |
klass = options[:as] || name.to_s.classify.constantize | |
builder = if options[:like] | |
lambda { send(name.to_s.pluralize, options[:like]).attributes.merge(block.call) } | |
else | |
block | |
end | |
@@builders[name] = [klass, builder] | |
end | |
def self.retrieve(scope, name, method, options) | |
if builder = @@builders[name.to_sym] | |
klass, block = builder | |
hash = block.bind(scope).call.merge(options || {}) | |
hash.delete("id") | |
[klass, hash] | |
else | |
raise NoMethodError, "No builder #{name.inspect} for `#{method}'" | |
end | |
end | |
def method_missing(method, *args, &block) | |
case method.to_s | |
when /(create|new)_(.*?)(!)?$/ | |
klass, hash = Builders.retrieve(self, $2, method, args.first) | |
object = klass.new | |
object.send("attributes=", hash, false) | |
object.send("save#{$3}") if $1 == "create" | |
object | |
when /valid_(.*?)_attributes$/ | |
Builders.retrieve(self, $1, method, args.first)[1] | |
else | |
super | |
end | |
end | |
ActiveSupport::TestCase.send :include, self | |
end | |
## Some examples from a Real App™. | |
module Builders | |
build :profile, :like => :hugobarauna do | |
{ :username => "georgeguimaraes" } | |
end | |
build :user do | |
{ | |
:email => "[email protected]", | |
:password => "123456", | |
:profile => new_profile | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment