modelfactory 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/License.txt +20 -0
- data/Manifest.txt +15 -0
- data/README.txt +91 -0
- data/Rakefile +20 -0
- data/lib/fixture_converter.rb +97 -0
- data/lib/model_factory.rb +111 -0
- data/lib/model_factory/version.rb +9 -0
- data/script/destroy +14 -0
- data/script/fixtures2factories +18 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/test/model_factory_test.rb +139 -0
- data/test/test_helper.rb +12 -0
- metadata +81 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Zack Hobson and Justin Balthrop, Geni.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
lib/fixture_converter.rb
|
7
|
+
lib/model_factory.rb
|
8
|
+
lib/model_factory/version.rb
|
9
|
+
script/destroy
|
10
|
+
script/fixtures2factories
|
11
|
+
script/generate
|
12
|
+
script/txt2html
|
13
|
+
setup.rb
|
14
|
+
test/model_factory_test.rb
|
15
|
+
test/test_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
= ModelFactory
|
2
|
+
|
3
|
+
ModelFactory is a module designed to replace the use of fixtures for testing
|
4
|
+
Rails applications.
|
5
|
+
|
6
|
+
The idea is that instead of keeping your test data in a nearly opaque fixture
|
7
|
+
file, you generate data in the test itself using a custom factory API designed
|
8
|
+
for your test environment.
|
9
|
+
|
10
|
+
By creating a new module just for your test factory API you make it easier
|
11
|
+
to spot factory calls in your tests and keep your factory code out of your
|
12
|
+
test code. ModelFactory adds some useful facilities for generating optional
|
13
|
+
defaults for commonly instantiated types. It also fakes up id generation in
|
14
|
+
the ActiveRecord models created with new, to assist in unit testing without
|
15
|
+
the database.
|
16
|
+
|
17
|
+
=== A Note About Defaults
|
18
|
+
|
19
|
+
When writing tests that use factory-generated objects, it's important never
|
20
|
+
to depend on default values in your test assertions. If you depend on defaults
|
21
|
+
in your tests they become more fragile and the intention is harder to discern.
|
22
|
+
|
23
|
+
If you find yourself repeating the same initialization to avoid using defaults,
|
24
|
+
consider whether it would be appropriate to add a custom toplevel method to
|
25
|
+
your factory module that includes this initialization.
|
26
|
+
|
27
|
+
=== A Note About ID Generation
|
28
|
+
|
29
|
+
Since basic ID generation is done when you instantiate objects using
|
30
|
+
Factory.new_<type> it is recommended not to mix such objects with those
|
31
|
+
created using Factory.create_<type>. Use the former in unit tests and
|
32
|
+
use the latter in functional tests.
|
33
|
+
|
34
|
+
== Using ModelFactory
|
35
|
+
|
36
|
+
Put something like this in your test helper:
|
37
|
+
|
38
|
+
require 'model_factory'
|
39
|
+
|
40
|
+
module Factory
|
41
|
+
extend ModelFactory
|
42
|
+
|
43
|
+
# a default block accepts a class and a hash of default values
|
44
|
+
default Color, {
|
45
|
+
:name => 'chartreuse'
|
46
|
+
}
|
47
|
+
|
48
|
+
default User, {
|
49
|
+
:first_name => 'Harry',
|
50
|
+
:last_name => 'Manchester',
|
51
|
+
:favorite_color => default_color
|
52
|
+
}
|
53
|
+
|
54
|
+
# Add class methods to create whatever kind of objects you need for your tests
|
55
|
+
def self.new_user_with_colorblindness
|
56
|
+
new_user { :favorite_color => nil }
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
Then in your tests you use Factory methods to instantiate your test objects:
|
62
|
+
|
63
|
+
# For most functional tests you can use create.
|
64
|
+
def test_something
|
65
|
+
user = Factory.create_user
|
66
|
+
user.friends << Factory.create_user(:first_name => 'Frank')
|
67
|
+
assert user.likes_frank?
|
68
|
+
end
|
69
|
+
|
70
|
+
# For unit tests you use new.
|
71
|
+
def test_something_else
|
72
|
+
user = Factory.new_user(:favorite_color => Factory.new_color(:name => 'blue'))
|
73
|
+
assert user.likes_blue?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Assertions should not depend on default data, but it can be useful to create
|
77
|
+
# factory methods that build objects with specific traits.
|
78
|
+
def test_yet_something_else
|
79
|
+
user = Factory.new_user_with_colorblindness
|
80
|
+
assert !user.likes_blue?
|
81
|
+
end
|
82
|
+
|
83
|
+
== Installing ModelFactory
|
84
|
+
|
85
|
+
sudo gem install modelfactory
|
86
|
+
|
87
|
+
== License
|
88
|
+
|
89
|
+
Copyright (c) 2008 Justin Balthrop and Zack Hobson
|
90
|
+
Published under The MIT License, see License.txt
|
91
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
4
|
+
require 'model_factory/version'
|
5
|
+
|
6
|
+
Hoe.new('ModelFactory', ModelFactory::VERSION::STRING) do |p|
|
7
|
+
p.name = "modelfactory"
|
8
|
+
p.author = ['Justin Balthrop', 'Zack Hobson']
|
9
|
+
p.description = "A replacement for fixtures."
|
10
|
+
p.email = "justin@geni.com"
|
11
|
+
p.summary = "A replacement for fixtures."
|
12
|
+
p.url = "http://modelfactory.rubyforge.org/"
|
13
|
+
p.rubyforge_name = 'modelfactory'
|
14
|
+
p.remote_rdoc_dir = '' # Release to root
|
15
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
16
|
+
p.test_globs = ["test/**/*_test.rb"]
|
17
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
|
18
|
+
end
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class FixtureConverter
|
2
|
+
def initialize(opts = {})
|
3
|
+
@body = []
|
4
|
+
@header = []
|
5
|
+
@indent_depth = opts[:indent_depth] || 0
|
6
|
+
@output_style = opts[:output_style]
|
7
|
+
end
|
8
|
+
|
9
|
+
def powder?
|
10
|
+
@output_style == :clay
|
11
|
+
end
|
12
|
+
|
13
|
+
def convert_fixture(path)
|
14
|
+
fixture = YAML.load_file(path)
|
15
|
+
return if not fixture
|
16
|
+
|
17
|
+
plural_model_name = path.basename.to_s.split('.').first
|
18
|
+
model_name = plural_model_name.singularize
|
19
|
+
header do
|
20
|
+
"#{plural_model_name} = {}"
|
21
|
+
end
|
22
|
+
|
23
|
+
body 'before(:all) do' if powder?
|
24
|
+
indent(powder?) do
|
25
|
+
body "# Setup #{plural_model_name}"
|
26
|
+
body "# Generated from fixture in #{path.dirname.basename}/#{path.basename}"
|
27
|
+
body '#'
|
28
|
+
body ''
|
29
|
+
fixture.each do |name, record|
|
30
|
+
max_length = record.keys.collect {|key| key.length}.max
|
31
|
+
|
32
|
+
body "#{plural_model_name}[:#{name}] = Factory.create_#{model_name}("
|
33
|
+
indent do
|
34
|
+
body do
|
35
|
+
record.collect do |key,value|
|
36
|
+
value = "\"#{value}\"" if value.kind_of?(String)
|
37
|
+
key = key.ljust(max_length)
|
38
|
+
":#{key} => #{value}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
body ')'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
body 'end' if powder?
|
46
|
+
body ''
|
47
|
+
end
|
48
|
+
|
49
|
+
def convert_scenario(path)
|
50
|
+
path.each_entry do |file|
|
51
|
+
next if file.extname != '.yml'
|
52
|
+
next if file.basename.to_s =~ /relationships/
|
53
|
+
convert_fixture( path + file )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def out
|
58
|
+
puts @header.join("\n")
|
59
|
+
puts "\n"
|
60
|
+
puts @body.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
INDENT = ' '
|
65
|
+
|
66
|
+
def indent(enabled = true)
|
67
|
+
@indent_depth += 1 if enabled
|
68
|
+
yield
|
69
|
+
@indent_depth -= 1 if enabled
|
70
|
+
end
|
71
|
+
|
72
|
+
def body(*lines)
|
73
|
+
lines = yield if block_given?
|
74
|
+
lines = [lines].flatten
|
75
|
+
|
76
|
+
lines.each do |line|
|
77
|
+
@body << indent_line(line, @indent_depth)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def header(*lines)
|
82
|
+
lines = yield if block_given?
|
83
|
+
lines = [lines].flatten
|
84
|
+
|
85
|
+
lines.each do |line|
|
86
|
+
@header << line
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def indent_line(line, indent_depth)
|
91
|
+
ws = ''
|
92
|
+
indent_depth.times do
|
93
|
+
ws += INDENT
|
94
|
+
end
|
95
|
+
ws + line
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module ModelFactory
|
4
|
+
def next_local_id # :nodoc:
|
5
|
+
@max_id ||= 0
|
6
|
+
return @max_id += 1
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
# When specifying defaults, you should only provide only enough data that
|
11
|
+
# the created instance is valid. If you want to include another factory
|
12
|
+
# object as a dependency use the special method default_* instead of
|
13
|
+
# create_* or new_*.
|
14
|
+
#
|
15
|
+
def default(class_type, defaults={})
|
16
|
+
class_name = class_type.name.demodulize.underscore
|
17
|
+
|
18
|
+
(class << self; self; end).module_eval do
|
19
|
+
define_method "create_#{class_name}" do |*args|
|
20
|
+
attributes = args.first || {}
|
21
|
+
create_instance(class_type, attributes, defaults)
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method "new_#{class_name}" do |*args|
|
25
|
+
attributes = args.first || {}
|
26
|
+
new_instance(class_type, attributes, defaults)
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method "default_#{class_name}" do |*args|
|
30
|
+
attributes = args.first || {}
|
31
|
+
default_closure(class_type, attributes, defaults)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_instance(class_type, attributes, defaults = {}) # :nodoc:
|
37
|
+
attributes = instantiate_defaults(:create, defaults.merge(attributes))
|
38
|
+
instance = class_type.create!(attributes)
|
39
|
+
if update_protected_attributes(instance, attributes)
|
40
|
+
instance.save
|
41
|
+
end
|
42
|
+
instance
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_instance(class_type, attributes, defaults = {}) # :nodoc:
|
46
|
+
attributes = instantiate_defaults(:new, defaults.merge(attributes))
|
47
|
+
instance = class_type.new(attributes)
|
48
|
+
instance.id = next_local_id
|
49
|
+
update_protected_attributes(instance, attributes)
|
50
|
+
instance
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_closure(class_type, attributes, defaults = {}) # :nodoc:
|
54
|
+
lambda do |create_or_new|
|
55
|
+
case create_or_new
|
56
|
+
when :new : new_instance(class_type, attributes, defaults)
|
57
|
+
when :create : create_instance(class_type, attributes, defaults)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def instantiate_defaults(create_or_new, attributes) # :nodoc:
|
63
|
+
attributes.each do |key, value|
|
64
|
+
if value.is_a?(Proc)
|
65
|
+
attributes[key] = value.arity == 0 ? value.call : value.call(create_or_new)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
attributes
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_protected_attributes(instance, attributes) # :nodoc:
|
72
|
+
modified = false
|
73
|
+
protected_attrs = instance.class.protected_attributes
|
74
|
+
protected_attrs = protected_attrs.to_set if protected_attrs
|
75
|
+
accessible_attrs = instance.class.accessible_attributes
|
76
|
+
accessible_attrs = accessible_attrs.to_set if accessible_attrs
|
77
|
+
|
78
|
+
if protected_attrs or accessible_attrs
|
79
|
+
attributes.each do |key, value|
|
80
|
+
# Support symbols and strings.
|
81
|
+
[key, key.to_s].each do |attr|
|
82
|
+
next if protected_attrs and not protected_attrs.include?(attr)
|
83
|
+
next if accessible_attrs and accessible_attrs.include?(attr)
|
84
|
+
end
|
85
|
+
modified = true
|
86
|
+
instance.send("#{key}=", value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return modified
|
90
|
+
end
|
91
|
+
|
92
|
+
# Any class methods of the form "new_some_type(attrs)" or "create_some_type(attrs)" will be converted to
|
93
|
+
# "SomeType.new(attrs)" and "SomeType.create!(attrs)" respectively.
|
94
|
+
# These basically function as though you'd used the 'default' directive with empty defaults.
|
95
|
+
def method_missing(missing_method, attributes = {})
|
96
|
+
if missing_method.to_s.match(/^(new|create|default)_([a-z][\w_]+)$/)
|
97
|
+
method, class_name = $1, $2
|
98
|
+
class_type = class_name.camelize.constantize
|
99
|
+
case method
|
100
|
+
when 'create'
|
101
|
+
create_instance(class_type, attributes)
|
102
|
+
when 'new'
|
103
|
+
new_instance(class_type, attributes)
|
104
|
+
when 'default'
|
105
|
+
default_closure(class_type, attributes)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
raise NoMethodError, "no such method '#{missing_method}'"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
4
|
+
require 'pp'
|
5
|
+
require 'pathname'
|
6
|
+
require 'yaml'
|
7
|
+
require File.dirname(__FILE__) + '/../lib/fixture_converter'
|
8
|
+
|
9
|
+
path = Pathname.new(ARGV[0])
|
10
|
+
|
11
|
+
fc = FixtureConverter.new
|
12
|
+
if path.file?
|
13
|
+
fc.convert_fixture(path)
|
14
|
+
else
|
15
|
+
fc.convert_scenario(path)
|
16
|
+
end
|
17
|
+
fc.out
|
18
|
+
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/script/txt2html
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'newgem'
|
6
|
+
rescue LoadError
|
7
|
+
puts "\n\nGenerating the website requires the newgem RubyGem"
|
8
|
+
puts "Install: gem install newgem\n\n"
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
require 'redcloth'
|
12
|
+
require 'syntax/convertors/html'
|
13
|
+
require 'erb'
|
14
|
+
require File.dirname(__FILE__) + '/../lib/model_factory/version.rb'
|
15
|
+
|
16
|
+
version = ModelFactory::VERSION::STRING
|
17
|
+
download = 'http://rubyforge.org/projects/model_factory'
|
18
|
+
|
19
|
+
class Fixnum
|
20
|
+
def ordinal
|
21
|
+
# teens
|
22
|
+
return 'th' if (10..19).include?(self % 100)
|
23
|
+
# others
|
24
|
+
case self % 10
|
25
|
+
when 1: return 'st'
|
26
|
+
when 2: return 'nd'
|
27
|
+
when 3: return 'rd'
|
28
|
+
else return 'th'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Time
|
34
|
+
def pretty
|
35
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def convert_syntax(syntax, source)
|
40
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
41
|
+
end
|
42
|
+
|
43
|
+
if ARGV.length >= 1
|
44
|
+
src, template = ARGV
|
45
|
+
template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
|
46
|
+
|
47
|
+
else
|
48
|
+
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
49
|
+
exit!
|
50
|
+
end
|
51
|
+
|
52
|
+
template = ERB.new(File.open(template).read)
|
53
|
+
|
54
|
+
title = nil
|
55
|
+
body = nil
|
56
|
+
File.open(src) do |fsrc|
|
57
|
+
title_text = fsrc.readline
|
58
|
+
body_text = fsrc.read
|
59
|
+
syntax_items = []
|
60
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
61
|
+
ident = syntax_items.length
|
62
|
+
element, syntax, source = $1, $2, $3
|
63
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
64
|
+
"syntax-temp-#{ident}"
|
65
|
+
}
|
66
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
67
|
+
body = RedCloth.new(body_text).to_html
|
68
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
69
|
+
end
|
70
|
+
stat = File.stat(src)
|
71
|
+
created = stat.ctime
|
72
|
+
modified = stat.mtime
|
73
|
+
|
74
|
+
$stdout << template.result(binding)
|