aub-machine 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,158 @@
1
+ h1. machine
2
+
3
+ Machine is a factory tool designed for a fixture replacement in Rails applications. It borrows a few concepts
4
+ from "factory_girl":http://github.com/thoughtbot/factory_girl/tree/master but applies attributes to objects
5
+ differently and has a different concept for associations.
6
+
7
+ Written by "Aubrey Holland":mailto:aubreyholland@gmail.com.
8
+
9
+ h2. download
10
+
11
+ Github: "Page":http://github.com/aub/machine/tree/master
12
+
13
+ Gem: <pre>gem install aub-machine --source http://gems.github.com</pre>
14
+
15
+ Note: if you install using the gem from Github, you'll need this
16
+ in your environment.rb if you want to use Rails 2.1's dependency manager:
17
+
18
+ config.gem "aub-machine",
19
+ :lib => "machine",
20
+ :source => "http://gems.github.com"
21
+
22
+ h3. define a machine
23
+
24
+ <pre><code>
25
+ Machine.define :car do |car, machine|
26
+ car.make = 'Ford'
27
+ car.model = 'Taurus'
28
+ end
29
+ </code></pre>
30
+
31
+ This defines a factory for building Car objects. The block is yielded an instance of the Car class
32
+ along with a machine object that can be used for building associated objects. Optionally, a class
33
+ name can be provided in cases where the name of the machine is not the same as that of the class.
34
+
35
+ <pre><code>
36
+ Machine.define :auto, :class => Car do |auto, machine|
37
+ ...
38
+ end
39
+ </code></pre>
40
+
41
+ Machines can also be extended to any number of levels. When an extended machine is executed, the
42
+ tree will be applied starting from the bottom. This is useful in cases where one machine is a
43
+ specialization of another one.
44
+
45
+ <pre><code>
46
+ Machine.define :car do |car, machine|
47
+ car.make = 'Ford'
48
+ car.type = 'Car'
49
+ end
50
+
51
+ Machine.define :station_wagon, :class => Car, :extends => :car do |car, machine|
52
+ car.type = 'Station Wagon'
53
+ end
54
+ </code></pre>
55
+
56
+ In addition, groups of machines can be defined that are built from a set of base attributes.
57
+ This is useful for namespacing similar types.
58
+
59
+ <pre><code>
60
+ Machine.define_group :user do |group|
61
+ group.base do |user, machine|
62
+ user.password = 'password'
63
+ user.password_confirmation = 'password'
64
+ user.status = 'active'
65
+ user.permissions = 'none'
66
+ end
67
+
68
+ group.define :super_user do |user, machine|
69
+ user.permissions = 'super'
70
+ end
71
+ end
72
+ </code></pre>
73
+
74
+ This defines two machines, user and super_user, where super_user has all of the attributes of
75
+ user but with a different permissions value.
76
+
77
+ h3. use a machine
78
+
79
+ Machines can be applied using one of three methods:
80
+
81
+ <pre><code>
82
+ Machine(:car) # creates an unsaved object using the machine 'car'.
83
+ Machine.build(:car) # same as above.
84
+ Machine.build!(:car) # creates an object using the 'car' machine and saves it.
85
+ </code></pre>
86
+
87
+ With each of these methods, a hash of replacement attributes can be passed, and those
88
+ attributes will be used in place of the default ones defined in the machine.
89
+
90
+ <pre><code>
91
+ Machine(:car, :make => 'Ferrari')
92
+ </code></pre>
93
+
94
+ h3. associations
95
+
96
+ Associations are filled using the machine object that is yielded in the block of the machine
97
+ definition. Instances of other machines can be created by calling the object with the name of
98
+ the machine.
99
+
100
+ <pre><code>
101
+ Machine.define :garage do |garage, machine|
102
+ garage.car = machine.car
103
+ end
104
+ </code></pre>
105
+
106
+ Replacement attributes can be passed to the machine in order to specialize the instance
107
+ for for a given association.
108
+
109
+ <pre><code>
110
+ Machine.define :garage do |garage, machine|
111
+ garage.cars = [machine.car(:model => 'Thunderbird'), machine.car(:model => 'Mustang')]
112
+ end
113
+ </code></pre>
114
+
115
+ h3. sequences
116
+
117
+ In cases where an attribute is required to be unique, sequences can be used. A sequence is
118
+ defined as a block that takes as its argument a unique integer and returns a result.
119
+
120
+ <pre><code>
121
+ Machine.sequence :vin do |n|
122
+ "abc123-#{n}"
123
+ end
124
+ </code></pre>
125
+
126
+ Sequences can then be used in machine definitions to set attribute values.
127
+
128
+ <pre><code>
129
+ Machine.define :car do |car, machine|
130
+ car.vin = machine.next(:vin)
131
+ car.make = 'Honda'
132
+ end
133
+ </code></pre>
134
+
135
+ h1. license
136
+
137
+ (The MIT License)
138
+
139
+ Copyright (c) 2008 Aubrey Holland and patch
140
+
141
+ Permission is hereby granted, free of charge, to any person obtaining
142
+ a copy of this software and associated documentation files (the
143
+ 'Software'), to deal in the Software without restriction, including
144
+ without limitation the rights to use, copy, modify, merge, publish,
145
+ distribute, sublicense, and/or sell copies of the Software, and to
146
+ permit persons to whom the Software is furnished to do so, subject to
147
+ the following conditions:
148
+
149
+ The above copyright notice and this permission notice shall be
150
+ included in all copies or substantial portions of the Software.
151
+
152
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
153
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
154
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
155
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
156
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
157
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
158
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'date'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the machine.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the machine.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Machine'
22
+ rdoc.options << '--line-numbers' << '--inline-source' << "--main" << "README.textile"
23
+ rdoc.rdoc_files.include('README.textile')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ spec = Gem::Specification.new do |s|
28
+ s.name = %q{machine}
29
+ s.version = "1.0.0"
30
+ s.summary = %q{machine defines a factory system for creating model objects to replace fixtures in Ruby apps.}
31
+ s.description = %q{machine defines a factory system for creating model objects to replace fixtures in Ruby apps.}
32
+
33
+ s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'test/**/*.rb']
34
+ s.require_path = 'lib'
35
+ s.test_files = Dir[*['test/**/*_test.rb']]
36
+
37
+ s.has_rdoc = true
38
+ s.extra_rdoc_files = ["README.textile"]
39
+ s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.textile"]
40
+
41
+ s.authors = ["Aubrey Holland"]
42
+ s.email = %q{aubrey@patch.com}
43
+
44
+ s.platform = Gem::Platform::RUBY
45
+ s.add_dependency(%q<activesupport>, [">= 1.0"])
46
+ end
47
+
48
+ Rake::GemPackageTask.new spec do |pkg|
49
+ pkg.need_tar = true
50
+ pkg.need_zip = true
51
+ end
52
+
53
+ desc "Clean files generated by rake tasks"
54
+ task :clobber => [:clobber_rdoc, :clobber_package]
55
+
56
+ desc "Generate a gemspec file"
57
+ task :gemspec do
58
+ File.open("#{spec.name}.gemspec", 'w') do |f|
59
+ f.write spec.to_ruby
60
+ end
61
+ end
data/TODO.textile ADDED
File without changes
@@ -0,0 +1,16 @@
1
+ class AssociationHelper
2
+
3
+ def initialize #:nodoc
4
+
5
+ end
6
+
7
+ def next(sequence) #:nodoc
8
+ Machine.next(sequence)
9
+ end
10
+
11
+ def method_missing(name, *args) #:nodoc
12
+ if Machine.machines.has_key?(name)
13
+ Machine.machines[name].build(*args)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,226 @@
1
+ class MachineNotFoundError < StandardError; end
2
+
3
+ class Machine
4
+
5
+ cattr_accessor :machines #:nodoc:
6
+ self.machines = {}
7
+
8
+ cattr_accessor :sequences #:nodoc:
9
+ self.sequences = {}
10
+
11
+ # An Array of strings specifying locations that should be searched for
12
+ # machine definitions. By default, machine will attempt to require
13
+ # "machines," "test/machines," and "spec/machines." Only the first
14
+ # existing file will be loaded.
15
+ cattr_accessor :definition_file_paths
16
+ self.definition_file_paths = %w(machines test/machines spec/machines)
17
+
18
+ def self.find_definitions #:nodoc:
19
+ definition_file_paths.each do |file_path|
20
+ begin
21
+ require(file_path)
22
+ break
23
+ rescue LoadError
24
+ end
25
+ end
26
+ end
27
+
28
+ # Defines a new machine that sets up the default attributes for new objects.
29
+ #
30
+ # Arguments:
31
+ # name: (Symbol)
32
+ # A unique name used to identify this machine.
33
+ # options: (Hash)
34
+ # class: (Class) the class that will be used when generating instances for this
35
+ # machine. If not specified, the class will be guessed from the
36
+ # machine name.
37
+ # extends: (Symbol) the name of a machine that will be extended by this one.
38
+ # If provided, the attributes of the extended machine will be applied
39
+ # to the object before the one being defined.
40
+ #
41
+ # Yields:
42
+ # The object being created and an association helper
43
+ #
44
+ # Example:
45
+ #
46
+ # Machine.define :car do |car, machine|
47
+ # car.make = 'GMC'
48
+ # car.model = 'S-15'
49
+ # end
50
+ def self.define(name, options={}, &block)
51
+ self.machines[name] = Machine.new(name, options, block)
52
+ end
53
+
54
+ # Defines a group of machines with a base set of attributes and then a set of
55
+ # machines as children. This is useful as a namespacing technique or for any
56
+ # case where you wish to define a set of objects that share a set of base
57
+ # attributes.
58
+ #
59
+ # Arguments:
60
+ # name: (Symbol)
61
+ # A unique name to identify the group. This name will itself become a machine
62
+ # that will build from the base attributes.
63
+ # options: (Hash)
64
+ # class: (Class) the class that will be used when generating instances for this
65
+ # machine. If not specified, the class will be guessed from the
66
+ # machine name.
67
+ #
68
+ # Example
69
+ #
70
+ # Machine.define_group :user do |group|
71
+ # group.base do |user, machine|
72
+ # user.password = 'password'
73
+ # user.password_confirmation = 'password'
74
+ # user.login = Machine.next(:login)
75
+ # user.email = Machine.next(:email)
76
+ # end
77
+ #
78
+ # group.define :super_user do |user, machine|
79
+ # user.permissions = [machine.permission(:user => user)]
80
+ # end
81
+ # end
82
+ #
83
+ # Machine.build(:user)
84
+ # Machine.build(:super_user)
85
+ #
86
+ def self.define_group(name, options={}, &block)
87
+ group = MachineGroup.new(name, options)
88
+ yield group
89
+ end
90
+
91
+ # Creates an unsaved object using the machine with the given name.
92
+ #
93
+ # Arguments:
94
+ # name: (Symbol)
95
+ # The name of the machine to apply.
96
+ # attributes: (Hash)
97
+ # A set of attributes to use as a replacement for the default ones provided by
98
+ # the machine.
99
+ #
100
+ # Example
101
+ #
102
+ # Machine.build(:car, :model => 'Civic', :make => 'Honda')
103
+ def self.build(name, attributes={})
104
+ machines = machines_for(name)
105
+ raise MachineNotFoundError if machines.empty?
106
+ object = machines.shift.build(attributes)
107
+ while machine = machines.shift
108
+ machine.apply_to(object, attributes)
109
+ end
110
+ object
111
+ end
112
+
113
+ # Creates an saved object using the machine with the given name.
114
+ #
115
+ # Arguments:
116
+ # name: (Symbol)
117
+ # The name of the machine to apply.
118
+ # attributes: (Hash)
119
+ # A set of attributes to use as a replacement for the default ones provided by
120
+ # the machine.
121
+ #
122
+ # Example
123
+ #
124
+ # Machine.build!(:car, :model => 'Civic', :make => 'Honda')
125
+ def self.build!(name, attributes={})
126
+ result = build(name, attributes)
127
+ result.save! if result.respond_to?(:save!)
128
+ result
129
+ end
130
+
131
+ # Apply the machine with the given name to the provided object. This can
132
+ # be used to load an existing object with the attributes defined in a machine.
133
+ #
134
+ # Arguments:
135
+ # name: (Symbol)
136
+ # The name of the machine to apply.
137
+ # object: (Object)
138
+ # The object whose attributes should be set.
139
+ # attributes: (Hash)
140
+ # A set of replacements attributes for those specified in the machine.
141
+ #
142
+ # Example
143
+ #
144
+ # car = Car.new
145
+ # Machine.apply_to(:car, car, :model => 'Jetta')
146
+ def self.apply_to(name, object, attributes={})
147
+ machines = machines_for(name)
148
+ return if machines.empty?
149
+ while machine = machines.shift
150
+ machine.apply_to(object, attributes)
151
+ end
152
+ object
153
+ end
154
+
155
+ # Defines a new named sequence. Sequences can be used to set attributes
156
+ # that must be unique. Once a sequence is created it can be applied by
157
+ # calling Machine.next, passing the sequence name.
158
+ #
159
+ # Arguments:
160
+ # name: (Symbol)
161
+ # A unique name used to identify this sequence.
162
+ # block: (Proc)
163
+ # The code to generate each value in the sequence. This block will be
164
+ # called with a unique number each time a value in the sequence is to be
165
+ # generated. The block should return the generated value for the
166
+ # sequence.
167
+ #
168
+ # Example
169
+ #
170
+ # Machine.sequence :street do |n|
171
+ # "#{n} Main St."
172
+ # end
173
+ def self.sequence(name, &block)
174
+ self.sequences[name] = Sequence.new(block)
175
+ end
176
+
177
+ # Get the next value produced by the sequence with the given name. This
178
+ # can be used in machine definitions to fill in attributes that must be
179
+ # unique.
180
+ #
181
+ # Arguments:
182
+ # sequence: (Symbol)
183
+ # The name of the sequence to use.
184
+ #
185
+ # Example
186
+ #
187
+ # Machine.define :address do |address, machine|
188
+ # address.street = machine.next(:street)
189
+ # end
190
+ def self.next(sequence)
191
+ sequences[sequence].next if sequences.has_key?(sequence)
192
+ end
193
+
194
+ def initialize(name, options, proc) #:nodoc
195
+ @name, @options, @proc = name, options, proc
196
+ end
197
+
198
+ def extends #:nodoc
199
+ @options[:extends] ? machines[@options[:extends]] : nil
200
+ end
201
+
202
+ def build(attributes={}) #:nodoc
203
+ object = build_class.new
204
+ apply_to(object, attributes)
205
+ object
206
+ end
207
+
208
+ def apply_to(object, attributes={}) #:nodoc
209
+ @proc.call(object, AssociationHelper.new)
210
+ attributes.each { |key, value| object.send("#{key}=", value) }
211
+ end
212
+
213
+ protected
214
+
215
+ def build_class #:nodoc
216
+ @options[:class] || @name.to_s.camelize.constantize
217
+ end
218
+
219
+ def self.machines_for(name) #:nodoc
220
+ result = [machines[name]]
221
+ while result.last
222
+ result << result.last.extends
223
+ end
224
+ result.compact.reverse
225
+ end
226
+ end
@@ -0,0 +1,22 @@
1
+ class MachineGroup
2
+
3
+ def initialize(name, options={}) #:nodoc
4
+ @name = name
5
+ @options = options
6
+ end
7
+
8
+ def base(&block) #:nodoc
9
+ @base_machine = Machine.new(@name, @options, block)
10
+ Machine.machines[@name] = @base_machine
11
+ end
12
+
13
+ def define(name, &block) #:nodoc
14
+ if @base_machine
15
+ options = @options.merge(:extends => @name)
16
+ else
17
+ options = @options.merge(:class => (@options[:class] || @name.to_s.camelize.constantize))
18
+ end
19
+ machine = Machine.new(name, options, block)
20
+ Machine.machines[name] = machine
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ class Sequence
2
+
3
+ def initialize(proc) #:nodoc
4
+ @proc, @current_value = proc, 0
5
+ end
6
+
7
+ def next #:nodoc
8
+ @current_value += 1
9
+ @proc.call(@current_value)
10
+ end
11
+
12
+ end
data/lib/machine.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'machine/association_helper'
2
+ require 'machine/machine'
3
+ require 'machine/machine_group'
4
+ require 'machine/sequence'
5
+
6
+ # Find the definitions in the default locations:
7
+ # machines.rb
8
+ # test/machines.rb
9
+ # spec/machines.rb
10
+ Machine.find_definitions
11
+
12
+ # Shorthand method for building a machine, is an alias for Machine.build(name, attributes)
13
+ def Machine(name, attributes={})
14
+ Machine.build(name, attributes)
15
+ end
data/machine.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.version = '1.0.0'
3
+ s.date = %q{2008-11-07}
4
+
5
+ s.name = %q{machine}
6
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
7
+ s.authors = ['Aubrey Holland', 'patch']
8
+ s.description = %q{machine defines a factory system for creating model objects to replace fixtures in Ruby apps.}
9
+ s.email = %q{aubrey@patch.com}
10
+ s.files = ['lib', 'lib/machine', 'lib/machine/association_helper.rb', 'lib/machine/machine.rb', 'lib/machine/machine_group.rb', 'lib/machine/sequence.rb', 'lib/machine.rb', 'machine-1.0.0.gem', 'machine.gemspec', 'rails', 'rails/init.rb', 'Rakefile', 'README.textile', 'test', 'test/machine_group_test.rb', 'test/machine_test.rb', 'test/models.rb', 'test/sequence_test.rb', 'test/test.db', 'test/test_helper.rb', 'TODO.textile']
11
+ s.homepage = %q{http://github.com/aub/machine/tree/master}
12
+ s.require_paths = ['lib']
13
+ s.rubygems_version = %q{1.2.0}
14
+ s.summary = %q{Machine crunches factories.}
15
+ s.has_rdoc = true
16
+ s.extra_rdoc_files = ['README.textile']
17
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.textile']
18
+ s.test_files = ['test/machine_test.rb', 'test/machine_group_test.rb', 'test/sequence_test.rb']
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 2
23
+ end
24
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'machine'
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class MachineGroupTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Machine.machines.clear
7
+ end
8
+
9
+ context "defining the base machine" do
10
+ setup do
11
+ @group = MachineGroup.new(:article)
12
+ end
13
+
14
+ should "create a machine from the name of the group" do
15
+ @group.base do |article, machine|
16
+ article.title = 'ooooh'
17
+ end
18
+ a = Machine(:article)
19
+ assert_equal 'ooooh', a.title
20
+ end
21
+ end
22
+
23
+ context "creating a group with a specified class" do
24
+ setup do
25
+ @group = MachineGroup.new(:whoosit, :class => Article)
26
+ end
27
+
28
+ should "create the correct type of object and set its attributes" do
29
+ @group.base do |article, machine|
30
+ article.title = 'ooooh'
31
+ end
32
+ a = Machine(:whoosit)
33
+ assert a.instance_of?(Article)
34
+ assert_equal 'ooooh', a.title
35
+ end
36
+ end
37
+
38
+ context "defining the sub-machines" do
39
+ setup do
40
+ @group = MachineGroup.new(:article)
41
+ @group.base do |article, machine|
42
+ article.title = 'ooooh'
43
+ article.rating = 3
44
+ end
45
+ @group.define :arts_article do |article, machine|
46
+ article.title = 'awww'
47
+ end
48
+ end
49
+
50
+ should "create a machine for the sub-machine" do
51
+ a = Machine(:arts_article)
52
+ assert a.instance_of?(Article)
53
+ end
54
+
55
+ should "set make the new machine an extension of the base" do
56
+ a = Machine(:arts_article)
57
+ assert_equal 'awww', a.title
58
+ assert_equal 3, a.rating
59
+ end
60
+ end
61
+
62
+ context "defining sub-machines with no base" do
63
+ setup do
64
+ @group = MachineGroup.new(:article)
65
+ @group.define :arts_article do |article, machine|
66
+ article.title = 'awww'
67
+ end
68
+ end
69
+
70
+ should "create a machine for the sub-machine" do
71
+ a = Machine(:arts_article)
72
+ assert a.instance_of?(Article)
73
+ end
74
+
75
+ should "set the attributes correctly" do
76
+ a = Machine(:arts_article)
77
+ assert_equal 'awww', a.title
78
+ end
79
+ end
80
+
81
+ context "creating a group with a specified class and no base" do
82
+ setup do
83
+ @group = MachineGroup.new(:whoosit, :class => Article)
84
+ @group.define :arts_article do |article, machine|
85
+ article.title = 'ummm'
86
+ end
87
+ end
88
+
89
+ should "create the right type of object and set its attributes" do
90
+ a = Machine(:arts_article)
91
+ assert a.instance_of?(Article)
92
+ assert_equal 'ummm', a.title
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,269 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class MachineTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Machine.machines.clear
7
+ end
8
+
9
+ context "building simple machines" do
10
+ should "add a machine to the list when defined" do
11
+ Machine.define :test, :hack => 'ack' do
12
+ end
13
+ assert_equal 1, Machine.machines.size
14
+ end
15
+
16
+ should "set proper machine variables when creating" do
17
+ Machine.define :testo, :class => Article do
18
+ end
19
+ assert_not_nil Machine.machines[:testo]
20
+ end
21
+ end
22
+
23
+ context "using machines" do
24
+ should "build an instance of the object" do
25
+ Machine.define :article do
26
+ end
27
+ assert Machine.build(:article).instance_of?(Article)
28
+ end
29
+
30
+ should "build an instance of the provided class" do
31
+ Machine.define :something, :class => Article do
32
+ end
33
+ assert Machine.build(:something).instance_of?(Article)
34
+ end
35
+
36
+ should "set default attributes on the built object" do
37
+ Machine.define :thing, :class => Article do |article, machine|
38
+ article.title = 'aha'
39
+ end
40
+ assert_equal 'aha', Machine.build(:thing).title
41
+ end
42
+
43
+ should "allow overriding of default attributes" do
44
+ Machine.define :thing, :class => Article do |article, machine|
45
+ article.title = 'aha'
46
+ end
47
+ assert_equal 'ooooh', Machine.build(:thing, :title => 'ooooh').title
48
+ end
49
+
50
+ should "only replace the attributes that are passed" do
51
+ Machine.define :thing, :class => Article do |article, machine|
52
+ article.title = 'aha'
53
+ article.rating = 123
54
+ end
55
+ assert_equal 123, Machine.build(:thing, :title => 'ooooh').rating
56
+ end
57
+
58
+ should "not save records with build" do
59
+ Machine.define :thing, :class => Article do |article, machine|
60
+ article.title = 'aha'
61
+ end
62
+ assert Machine.build(:thing).new_record?
63
+ end
64
+
65
+ should "save records with build!" do
66
+ Machine.define :thing, :class => Article do |article, machine|
67
+ article.title = 'aha'
68
+ end
69
+ assert !Machine.build!(:thing).new_record?
70
+ end
71
+
72
+ should "allow redefinition of a machine" do
73
+ Machine.define :article do |article, machine|
74
+ article.title = 'aha'
75
+ end
76
+
77
+ Machine.define :article do |article, machine|
78
+ article.title = 'oho'
79
+ end
80
+ assert_equal 'oho', Machine.build(:article).title
81
+ end
82
+
83
+ should 'raise an exception when using a machine that doesn\'t exist' do
84
+ assert_raises MachineNotFoundError do
85
+ Machine.build(:i_can_has_exception)
86
+ end
87
+ end
88
+ end
89
+
90
+ context "with objects that are not active record" do
91
+ setup do
92
+ Machine.define :non_model do |nonmodel, machine|
93
+ nonmodel.name = 'hoo'
94
+ end
95
+ end
96
+
97
+ should "build the object correctly" do
98
+ assert_equal 'hoo', Machine.build(:non_model).name
99
+ end
100
+
101
+ should "not freak out when calling build! with non-activerecord objects" do
102
+ assert_nothing_raised do
103
+ Machine.build!(:non_model).name
104
+ end
105
+ end
106
+ end
107
+
108
+ context "with associations" do
109
+ setup do
110
+ Machine.define :publication do |publication, machine|
111
+ publication.name = 'booya'
112
+ end
113
+
114
+ Machine.define :comment do |comment, machine|
115
+ comment.data = 'dsfsdf'
116
+ end
117
+
118
+ Machine.define :article do |article, machine|
119
+ article.comments = [machine.comment, machine.comment]
120
+ article.publication = machine.publication
121
+ end
122
+ end
123
+
124
+ should "build associated records" do
125
+ assert_equal 2, Machine.build(:article).comments.size
126
+ assert Machine.build(:article).comments[0].instance_of?(Comment)
127
+ end
128
+
129
+ should "allow passing replacement attributes" do
130
+ Machine.define :article do |article, machine|
131
+ article.comments = [machine.comment(:data => 'nice article'), machine.comment(:data => 'bad article')]
132
+ end
133
+ assert_equal ['nice article', 'bad article'].sort, Machine.build(:article).comments.map { |c| c.data }.sort
134
+ end
135
+
136
+ should "not save associated objects when using build" do
137
+ assert Machine.build(:article).comments[0].new_record?
138
+ end
139
+
140
+ should "save associated objects when using build!" do
141
+ assert !Machine.build!(:article).comments[0].new_record?
142
+ assert !Machine.build!(:article).publication.new_record?
143
+ end
144
+ end
145
+
146
+ context "extending existing factories" do
147
+ setup do
148
+ Machine.define :old_article, :class => Article do |article, machine|
149
+ article.title = 'old'
150
+ article.rating = 12
151
+ end
152
+
153
+ Machine.define :new_article, :class => Article, :extends => :old_article do |article, machine|
154
+ article.title = 'new'
155
+ article.author = 'Joe Six Pack'
156
+ end
157
+ end
158
+
159
+ should "create a new model with the correct attributes" do
160
+ article = Machine.build(:new_article)
161
+ assert_equal 'new', article.title
162
+ assert_equal 12, article.rating
163
+ assert_equal 'Joe Six Pack', article.author
164
+ end
165
+
166
+ should "work properly with multiple layers of extensions" do
167
+ Machine.define :newer_article, :class => Article, :extends => :new_article do |article, machine|
168
+ article.rating = 13
169
+ end
170
+
171
+ article = Machine.build(:newer_article)
172
+ assert_equal 'new', article.title
173
+ assert_equal 13, article.rating
174
+ assert_equal 'Joe Six Pack', article.author
175
+ end
176
+ end
177
+
178
+ context "loading default paths" do
179
+ Machine.definition_file_paths.each do |file|
180
+ should "automatically load definitions from #{file}.rb" do
181
+ Machine.stubs(:require).raises(LoadError)
182
+ Machine.expects(:require).with(file)
183
+ Machine.find_definitions
184
+ end
185
+ end
186
+
187
+ should "only load the first set of machines detected" do
188
+ first, second, third = Machine.definition_file_paths
189
+ Machine.expects(:require).with(first).raises(LoadError)
190
+ Machine.expects(:require).with(second)
191
+ Machine.expects(:require).with(third).never
192
+ Machine.find_definitions
193
+ end
194
+ end
195
+
196
+ context "using the helper method" do
197
+ setup do
198
+ Machine.define :article do |article, machine|
199
+ article.title = 'aha'
200
+ end
201
+ end
202
+
203
+ should "apply machines correctly" do
204
+ assert Machine(:article).instance_of?(Article)
205
+ end
206
+
207
+ should "set the attributes" do
208
+ assert_equal 'aha', Machine(:article).title
209
+ end
210
+
211
+ should "allow replacement attributes" do
212
+ assert_equal 'ooh', Machine(:article, :title => 'ooh').title
213
+ end
214
+
215
+ should "not save the object" do
216
+ assert Machine(:article).new_record?
217
+ end
218
+ end
219
+
220
+ context "applying a machine to an existing object" do
221
+ setup do
222
+ Machine.define :article do |article, machine|
223
+ article.title = 'aha'
224
+ end
225
+ @article = Article.new(:title => 'old')
226
+ end
227
+
228
+ should "update the object's attributes" do
229
+ Machine.apply_to(:article, @article)
230
+ assert_equal 'aha', @article.title
231
+ end
232
+
233
+ should "accept replacement attributes" do
234
+ Machine.apply_to(:article, @article, :title => 'new')
235
+ assert_equal 'new', @article.title
236
+ end
237
+
238
+ should "not save the object" do
239
+ Machine.apply_to(:article, @article)
240
+ assert @article.new_record?
241
+ end
242
+ end
243
+
244
+ context "group definition" do
245
+ should "yield a Group instance" do
246
+ thing = nil
247
+ Machine.define_group :user do |group|
248
+ thing = group
249
+ end
250
+ assert thing.kind_of?(MachineGroup)
251
+ end
252
+ end
253
+
254
+ context 'using sequences' do
255
+ setup do
256
+ Machine.sequence :title do |n|
257
+ "title-#{n}"
258
+ end
259
+ Machine.define :article do |article, machine|
260
+ article.title = machine.next(:title)
261
+ end
262
+ end
263
+
264
+ should 'be able to set values using the sequence' do
265
+ assert_equal 'title-1', Machine(:article).title
266
+ end
267
+ end
268
+
269
+ end
data/test/models.rb ADDED
@@ -0,0 +1,43 @@
1
+ ActiveRecord::Base.establish_connection(
2
+ :adapter => 'sqlite3',
3
+ :database => File.join(File.dirname(__FILE__), 'test.db')
4
+ )
5
+
6
+ class CreateSchema < ActiveRecord::Migration
7
+ def self.up
8
+ create_table :publications, :force => true do |t|
9
+ t.string :name
10
+ end
11
+
12
+ create_table :articles, :force => true do |t|
13
+ t.references :publication
14
+ t.string :title
15
+ t.integer :rating
16
+ t.string :author
17
+ end
18
+
19
+ create_table :comments, :force => true do |t|
20
+ t.references :article
21
+ t.string :data
22
+ end
23
+ end
24
+ end
25
+
26
+ CreateSchema.suppress_messages { CreateSchema.migrate(:up) }
27
+
28
+ class Publication < ActiveRecord::Base
29
+ has_many :articles
30
+ end
31
+
32
+ class Article < ActiveRecord::Base
33
+ has_many :comments
34
+ belongs_to :publication
35
+ end
36
+
37
+ class Comment < ActiveRecord::Base
38
+ belongs_to :article
39
+ end
40
+
41
+ class NonModel
42
+ attr_accessor :name
43
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class SequenceTest < Test::Unit::TestCase
4
+
5
+ should 'allow definition of sequences' do
6
+ Machine.sequence :thing do
7
+ end
8
+ assert_equal 1, Machine.sequences.size
9
+ end
10
+
11
+ should 'increment the sequence value' do
12
+ Machine.sequence :thing do |n|
13
+ n
14
+ end
15
+ assert_equal 1, Machine.sequences[:thing].next
16
+ assert_equal 2, Machine.sequences[:thing].next
17
+ end
18
+
19
+ should 'be able to output strings' do
20
+ Machine.sequence :thing do |n|
21
+ "article-#{n}"
22
+ end
23
+ assert_equal 'article-1', Machine.sequences[:thing].next
24
+ assert_equal 'article-2', Machine.sequences[:thing].next
25
+ end
26
+
27
+ should 'be callable through the machine' do
28
+ Machine.sequence :thing do |n|
29
+ "article-#{n}"
30
+ end
31
+ assert_equal 'article-1', Machine.next(:thing)
32
+ end
33
+
34
+ should 'raise an exception when calling a nonexistent sequence' do
35
+ assert_raises MachineNotFoundError do
36
+ Machine.next(:who)
37
+ end
38
+ end
39
+ end
data/test/test.db ADDED
Binary file
@@ -0,0 +1,13 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $: << File.join(File.dirname(__FILE__))
3
+
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+ require 'ruby-debug'
7
+
8
+ require 'activerecord'
9
+ require 'machine'
10
+ require 'mocha'
11
+ require 'models'
12
+ require 'shoulda'
13
+
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aub-machine
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aubrey Holland
8
+ - patch
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-11-07 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: machine defines a factory system for creating model objects to replace fixtures in Ruby apps.
18
+ email: aubrey@patch.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.textile
25
+ files:
26
+ - lib
27
+ - lib/machine
28
+ - lib/machine/association_helper.rb
29
+ - lib/machine/machine.rb
30
+ - lib/machine/machine_group.rb
31
+ - lib/machine/sequence.rb
32
+ - lib/machine.rb
33
+ - machine-1.0.0.gem
34
+ - machine.gemspec
35
+ - rails
36
+ - rails/init.rb
37
+ - Rakefile
38
+ - README.textile
39
+ - test
40
+ - test/machine_group_test.rb
41
+ - test/machine_test.rb
42
+ - test/models.rb
43
+ - test/sequence_test.rb
44
+ - test/test.db
45
+ - test/test_helper.rb
46
+ - TODO.textile
47
+ has_rdoc: true
48
+ homepage: http://github.com/aub/machine/tree/master
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --line-numbers
52
+ - --inline-source
53
+ - --main
54
+ - README.textile
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: Machine crunches factories.
76
+ test_files:
77
+ - test/machine_test.rb
78
+ - test/machine_group_test.rb
79
+ - test/sequence_test.rb