Commit 5a5858f4 authored by Bhavitha.S's avatar Bhavitha.S

Import Upstream version 2.0

parents
.DS_Store
/.bundle
/.rvmrc
/coverage
/doc
/pkg
/tags
PATH
remote: .
specs:
machinist (2.0.0.beta2)
GEM
remote: http://rubygems.org/
specs:
activemodel (3.0.9)
activesupport (= 3.0.9)
builder (~> 2.1.2)
i18n (~> 0.5.0)
activerecord (3.0.9)
activemodel (= 3.0.9)
activesupport (= 3.0.9)
arel (~> 2.0.10)
tzinfo (~> 0.3.23)
activesupport (3.0.9)
arel (2.0.10)
builder (2.1.2)
diff-lcs (1.1.2)
i18n (0.5.0)
mysql (2.8.1)
rake (0.9.2)
rcov (0.9.9)
rdoc (3.6.1)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
tzinfo (0.3.28)
PLATFORMS
ruby
DEPENDENCIES
activerecord
machinist!
mysql
rake
rcov
rdoc
rspec
Copyright (c) 2008-2010 Peter Yandell
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Machinist 2
*Fixtures aren't fun. Machinist is.*
Machinist 2 is **still in beta**!
If you're using Rails 3, you'll want to give Machinist 2 a go, but be aware
that the documentation is still patchy.
That said, have a look at [the
specs](https://github.com/notahat/machinist/tree/master/spec), starting with
[the spec for
Machinable](https://github.com/notahat/machinist/blob/master/spec/machinable_spec.rb).
No, really, have a look. I wrote this code to be read, and the specs do a
pretty clean job of documenting what it all does.
If, on the other hand, you want the tried, tested, and well-documented official
release version of Machinist, [then go with Machinist
1](http://github.com/notahat/machinist/tree/1.0-maintenance).
- [Home page](http://github.com/notahat/machinist)
- [Google group](http://groups.google.com/group/machinist-users), for support
- [Bug tracker](http://github.com/notahat/machinist/issues), for reporting Machinist bugs
## Introduction
Machinist makes it easy to create objects for use in tests. It generates data
for the attributes you don't care about, and constructs any necessary
associated objects, leaving you to specify only the fields you care about in
your test. For example:
describe Comment, "without_spam scope" do
it "doesn't include spam" do
# This will make a Comment, a Post, and a User (the author of the
# Post), generate values for all their attributes, and save them:
spam = Comment.make!(:spam => true)
Comment.without_spam.should_not include(spam)
end
end
You tell Machinist how to do this with blueprints:
require 'machinist/active_record'
User.blueprint do
username { "user#{sn}" } # Each user gets a unique serial number.
end
Post.blueprint do
author
title { "Post #{sn}" }
body { "Lorem ipsum..." }
end
Comment.blueprint do
post
email { "commenter#{sn}@example.com" }
body { "Lorem ipsum..." }
end
## Installation
### Upgrading from Machinist 1
See [the wiki](http://wiki.github.com/notahat/machinist/machinist-2).
### Rails 3
In your app's `Gemfile`, in the `group :test` section, add:
gem 'machinist', '>= 2.0.0.beta2'
Then run:
bundle
rails generate machinist:install
If you want Machinist to automatically add a blueprint to your blueprints file
whenever you generate a model, add the following to your `config/application.rb`
inside the Application class:
config.generators do |g|
g.fixture_replacement :machinist
end
### Rails 2
See [the wiki](http://wiki.github.com/notahat/machinist/rails-2).
## Usage
### Blueprints
A blueprint describes how to generate an object. The blueprint takes care of
providing attributes that your test doesn't care about, leaving you to focus on
just the attributes that are important for the test.
A simple blueprint might look like this:
Post.blueprint do
title { "A Post" }
body { "Lorem ipsum..." }
end
You can then construct a Post from this blueprint with:
Post.make!
When you call `make!`, Machinist calls `Post.new`, then runs through the
attributes in your blueprint, calling the block for each attribute to generate
a value. It then saves and reloads the Post. (It throws an exception if the
Post can't be saved.)
You can override values defined in the blueprint by passing a hash to make:
Post.make!(:title => "A Specific Title")
If you want to generate an object without saving it to the database, replace
`make!` with `make`.
### Unique Attributes
For attributes that need to be unique, you can call the `sn` method from
within the attribute block to get a unique serial number for the object.
User.blueprint do
username { "user-#{sn}" }
end
### Associations
If your object needs associated objects, you can generate them like this:
Comment.blueprint do
post { Post.make }
end
Calling `Comment.make!` will construct a Comment and its associated Post, and
save both.
Machinist is smart enough to look at the association and work out what sort of
object it needs to create, so you can shorten the above blueprint to:
Comment.blueprint do
post
end
If you want to override the value for post when constructing the comment, you
can do this:
post = Post.make(:title => "A particular title)
comment = Comment.make(:post => post)
For `has_many` and `has_and_belongs_to_many` associations, you can create
multiple associated objects like this:
Post.blueprint do
comments(3) # Makes 3 comments.
end
### Named Blueprints
Named blueprints let you define variations on an object. For example, suppose
some of your Users are administrators:
User.blueprint do
name { "User #{sn}" }
email { "user-#{sn}@example.com" }
end
User.blueprint(:admin) do
name { "Admin User #{sn}" }
admin { true }
end
Calling:
User.make!(:admin)
will use the `:admin` blueprint.
Named blueprints call the default blueprint to set any attributes not
specifically provided, so in this example the `email` attribute will still be
generated even for an admin user.
You must define a default blueprint for any class that has a named blueprint,
even if the default blueprint is empty.
### Blueprints on Plain Old Ruby Objects
Machinist also works with plain old Ruby objects. Let's say you have a class like:
class Post
extend Machinist::Machinable
attr_accessor :title
attr_accessor :body
end
You can blueprint the Post class just like anything else:
Post.blueprint do
title { "A title!" }
body { "A body!" }
end
And `Post.make` will construct a new Post.
### Other Tricks
You can refer to already assigned attributes when constructing a new attribute:
Post.blueprint do
author { "Author #{sn}" }
body { "Post by #{object.author}" }
end
## Compatibility
I've tested this with:
Ruby versions: 1.8.7, 1.9.2
Rails versions: 2.3, 3.0
It may well be happy with other versions too, but I'm not promising anything.
Compatibility patches are welcome.
## Developing
The Machinist specs and source code were written to be read, and I'm pretty
happy with them. Don't be afraid to have a look under the hood!
If you want to submit a patch:
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don't break it in a
future version unintentionally.
- Commit, do not mess with rakefile, version, or history.
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- Send me a pull request. Bonus points for topic branches.
## Status
In active use in a number of large Rails 2 apps.
Development has been sporadic, but is picking up again.
## Contributors
Machinist is maintained by Pete Yandell ([pete@notahat.com](mailto:pete@notahat.com), [@notahat](http://twitter.com/notahat))
Other contributors include:
[Marcos Arias](http://github.com/yizzreel),
[Jack Dempsey](http://github.com/jackdempsey),
[Jeremy Durham](http://github.com/jeremydurham),
[Clinton Forbes](http://github.com/clinton),
[Perryn Fowler](http://github.com/perryn),
[Niels Ganser](http://github.com/Nielsomat),
[Jeremy Grant](http://github.com/jeremygrant),
[Jon Guymon](http://github.com/gnarg),
[James Healy](http://github.com/yob),
[Ben Hoskings](http://github.com/benhoskings),
[Evan David Light](http://github.com/elight),
[Chris Lloyd](http://github.com/chrislloyd),
[Adam Meehan](http://github.com/adzap),
[Kyle Neath](http://github.com/kneath),
[Lawrence Pit](http://github.com/lawrencepit),
[Xavier Shay](http://github.com/xaviershay),
[T.J. Sheehy](http://github.com/tjsheehy),
[Roland Swingler](http://github.com/knaveofdiamonds),
[Gareth Townsend](http://github.com/quamen),
[Matt Wastrodowski](http://github.com/towski),
[Ian White](http://github.com/ianwhite)
Thanks to Thoughtbot's [Factory
Girl](http://github.com/thoughtbot/factory_girl/tree/master). Machinist was
written because I loved the idea behind Factory Girl, but I thought the
philosophy wasn't quite right, and I hated the syntax.
require 'rubygems'
require 'bundler'
Bundler::GemHelper.install_tasks
require 'rake'
require 'rspec/core/rake_task'
require 'rdoc/task'
RSpec::Core::RakeTask.new
RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.rcov = true
spec.rcov_opts = ['--exclude', 'spec', '--exclude', '.rvm']
end
desc 'Run the specs.'
task :default => :rcov
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = 'Machinist'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('lib')
end
task :notes do
system "grep -n -r 'FIXME\\|TODO' lib spec"
end
Description:
Copy Machinist files to your application.
module Machinist
module Generators #:nodoc:
class InstallGenerator < Rails::Generators::Base #:nodoc:
source_root File.expand_path('../templates', __FILE__)
class_option :test_framework, :type => :string, :aliases => "-t", :desc => "Test framework to use Machinist with"
class_option :cucumber, :type => :boolean, :desc => "Set up access to Machinist from Cucumber"
def blueprints_file
if rspec?
copy_file "blueprints.rb", "spec/support/blueprints.rb"
else
copy_file "blueprints.rb", "test/blueprints.rb"
end
end
def test_helper
if test_unit?
inject_into_file("test/test_helper.rb", :after => "require 'rails/test_help'\n") do
"require File.expand_path(File.dirname(__FILE__) + '/blueprints')\n"
end
end
end
def cucumber_support
if cucumber?
template "machinist.rb.erb", "features/support/machinist.rb"
end
end
private
def rspec?
options[:test_framework].to_sym == :rspec
end
def test_unit?
options[:test_framework].to_sym == :test_unit
end
def cucumber?
options[:cucumber]
end
end
end
end
require 'machinist/active_record'
# Add your blueprints here.
#
# e.g.
# Post.blueprint do
# title { "Post #{sn}" }
# body { "Lorem ipsum..." }
# end
<%- if rspec? -%>
# Load the blueprints from over in spec support.
require "#{Rails.root}/spec/support/blueprints"
<%- else -%>
# Load the blueprints from over in test.
require "#{Rails.root}/test/blueprints"
<%- end -%>
module Machinist
module Generators #:nodoc:
class ModelGenerator < Rails::Generators::NamedBase #:nodoc:
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
def create_blueprint
append_file "spec/support/blueprints.rb", "\n#{class_name}.blueprint do\n # Attributes here\nend\n"
end
end
end
end
require 'machinist/blueprint'
require 'machinist/exceptions'
require 'machinist/lathe'
require 'machinist/machinable'
require 'active_record'
require 'machinist'
require 'machinist/active_record/blueprint'
require 'machinist/active_record/lathe'
module ActiveRecord #:nodoc:
class Base #:nodoc:
extend Machinist::Machinable
def self.blueprint_class
Machinist::ActiveRecord::Blueprint
end
end
end
module Machinist::ActiveRecord
class Blueprint < Machinist::Blueprint
# Make and save an object.
def make!(attributes = {})
object = make(attributes)
object.save!
object.reload
end
def lathe_class #:nodoc:
Machinist::ActiveRecord::Lathe
end
end
end
module Machinist::ActiveRecord
class Lathe < Machinist::Lathe
def make_one_value(attribute, args) #:nodoc:
if block_given?
raise_argument_error(attribute) unless args.empty?
yield
else
make_association(attribute, args)
end
end
def make_association(attribute, args) #:nodoc:
association = @klass.reflect_on_association(attribute)
if association
association.klass.make(*args)
else
raise_argument_error(attribute)
end
end
end
end
module Machinist
# A Blueprint defines a method of constructing objects of a particular class.
class Blueprint
# Construct a blueprint for the given +klass+.
#
# Pass in the +:parent+ option to define a parent blueprint to apply after
# this one. You can supply another blueprint, or a class in which to look
# for a blueprint. In the latter case, make will walk up the superclass
# chain looking for blueprints to apply.
def initialize(klass, options = {}, &block)
@klass = klass
@parent = options[:parent]
@block = block
end
attr_reader :klass, :parent, :block
# Generate an object from this blueprint.
#
# Pass in attributes to override values defined in the blueprint.
def make(attributes = {})
lathe = lathe_class.new(@klass, new_serial_number, attributes)
lathe.instance_eval(&@block)
each_ancestor {|blueprint| lathe.instance_eval(&blueprint.block) }
lathe.object
end
# Returns the Lathe class used to make objects for this blueprint.
#
# Subclasses can override this to substitute a custom lathe class.
def lathe_class
Lathe
end
# Returns the parent blueprint for this blueprint.
def parent_blueprint
case @parent
when nil
nil
when Blueprint
# @parent references the parent blueprint directly.
@parent
else
# @parent is a class in which we should look for a blueprint.
find_blueprint_in_superclass_chain(@parent)
end
end
# Yields the parent blueprint, its parent blueprint, etc.
def each_ancestor
ancestor = parent_blueprint
while ancestor
yield ancestor
ancestor = ancestor.parent_blueprint
end
end
protected
def new_serial_number #:nodoc:
parent_blueprint = self.parent_blueprint # Cache this for speed.
if parent_blueprint
parent_blueprint.new_serial_number
else
@serial_number ||= 0
@serial_number += 1
sprintf("%04d", @serial_number)
end
end
private
def find_blueprint_in_superclass_chain(klass)
until has_blueprint?(klass) || klass.nil?
klass = klass.superclass
end
klass && klass.blueprint
end
def has_blueprint?(klass)
klass.respond_to?(:blueprint) && !klass.blueprint.nil?
end
end
end
module Machinist
# Raised when make! is called on a class whose blueprints don't support
# saving.
class BlueprintCantSaveError < RuntimeError
attr_reader :blueprint
def initialize(blueprint)
@blueprint = blueprint
end
def message
"make! is not supported by blueprints for class #{@blueprint.klass.name}"
end
end
# Raised when calling make on a class with no corresponding blueprint
# defined.
class NoBlueprintError < RuntimeError
attr_reader :klass, :name
def initialize(klass, name)
@klass = klass
@name = name
end
def message
"No #{@name} blueprint defined for class #{@klass.name}"
end
end
end
module Machinist
# When you make an object, the blueprint for that object is instance-evaled
# against a Lathe.
#
# The Lathe implements all the methods that are available to the blueprint,
# including method_missing to let the blueprint define attributes.
class Lathe
def initialize(klass, serial_number, attributes = {})
@klass = klass
@serial_number = serial_number
@assigned_attributes = {}
@object = @klass.new
attributes.each {|key, value| assign_attribute(key, value) }
end
# Returns a unique serial number for the object under construction.
def sn
@serial_number
end
# Returns the object under construction.
attr_reader :object
def method_missing(attribute, *args, &block) #:nodoc:
unless attribute_assigned?(attribute)
assign_attribute(attribute, make_attribute(attribute, args, &block))
end
end
# Undef a couple of methods that are common ActiveRecord attributes.
# (Both of these are deprecated in Ruby 1.8 anyway.)
undef_method :id if respond_to?(:id)
undef_method :type if respond_to?(:type)
protected
def make_attribute(attribute, args, &block) #:nodoc:
count = args.shift if args.first.is_a?(Fixnum)
if count
Array.new(count) { make_one_value(attribute, args, &block) }
else
make_one_value(attribute, args, &block)
end
end
def make_one_value(attribute, args) #:nodoc:
raise_argument_error(attribute) unless args.empty?
yield
end
def assign_attribute(key, value) #:nodoc:
@assigned_attributes[key.to_sym] = value
@object.send("#{key}=", value)
end
def attribute_assigned?(key) #:nodoc:
@assigned_attributes.has_key?(key.to_sym)
end
def raise_argument_error(attribute) #:nodoc:
raise ArgumentError.new("Invalid arguments to attribute #{attribute} in blueprint")
end
end
end
module Machinist
# Extend classes with this module to define the blueprint and make methods.
module Machinable
# Define a blueprint with the given name for this class.
#
# e.g.
# Post.blueprint do<