h1. dm-sweatshop h2. Overview dm-sweatshop is a model factory for DataMapper. It makes it easy & painless to crank out complex pseudo random models -- useful for tests and seed data. Production Goals: * Easy generation of random models with data that fits the application domain. * Simple syntax for declaring and generating model patterns. * Add context to model patterns, allowing grouping and * Effortlessly generate or fill in associations for creating complex models with few lines of code. h2. Examples Starting off with a simple user model.

  class User
    include DataMapper::Resource
    
    property :id, Serial
    property :username, String
    property :email,    String
    property :password, String
  end

A fixture for the user model can be defined using the @fixture@ method.

  User.fixture {{
    :username             => (username = /\w+/.gen),
    :email                => "#{username}@example.com",
    :password             => (password = /\w+/.gen),
    :pasword_confirmation => password
    
    # The /\w+/.gen notation is part of the randexp gem:
    # http://github.com/benburkert/randexp/
  }}

Notice the double curly brace (@{{@), a quick little way to pass a block that returns a hash to the fixture method. This is important because it ensures the data is random when we generate a new instance of the model, by calling the block every time. And here's how you generate said model.

  User.generate

That's it. In fact, it can even be shortened.

  User.gen

h3. Associations The real power of sweatshop is generating working associations.

  DataMapper.setup(:default, "sqlite3::memory:")
  
  class Tweet
    include DataMapper::Resource
    
    property :id, Serial
    property :message,  String, :length => 140
    property :user_id,  Integer

    belongs_to :user
    has n, :tags, :through => Resource
  end
  
  class Tag
    include DataMapper::Resource

    property :id, Serial
    property :name, String

    has n, :tweets, :through => Resource
  end
  
  class User
    include DataMapper::Resource
    
    property :id, Serial
    property :username, String
    
    has n, :tweets
  end

  DataMapper.auto_migrate!

  User.fix {{
    :username => /\w+/.gen,
    :tweets   => 500.of {Tweet.make}
  }}
  
  Tweet.fix {{
    :message => /[:sentence:]/.gen[0..140],
    :tags    => (0..10).of {Tag.make}
  }}

  Tag.fix {{
    :name => /\w+/.gen
  }}
  
  # now lets generate 100 users, each with 500 tweets.  Also, the tweet's have 0 to 10 tags!
  users = 10.of {User.gen}

That's going to generate alot of tags, way more than you would see in the production app. Let's recylce some already generated tags instead.

  User.fix {{
    :username => /\w+/.gen,
    :tweets   => 500.of {Tweet.make}
  }}
  
  Tweet.fix {{
    :message => /[:sentence:]/.gen[0..140],
    :tags    => (0..10).of {Tag.pick}           #lets pick, not make this time
  }}
  
  Tag.fix {{
    :name => /\w+/.gen
  }}
  
  50.times {Tag.gen}
  
  users = 10.of {User.gen}

h3. Contexts You can add multiple fixtures to a mode, dm-sweatshop will randomly pick between the available fixtures when it generates a new model.

  Tweet.fix {{
    :message  => /\@#{User.pick.name} [:sentence:]/.gen[0..140],   #an @reply for some user
    :tags     => (0..10).of {Tag.pick}
  }}

To keep track of all of our new fixtures, we can even give them a context.

  Tweet.fix(:at_reply) {{
    :message  => /\@#{User.pick.name} [:sentence:]/.gen[0..140],
    :tags     => (0..10).of {Tag.pick}
  }}
  
  Tweet.fix(:conversation) {{
    :message  => /\@#{(tweet = Tweet.pick(:at_reply)).user.name} [:sentence:]/.gen[0..140],
    :tags     => tweet.tags
  }}

h3. Overriding a fixture Sometimes you will want to change one of your fixtures a little bit. You create a new fixture with a whole new context, but this can be overkill. The other option is to specify attributes in the call to @generate@.

  User.gen(:username => 'datamapper')  #uses 'datamapper' as the user name instead of the randomly generated word

This works with contexts too.

  User.gen(:conversation, :tags => Tag.all)       #a very, very broad conversation

Go forth, and populate your data. h2. Best Practices h3. Specs The suggested way to use dm-sweatshop with test specs is to create a @spec/spec_fixtures.rb@ file, then declare your fixtures in there. Next, @require@ it in your @spec/spec_helper.rb@ file, after your models have loaded.

  Merb.start_environment(:testing => true, :adapter => 'runner', :environment => ENV['MERB_ENV'] || 'test')

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

Add the @.generate@ calls in your @before@ setup. Make sure to clear your tables or @auto_migrate@ your models after each spec! h2. Possible Improvements h3. Enforcing Validations Enforce validations at generation time, before the call to @new@/@create@.

  User.fix {{
    :username.unique  => /\w+/.gen,
    :tweets           => 500.of {Tweet.make}
  }}

h3. Better Exception Handling h3. Smarter @pick@ Add multiple contexts to pick, or an ability to _fall back_ if one context has no generated models.