Skip to content

Instantly share code, notes, and snippets.

@josevalim
Created October 1, 2010 12:25
Show Gist options
  • Save josevalim/606129 to your computer and use it in GitHub Desktop.
Save josevalim/606129 to your computer and use it in GitHub Desktop.

Revisions

  1. josevalim revised this gist Oct 1, 2010. 1 changed file with 26 additions and 27 deletions.
    53 changes: 26 additions & 27 deletions builders.rb
    Original file line number Diff line number Diff line change
    @@ -1,50 +1,49 @@
    # Rails developers have had bad experience with fixtures since a long time
    # due to several reasons, including misuse.
    # Rails developers have long had bad experiences with fixtures for
    # several reasons, including misuse.
    #
    # This misuse of fixtures is often characterized by a huge ammount of
    # fixtures, causing a lot of data to maintain and dependence between tests.
    # In my experience working (and rescueing) different applications, 80% of
    # these fixtures are used only by 20% of tests.
    # 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 a test that assures a given SQL query with
    # 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,
    # we need a huge amount of data in this test which we usually don't need in
    # order tests.
    # a huge amount of data is needed for this test, most of which we won't be used
    # in other tests.
    #
    # For such scenarios, factories are a fine solution. They won't cluster
    # all your database since they are created for these specific tests and they
    # are also easier to maintain.
    # 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 as we saw in the couple two years ago.
    # adopt factories builders over the few years.
    #
    # However, factories are also misused. It is common to see people creating
    # a huge amount of data with factories before each test in their integration
    # suite, causing their whole test suite to be slow, while fixtures would
    # 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, use fixtures. For all the
    # 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 code below provides a quick solution
    # that allows you to create small, simple factories from the information
    # stored in your fixtures.
    # require valid attributes, this quick solution allows you to create small,
    # simple factories from the information stored in your fixtures.
    #
    # == Examples
    #
    # You can define your builder inside the Builders module:
    # Define your builder inside the Builders module:
    #
    # module Builders
    # build :message do
    # { :title => "OMG", :queue => queues(:general) }
    # end
    # end
    #
    # It should necessarily return a hash. After defining this builder,
    # you can easily create a new message calling +create_message+ or
    # +new_message+ in your tests. Both methods accepts an optional
    # options parameter that is merged into the given hash.
    # 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
    #
    @@ -66,8 +65,8 @@
    # == Just Ruby
    #
    # Since all Builders are defined inside the Builders module, without
    # a DSL on top of it, it allows us to use Ruby in case we need to do
    # something more complex, like supporting sequences.
    # a DSL on top of it, we can use Ruby to meet more complex needs,
    # like supporting sequences.
    #
    # module Builders
    # @@sequence = 0
  2. josevalim revised this gist Oct 1, 2010. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion builders.rb
    Original file line number Diff line number Diff line change
    @@ -140,4 +140,9 @@ module Builders
    :profile => new_profile
    }
    end
    end
    end

    test "users sets profile gravatar on save" do
    user = create_user!
    assert_equal Digest::MD5.hexdigest("[email protected]"), user.profile.gravatar
    end
  3. josevalim revised this gist Oct 1, 2010. 1 changed file with 31 additions and 18 deletions.
    49 changes: 31 additions & 18 deletions builders.rb
    Original file line number Diff line number Diff line change
    @@ -1,22 +1,35 @@
    # A couple years ago the Rails community discovered the joys of factory
    # builders as an alternative to fixtures. Rails developers have had
    # bad experience with fixtures since a long time due to several reasons
    # including misuse.
    # Rails developers have had bad experience with fixtures since a long time
    # due to several reasons, including misuse.
    #
    # This misuse is often characterized by a huge ammount of factories,
    # causing a lot of data to maintain and dependence between tests. In my
    # experience working (and rescueing) different applications, 80% of these
    # factories are used only by 20% of tests (which are usually tests that
    # need a huge ammount of data to test different scenarios).
    #
    # Factory builders provides an alternative for you to easily build different
    # scenarios based on a commont set of factories. However, if you have
    # integration tests, creating the basic structure before each tests using
    # factories is definitely expensive.
    #
    # This is an attempt to have the best of both worlds by incentivating
    # you to use fixtures but providing helpers similar to factories built
    # on top of your fixtures.
    # This misuse of fixtures is often characterized by a huge ammount of
    # fixtures, causing a lot of data to maintain and dependence between tests.
    # In my experience working (and rescueing) different applications, 80% of
    # these fixtures are used only by 20% of tests.
    #
    # An example of such tests is a test that assures a given SQL query with
    # GROUP BY and ORDER BY conditions returns the correct result set. As expected,
    # we need a huge amount of data in this test which we usually don't need in
    # order tests.
    #
    # For such scenarios, factories are a fine solution. They won't cluster
    # all your database since they are created for these specific tests and they
    # are also easier to maintain.
    #
    # I believe this was the primary reason for the Rails community to strongly
    # adopt factories builders as we saw in the couple two years ago.
    #
    # However, factories are also misused. It is common to see people creating
    # a huge amount of data with factories before each test in their integration
    # suite, causing their whole test suite to be slow, while 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, use fixtures. For all the
    # other smaller scenarios, use factories. As both fixtures and factories
    # require valid attributes, this code below provides a quick solution
    # that allows you to create small, simple factories from the information
    # stored in your fixtures.
    #
    # == Examples
    #
  4. josevalim revised this gist Oct 1, 2010. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion builders.rb
    Original file line number Diff line number Diff line change
    @@ -65,7 +65,9 @@
    # end
    #

    ## Source code. May be released as gem soon.
    ## 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
  5. josevalim created this gist Oct 1, 2010.
    128 changes: 128 additions & 0 deletions builders.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,128 @@
    # A couple years ago the Rails community discovered the joys of factory
    # builders as an alternative to fixtures. Rails developers have had
    # bad experience with fixtures since a long time due to several reasons
    # including misuse.
    #
    # This misuse is often characterized by a huge ammount of factories,
    # causing a lot of data to maintain and dependence between tests. In my
    # experience working (and rescueing) different applications, 80% of these
    # factories are used only by 20% of tests (which are usually tests that
    # need a huge ammount of data to test different scenarios).
    #
    # Factory builders provides an alternative for you to easily build different
    # scenarios based on a commont set of factories. However, if you have
    # integration tests, creating the basic structure before each tests using
    # factories is definitely expensive.
    #
    # This is an attempt to have the best of both worlds by incentivating
    # you to use fixtures but providing helpers similar to factories built
    # on top of your fixtures.
    #
    # == Examples
    #
    # You can define your builder inside the Builders module:
    #
    # module Builders
    # build :message do
    # { :title => "OMG", :queue => queues(:general) }
    # end
    # end
    #
    # It should necessarily return a hash. After defining this builder,
    # you can easily create a new message calling +create_message+ or
    # +new_message+ in your tests. Both methods accepts an optional
    # options parameter that is 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, it allows us to use Ruby in case we need to do
    # something more complex, like supporting sequences.
    #
    # module Builders
    # @@sequence = 0
    #
    # def sequence
    # @@sequence += 1
    # end
    # end
    #

    ## Source code. 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