dfl-factories-and-workers 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +10 -0
- data/LICENSE +19 -0
- data/README.rdoc +149 -0
- data/Rakefile +22 -0
- data/init.rb +2 -0
- data/lib/factories-and-workers.rb +4 -0
- data/lib/factories-and-workers/factory_builder.rb +112 -0
- data/lib/factories-and-workers/factory_generator.rb +49 -0
- data/lib/factories-and-workers/factory_worker.rb +60 -0
- data/lib/tasks/generate_factories.rake +18 -0
- data/rails/init.rb +8 -0
- data/test/app/models/monkey.rb +6 -0
- data/test/app/models/pirate.rb +3 -0
- data/test/app/models/user.rb +3 -0
- data/test/db/schema.rb +17 -0
- data/test/factories.rb +18 -0
- data/test/factory_builder_test.rb +146 -0
- data/test/factory_worker_test.rb +36 -0
- data/test/test_helper.rb +43 -0
- metadata +79 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2007-2008 Nathan Herald, David Lowenfels and Jonathan Barket
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
= Credits
|
2
|
+
|
3
|
+
Originally written by Nathan Herald @ Myobie.com
|
4
|
+
|
5
|
+
Feature enhancements by David Lowenfels @ InternautDesign.com
|
6
|
+
|
7
|
+
Minor improvements by Jonathan Barket
|
8
|
+
|
9
|
+
== Get It
|
10
|
+
|
11
|
+
Add it as a gem dependency
|
12
|
+
|
13
|
+
config.gem 'dfl-factories-and-workers', :lib => 'factories-and-workers', :source => 'http://gems.github.com'
|
14
|
+
|
15
|
+
Or install it as a gem manually
|
16
|
+
|
17
|
+
sudo gem install dfl-factories-and-workers
|
18
|
+
|
19
|
+
Or grab the source
|
20
|
+
|
21
|
+
git clone git://github.com/dfl/factories-and-workers.git
|
22
|
+
|
23
|
+
=The Factory
|
24
|
+
|
25
|
+
The Factory idea was inspired completely by Dan Manges' blog post "Fixin' Fixtures with Factory" @ http://www.dcmanges.com/blog/38
|
26
|
+
|
27
|
+
|
28
|
+
The Factory is a module that has three methods for each model: +build_model+, +create_model+ and +valid_model_attributes+.
|
29
|
+
( For semantic purposes, +default_model_attributes+ is also available as an alias to +valid_model_attributes+. )
|
30
|
+
|
31
|
+
These methods create objects in a valid state, with default attributes and associations. When using the +create_model+ and +build_model+ methods, you can pass in a hash to override any of the default attributes if needed.
|
32
|
+
Typically, you should create a file named 'factories.rb' in either the spec or test directory in your project to declare factory methods and default attributes.
|
33
|
+
It will be loaded during plugin initialization (see init.rb). You could also explicitly require it from test_helper.rb
|
34
|
+
|
35
|
+
A minimal factories.rb file:
|
36
|
+
|
37
|
+
include FactoriesAndWorkers::Factory # mixin to Object, which is especially useful for script/console work; season to taste.
|
38
|
+
|
39
|
+
factory :user, :name => "default name"
|
40
|
+
|
41
|
+
This would define methods named +build_user+, +create_user+, and +valid_user_attributes+ to be available in any Object in the project.
|
42
|
+
If you wanted to override the default valid attributes, you could call:
|
43
|
+
create_user( :name => "a different name" )
|
44
|
+
|
45
|
+
|
46
|
+
A more complicated example:
|
47
|
+
|
48
|
+
factory :role, :title => "role title"
|
49
|
+
|
50
|
+
factory :user, {
|
51
|
+
:first_name => "Joe",
|
52
|
+
:last_name => "Momma",
|
53
|
+
:password => "$UNIQ(7)", # the $UNIQ(n) magic variable interpolates to a unique string of length n
|
54
|
+
:login => "user_$COUNT", # the $COUNT magic variable interpolates to an incremental number, beginning with 1
|
55
|
+
:role => :belongs_to_model,
|
56
|
+
:created_at => lambda { Time.zone.now - 7 } # lazy evaluation: will be called on object instantiation rather than factory definition
|
57
|
+
} do |u| # code to be called in after_initialize hook
|
58
|
+
u.email = "#{u.first_name}.#{u.last_name}@example.com".downcase
|
59
|
+
end
|
60
|
+
|
61
|
+
factory :order, {
|
62
|
+
:quantity => 5,
|
63
|
+
:price => 500,
|
64
|
+
:user => :belongs_to_model
|
65
|
+
:number => lambda { increment! :foo } # increment a counter by arbitrary key
|
66
|
+
}
|
67
|
+
|
68
|
+
factory :special_order, {
|
69
|
+
:kind => 'Special'
|
70
|
+
}, :class => Order, :chain => lambda{ valid_order_attributes } # reverse merges with valid_order_attributes
|
71
|
+
|
72
|
+
|
73
|
+
A value of :belongs_to_model on an attribute adds logic to call +create_+ or +build_+, appropriately.
|
74
|
+
For example:
|
75
|
+
valid_user_attributes # assigns :role => build_role
|
76
|
+
build_user # assigns :role => build_role
|
77
|
+
|
78
|
+
create_user # assigns :role => create_role
|
79
|
+
valid_user_attributes(true) # assigns :role => create_role
|
80
|
+
In this way, models are not saved to the database unnecessarily.
|
81
|
+
|
82
|
+
Note that if you pass a foreign key attribute as a build or create override, the corresponding default object will not be constructed.
|
83
|
+
For example:
|
84
|
+
create_user( :role_id => 1 ) # will not call create_role.
|
85
|
+
|
86
|
+
|
87
|
+
Two helper methods are available to be used in lambda blocks:
|
88
|
+
* increment!( key ) -- increments a global counter keyed on a symbol or string
|
89
|
+
* uniq( length ) -- returns a random string
|
90
|
+
|
91
|
+
|
92
|
+
There is also a rake task to automagically generate template factories from your models.
|
93
|
+
rake factory:generate # will print templates for all AR models
|
94
|
+
rake factory:generate MODEL=user # will print template for user model
|
95
|
+
|
96
|
+
|
97
|
+
=The FactoryWorker
|
98
|
+
|
99
|
+
The FactoryWorker is a work in progress.
|
100
|
+
|
101
|
+
If you create a file named 'factory_workers.rb' in either your spec or test directory, you can define snippets of code that can be ran at anytime, anywhere in your tests (this may not be true in the future, I may limit where it can be run, iono).
|
102
|
+
|
103
|
+
A factory worker is defined as so:
|
104
|
+
|
105
|
+
factory_worker :name do
|
106
|
+
# any ruby code you want
|
107
|
+
end
|
108
|
+
|
109
|
+
Then, in your tests you can call 'worker :name' to run the ruby code contained in the worker.
|
110
|
+
|
111
|
+
It can be useful for populating the database with objects:
|
112
|
+
|
113
|
+
factory_worker :salable_and_non_salable_products do
|
114
|
+
create_variant( :sku => "1" )
|
115
|
+
create_variant( :sku => "2" )
|
116
|
+
create_variant( :sku => "3", :in_stock => false )
|
117
|
+
create_variant( :sku => "4", :in_stock => false )
|
118
|
+
end
|
119
|
+
|
120
|
+
The +create_variant+ method would provided by my factory setup, and creates a valid product with associated colors, sizes, and other options that I need. Now, I have 4 products in the database, 2 are in stock and 2 are not. So, in my test: find(:salable).length.should == 2
|
121
|
+
|
122
|
+
And it does.
|
123
|
+
|
124
|
+
Similar to rake task dependencies, you can chain workers together like this:
|
125
|
+
|
126
|
+
factory_worker :first do
|
127
|
+
puts "I am first"
|
128
|
+
end
|
129
|
+
|
130
|
+
factory_worker :second => :first do
|
131
|
+
puts "I am second"
|
132
|
+
end
|
133
|
+
|
134
|
+
factory_worker :third => :second do
|
135
|
+
puts "I am third"
|
136
|
+
end
|
137
|
+
|
138
|
+
If you call 'worker :third', it should output:
|
139
|
+
|
140
|
+
I am first
|
141
|
+
I am second
|
142
|
+
I am third
|
143
|
+
|
144
|
+
You can also chain workers like this:
|
145
|
+
factory_worker :several => [ :first, :second ]
|
146
|
+
|
147
|
+
and even add dependencies to the chain in further statements
|
148
|
+
factory_worker :several => [ :third ]
|
149
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the factories-and-workers plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the factorires-and-workers plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'factories-and-workers'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module FactoriesAndWorkers
|
4
|
+
|
5
|
+
module Factory
|
6
|
+
def self.included( base )
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def factory( kind, default_attrs, opts={}, &block )
|
12
|
+
FactoryBuilder.new( kind, default_attrs, opts, self, &block )
|
13
|
+
end
|
14
|
+
|
15
|
+
# creates a random hex string, converts it to hexatridecimal, and truncates to desired length (max 30)
|
16
|
+
def uniq len=10
|
17
|
+
Digest::SHA1.hexdigest("#{rand(1<<64)}/#{Time.now.to_f}/#{Process.pid}").to_i(16).to_s(36)[1..len]
|
18
|
+
end
|
19
|
+
|
20
|
+
@@factory_counter = Hash.new(0)
|
21
|
+
def increment! counter
|
22
|
+
@@factory_counter[ counter.to_s ] += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
@@factory_initializers = {}
|
26
|
+
def factory_initializers
|
27
|
+
@@factory_initializers
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# factory methods are defined as class methods; this delegation will allow them to also be called as instance methods
|
32
|
+
def method_missing method, *args, &block
|
33
|
+
if self.class.respond_to?( method )
|
34
|
+
self.class.send method, *args, &block
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class FactoryBuilder
|
43
|
+
def initialize( factory, default_attrs, opts, from_klass, &block )
|
44
|
+
raise ArgumentError, ":chain must be a lambda block!" if opts[:chain] && !opts[:chain].is_a?( Proc )
|
45
|
+
opts.reverse_merge!( :class => factory )
|
46
|
+
|
47
|
+
ar_klass = ActiveRecord.const_get( opts[:class].to_s.classify )
|
48
|
+
from_klass.factory_initializers[ factory ] = block if block_given?
|
49
|
+
|
50
|
+
# make the valid attributes method
|
51
|
+
valid_attrs_method = :"valid_#{factory}_attributes"
|
52
|
+
Factory::ClassMethods.send :define_method, valid_attrs_method do |*args|
|
53
|
+
action = args.first.is_a?( TrueClass ) ? :create : :build
|
54
|
+
attrs = default_attrs.symbolize_keys
|
55
|
+
attr_overrides = args.extract_options!
|
56
|
+
attrs.merge!( attr_overrides.symbolize_keys ) if attr_overrides
|
57
|
+
attrs.reverse_merge!( opts[:chain].call ) if opts[:chain]
|
58
|
+
attrs.each_pair do |key, value|
|
59
|
+
if attr_overrides.keys.include?(:"#{key}_id")
|
60
|
+
attrs.delete(key) # if :#{model}_id is overridden, then remove :#{model} and don't evaluate the lambda block
|
61
|
+
else
|
62
|
+
attrs[key] = case value
|
63
|
+
when Proc
|
64
|
+
value.call # evaluate lambda blocks
|
65
|
+
when :belongs_to_model
|
66
|
+
send( :"#{action}_#{key}" ) # create or build model dependencies, if none are found in the db
|
67
|
+
when String # interpolate magic variables
|
68
|
+
value.gsub( /\$UNIQ\((\d+)\)/ ){ from_klass.uniq( $1.to_i ) }.
|
69
|
+
gsub( '$COUNT', from_klass.increment!( :"#{ar_klass}_#{key}" ).to_s )
|
70
|
+
else
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# make the valid attribute method, which only fetches a single attribute
|
78
|
+
valid_attr_method = :"valid_#{factory}_attribute"
|
79
|
+
Factory::ClassMethods.send :define_method, valid_attr_method do |arg|
|
80
|
+
return unless arg.is_a?( Symbol )
|
81
|
+
base = default_attrs.dup
|
82
|
+
base.reverse_merge!( opts[:chain].call ) if opts[:chain]
|
83
|
+
returning base[ arg ] do |value|
|
84
|
+
value = value.call if value.is_a?( Proc ) # evaluate lambda if needed
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# alias default_*_attributes to valid_*_attributes, for semantic equivalency
|
89
|
+
Factory::ClassMethods.send :alias_method, valid_attrs_method.to_s.gsub('valid','default').to_sym, valid_attrs_method
|
90
|
+
Factory::ClassMethods.send :alias_method, valid_attr_method.to_s.gsub('valid','default').to_sym, valid_attr_method
|
91
|
+
|
92
|
+
|
93
|
+
after_initialize_block = from_klass.factory_initializers[ factory ]
|
94
|
+
|
95
|
+
# make the create method
|
96
|
+
Factory::ClassMethods.send :define_method, :"create_#{factory}" do |*args|
|
97
|
+
ar_klass.create!( self.send( valid_attrs_method, true, args.first ) ) do |obj|
|
98
|
+
after_initialize_block.call( obj ) if after_initialize_block
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# make the build method
|
103
|
+
Factory::ClassMethods.send :define_method, :"build_#{factory}" do |*args|
|
104
|
+
ar_klass.new( self.send( valid_attrs_method, false, args.first ) ) do |obj|
|
105
|
+
after_initialize_block.call( obj ) if after_initialize_block
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end #initialize
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Factory
|
2
|
+
|
3
|
+
IGNORED_COLUMNS = %w[ id created_at updated_at created_on updated_on ]
|
4
|
+
|
5
|
+
def self.generate_template arg
|
6
|
+
model = arg.classify.constantize
|
7
|
+
columns = {}
|
8
|
+
model.columns.each do |col|
|
9
|
+
key = col.name
|
10
|
+
if key =~ /^(.+)_id$/
|
11
|
+
columns[ $1.to_sym ] = :belongs_to_model
|
12
|
+
else
|
13
|
+
columns[ key.to_sym ] = { :type => col.type, :default => col.default }
|
14
|
+
end unless IGNORED_COLUMNS.include?( key )
|
15
|
+
end
|
16
|
+
|
17
|
+
template = "\nfactory :#{model.to_s.underscore}, {\n"
|
18
|
+
columns.each_pair do |name, val|
|
19
|
+
template += " :#{name} => #{ default_for( val ) },\n"
|
20
|
+
end
|
21
|
+
template += "}\n\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def self.default_for val
|
27
|
+
case val
|
28
|
+
when :belongs_to_model
|
29
|
+
return val.inspect
|
30
|
+
when Hash
|
31
|
+
# return val[:default] if val[:default] # not sure if this works?
|
32
|
+
case val[:type]
|
33
|
+
when :integer
|
34
|
+
123
|
35
|
+
when :string, :text
|
36
|
+
"SomeString".inspect
|
37
|
+
when :float
|
38
|
+
1.23
|
39
|
+
when :date
|
40
|
+
"lambda{ 7.days.from_now }"
|
41
|
+
when :datetime
|
42
|
+
"lambda{ 12.hours.from_now }"
|
43
|
+
when :boolean
|
44
|
+
val[:default] || false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module FactoriesAndWorkers
|
2
|
+
|
3
|
+
module Worker
|
4
|
+
def worker( worker_name )
|
5
|
+
FactoryWorker.find_and_work worker_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included( base )
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def factory_worker( worker, &block )
|
14
|
+
FactoryWorker.new( worker, &block )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class FactoryWorker
|
21
|
+
@@workers = HashWithIndifferentAccess.new
|
22
|
+
|
23
|
+
def initialize( worker, &block )
|
24
|
+
case worker
|
25
|
+
when Hash
|
26
|
+
@dependencies = [ *worker.values.first ]
|
27
|
+
@worker = worker.keys.first
|
28
|
+
when Symbol, String
|
29
|
+
@worker = worker
|
30
|
+
else
|
31
|
+
raise ArgumentError, "I don't know how to make a factory worker out of '#{worker.inspect}'"
|
32
|
+
end
|
33
|
+
|
34
|
+
@block = block if block_given?
|
35
|
+
@@workers[ @worker ] = self
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_and_work( worker_name )
|
39
|
+
raise ArgumentError, "There is no factory worker named '#{worker_name.to_s}' available" unless @@workers.include?(worker_name.to_s)
|
40
|
+
@@workers[ worker_name ].work
|
41
|
+
end
|
42
|
+
|
43
|
+
def work
|
44
|
+
@dependencies.each{ |w| self.class.find_and_work( w ) } if @dependencies
|
45
|
+
surface_errors{ @block.call( self ) } if @block
|
46
|
+
end
|
47
|
+
|
48
|
+
def surface_errors
|
49
|
+
yield
|
50
|
+
rescue Object => error
|
51
|
+
puts
|
52
|
+
puts "There was an error working the factory worker '#{@worker}':", error.inspect
|
53
|
+
puts
|
54
|
+
puts error.backtrace
|
55
|
+
puts
|
56
|
+
exit!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
desc "Generate factory templates from database schema; takes optional MODEL= argument"
|
2
|
+
|
3
|
+
namespace :factory do
|
4
|
+
|
5
|
+
task :generate do
|
6
|
+
require File.join( RAILS_ROOT, 'config', 'environment' )
|
7
|
+
|
8
|
+
if arg = ENV['model'] || ENV['MODEL']
|
9
|
+
puts Factory.generate_template( arg )
|
10
|
+
else
|
11
|
+
all_models = Dir.glob( File.join( RAILS_ROOT, 'app', 'models', '*.rb') ).map{|path| path[/.+\/(.+).rb/, 1] }
|
12
|
+
all_models.select{|m| m.classify.constantize < ActiveRecord::Base}.each do |model|
|
13
|
+
puts Factory.generate_template( model )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/rails/init.rb
ADDED
data/test/db/schema.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
|
3
|
+
create_table :monkeys do |t|
|
4
|
+
t.column :name, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table :pirates do |t|
|
8
|
+
t.column :catchphrase, :string
|
9
|
+
t.column :monkey_id, :integer
|
10
|
+
t.column :created_on, :datetime
|
11
|
+
t.column :updated_on, :datetime
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :users do |t|
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/factories.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
include FactoriesAndWorkers::Factory
|
2
|
+
|
3
|
+
factory :monkey, {
|
4
|
+
:name => "George",
|
5
|
+
:unique => "$UNIQ(10)",
|
6
|
+
:counter => "$COUNT",
|
7
|
+
:number => lambda{ increment! :foo }
|
8
|
+
}
|
9
|
+
|
10
|
+
factory :pirate, {
|
11
|
+
:catchphrase => "Ahhrrrr, Matey!",
|
12
|
+
:monkey => :belongs_to_model,
|
13
|
+
:created_on => lambda{ 1.day.ago }
|
14
|
+
}
|
15
|
+
|
16
|
+
factory :ninja_pirate, {
|
17
|
+
:catchphrase => "(silent)"
|
18
|
+
}, :class => Pirate, :chain => lambda{ valid_pirate_attributes( :monkey => nil ) }
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/test_helper")
|
2
|
+
|
3
|
+
class FactoryBuilderTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def metaclass; class << self; self; end; end
|
6
|
+
|
7
|
+
def test_unknown_factory_raises_error
|
8
|
+
e = assert_raises(NameError) do
|
9
|
+
metaclass.class_eval do
|
10
|
+
factory :foo, {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
assert_equal "uninitialized constant ActiveRecord::Foo", e.message
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_factory_with_initializer
|
17
|
+
metaclass.class_eval do
|
18
|
+
factory :user, {
|
19
|
+
:first_name => "Joe",
|
20
|
+
:last_name => "Blow"
|
21
|
+
} do |u| u.email = "#{u.first_name}.#{u.last_name}@example.com".downcase end
|
22
|
+
end
|
23
|
+
assert_equal "joe.doe@example.com", build_user( :last_name => 'Doe' ).email
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_build_monkey_does_not_save
|
27
|
+
assert_difference "Monkey.count", 0 do
|
28
|
+
build_monkey
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_create_monkey_does_save
|
33
|
+
assert_difference "Monkey.count", 1 do
|
34
|
+
create_monkey
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_valid_monkey_attributes
|
39
|
+
assert_equal( {:name => "George"}, remove_variability( valid_monkey_attributes ) )
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_valid_pirate_attributes_without_create_parents
|
43
|
+
assert_difference "Monkey.count", 0 do
|
44
|
+
valid_pirate_attributes( false )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_valid_pirate_attributes_with_create_parents
|
49
|
+
assert_difference "Monkey.count", 1 do
|
50
|
+
valid_pirate_attributes( true )
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_valid_attribute__does_not_evaluate_other_attributes
|
55
|
+
assert_difference "Monkey.count", 0 do
|
56
|
+
assert_equal "Ahhrrrr, Matey!", valid_pirate_attribute(:catchphrase)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_build_pirate
|
61
|
+
assert_difference "Pirate.count", 0 do
|
62
|
+
assert_difference "Monkey.count", 0 do
|
63
|
+
build_pirate
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_create_pirate_evaluates_lambda
|
69
|
+
assert_difference "Pirate.count", 1 do
|
70
|
+
assert_difference "Monkey.count", 1 do
|
71
|
+
@pirate = create_pirate
|
72
|
+
end
|
73
|
+
end
|
74
|
+
assert_equal @pirate.created_on.to_s, 1.day.ago.to_s
|
75
|
+
sleep 1
|
76
|
+
assert_not_equal create_pirate.updated_on.to_s, @pirate.updated_on.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_ninja_pirate_is_silent_and_has_no_monkey
|
80
|
+
assert_difference "Pirate.count", 1 do
|
81
|
+
assert_difference "Monkey.count", 0 do
|
82
|
+
@pirate = create_ninja_pirate
|
83
|
+
end
|
84
|
+
end
|
85
|
+
assert_equal "(silent)", @pirate.catchphrase
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_overridden_attributes
|
89
|
+
@phil = create_monkey( :name => "Phil" )
|
90
|
+
assert_not_equal valid_monkey_attribute(:name), @phil.name, "default monkey name should be overridden"
|
91
|
+
|
92
|
+
assert_difference "Pirate.count", 0 do
|
93
|
+
assert_difference "Monkey.count", 0 do
|
94
|
+
@pirate = build_pirate( :monkey => @phil, :catchphrase => "chunky bacon!" )
|
95
|
+
end
|
96
|
+
end
|
97
|
+
assert_not_equal valid_pirate_attribute(:catchphrase), @pirate.catchphrase, "default pirate catchphrase should be overridden"
|
98
|
+
assert_equal "Phil", @pirate.monkey.name
|
99
|
+
assert_equal "George", build_pirate.monkey.name
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_overridden_attribute_id_will_not_evaluate_lambda_for_model_creation
|
103
|
+
assert_difference "Monkey.count", 0 do
|
104
|
+
@pirate = build_pirate( :monkey_id => 1 )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_uniq_interpolation
|
109
|
+
a = build_monkey.unique
|
110
|
+
assert_equal 10, a.size
|
111
|
+
|
112
|
+
b = build_monkey.unique
|
113
|
+
assert_equal 10, b.size
|
114
|
+
|
115
|
+
assert_not_equal a, b
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_count_interpolation_not_interfered_with_by_external_counter
|
119
|
+
assert_difference "build_monkey.counter.to_i", 1 do
|
120
|
+
increment!(:foo)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_increment!
|
125
|
+
a = build_monkey.number
|
126
|
+
assert_equal a+1, build_monkey.number
|
127
|
+
assert_equal a+2, increment!(:foo)
|
128
|
+
assert_equal a+3, build_monkey.number
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_default_attributes_alias
|
132
|
+
hash1 = remove_variability( valid_monkey_attributes )
|
133
|
+
hash2 = remove_variability( default_monkey_attributes )
|
134
|
+
assert_equal hash1, hash2
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
|
139
|
+
def remove_variability hash
|
140
|
+
variable_attributes = [:unique, :counter, :number]
|
141
|
+
returning hash do
|
142
|
+
variable_attributes.each{ |a| hash.delete(a) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/test_helper")
|
2
|
+
|
3
|
+
class FactoryWorkerTest < Test::Unit::TestCase
|
4
|
+
include FactoriesAndWorkers::Worker
|
5
|
+
|
6
|
+
factory_worker :one do
|
7
|
+
@@foo = "one!"
|
8
|
+
end
|
9
|
+
|
10
|
+
factory_worker :two => :one do
|
11
|
+
@@foo += " two!"
|
12
|
+
end
|
13
|
+
|
14
|
+
factory_worker :three do
|
15
|
+
@@foo += " three!"
|
16
|
+
end
|
17
|
+
|
18
|
+
factory_worker :count => [ :one, :two ]
|
19
|
+
factory_worker :count => [ :three ]
|
20
|
+
|
21
|
+
def test_worker
|
22
|
+
worker :one
|
23
|
+
assert_equal "one!", @@foo
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_worker_with_dependencies
|
27
|
+
worker :two
|
28
|
+
assert_equal "one! two!", @@foo
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_worker_with_dependencies_2
|
32
|
+
worker :count
|
33
|
+
assert_equal "one! two! three!", @@foo
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
$:.reject!{ |e| e.include? 'TextMate' }
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
plugin_root = File.join(File.dirname(__FILE__), "..")
|
6
|
+
app_root = File.join(plugin_root, "..", "..", "..")
|
7
|
+
|
8
|
+
ENV["RAILS_ENV"] = "test"
|
9
|
+
|
10
|
+
# pull in some basic Rails bits, but not our whole app, yo
|
11
|
+
require File.expand_path(File.join(app_root, "config", "boot"))
|
12
|
+
$:.unshift(File.expand_path(File.join(plugin_root, "lib")))
|
13
|
+
|
14
|
+
# initialize the plugin
|
15
|
+
require plugin_root + "/init"
|
16
|
+
|
17
|
+
# pull in just what we need from Rails
|
18
|
+
%w( active_record active_support ).each { |f| require f }
|
19
|
+
|
20
|
+
|
21
|
+
# we'll use a totally standalone db for our testing
|
22
|
+
options = { :adapter => "sqlite3", :timeout => 500, :database => ":memory:" }
|
23
|
+
|
24
|
+
# establish the connection manually
|
25
|
+
ActiveRecord::Base.establish_connection(options)
|
26
|
+
|
27
|
+
# ...and provide some connection data so that fixtures will work
|
28
|
+
ActiveRecord::Base.configurations = { :sqlite3 => options }
|
29
|
+
|
30
|
+
# kickstart
|
31
|
+
ActiveRecord::Base.connection
|
32
|
+
|
33
|
+
# create our test schema
|
34
|
+
ActiveRecord::Migration.verbose = false
|
35
|
+
require File.expand_path(File.join(plugin_root, "test", "db", "schema"))
|
36
|
+
|
37
|
+
# pull in our test AR::B models
|
38
|
+
models = Dir.glob(File.join(plugin_root, "test", "app", "models", "*.rb"))
|
39
|
+
models.each { |m| require File.expand_path(m) }
|
40
|
+
|
41
|
+
|
42
|
+
# load the factories
|
43
|
+
require File.join( plugin_root, 'test', 'factories' )
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dfl-factories-and-workers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Herald
|
8
|
+
- David Lowenfels
|
9
|
+
- Jonathan Barket
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2008-10-14 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description: Fixtures replacement
|
19
|
+
email: david@internautdesign.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files:
|
25
|
+
- README.rdoc
|
26
|
+
files:
|
27
|
+
- README.rdoc
|
28
|
+
- CHANGELOG
|
29
|
+
- LICENSE
|
30
|
+
- init.rb
|
31
|
+
- Rakefile
|
32
|
+
- lib/factories-and-workers
|
33
|
+
- lib/factories-and-workers.rb
|
34
|
+
- lib/factories-and-workers/factory_builder.rb
|
35
|
+
- lib/factories-and-workers/factory_generator.rb
|
36
|
+
- lib/factories-and-workers/factory_worker.rb
|
37
|
+
- lib/tasks
|
38
|
+
- lib/tasks/generate_factories.rake
|
39
|
+
- rails
|
40
|
+
- rails/init.rb
|
41
|
+
- test/app
|
42
|
+
- test/app/models
|
43
|
+
- test/app/models/monkey.rb
|
44
|
+
- test/app/models/pirate.rb
|
45
|
+
- test/app/models/user.rb
|
46
|
+
- test/db
|
47
|
+
- test/db/schema.rb
|
48
|
+
- test/factories.rb
|
49
|
+
- test/factory_builder_test.rb
|
50
|
+
- test/factory_worker_test.rb
|
51
|
+
- test/test_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://blog.internautdesign.com/2008/6/4/factories-and-workers-plugin
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.2.0
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: Fixtures replacement
|
78
|
+
test_files: []
|
79
|
+
|