modelfactory 0.7.0
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/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)
|