malvestuto_factory_girl 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +9 -0
  2. data/Changelog +29 -0
  3. data/LICENSE +19 -0
  4. data/README.rdoc +274 -0
  5. data/Rakefile +79 -0
  6. data/VERSION +1 -0
  7. data/lib/factory_girl/aliases.rb +50 -0
  8. data/lib/factory_girl/attribute/association.rb +20 -0
  9. data/lib/factory_girl/attribute/callback.rb +16 -0
  10. data/lib/factory_girl/attribute/dynamic.rb +20 -0
  11. data/lib/factory_girl/attribute/static.rb +17 -0
  12. data/lib/factory_girl/attribute.rb +29 -0
  13. data/lib/factory_girl/factory.rb +432 -0
  14. data/lib/factory_girl/proxy/attributes_for.rb +21 -0
  15. data/lib/factory_girl/proxy/build.rb +30 -0
  16. data/lib/factory_girl/proxy/create.rb +12 -0
  17. data/lib/factory_girl/proxy/stub.rb +50 -0
  18. data/lib/factory_girl/proxy.rb +79 -0
  19. data/lib/factory_girl/sequence.rb +63 -0
  20. data/lib/factory_girl/step_definitions.rb +55 -0
  21. data/lib/factory_girl/syntax/blueprint.rb +42 -0
  22. data/lib/factory_girl/syntax/generate.rb +68 -0
  23. data/lib/factory_girl/syntax/make.rb +39 -0
  24. data/lib/factory_girl/syntax/sham.rb +42 -0
  25. data/lib/factory_girl/syntax.rb +12 -0
  26. data/lib/factory_girl.rb +35 -0
  27. data/spec/factory_girl/aliases_spec.rb +29 -0
  28. data/spec/factory_girl/attribute/association_spec.rb +29 -0
  29. data/spec/factory_girl/attribute/callback_spec.rb +23 -0
  30. data/spec/factory_girl/attribute/dynamic_spec.rb +49 -0
  31. data/spec/factory_girl/attribute/static_spec.rb +29 -0
  32. data/spec/factory_girl/attribute_spec.rb +30 -0
  33. data/spec/factory_girl/factory_spec.rb +583 -0
  34. data/spec/factory_girl/proxy/attributes_for_spec.rb +52 -0
  35. data/spec/factory_girl/proxy/build_spec.rb +81 -0
  36. data/spec/factory_girl/proxy/create_spec.rb +94 -0
  37. data/spec/factory_girl/proxy/stub_spec.rb +79 -0
  38. data/spec/factory_girl/proxy_spec.rb +84 -0
  39. data/spec/factory_girl/sequence_spec.rb +66 -0
  40. data/spec/factory_girl/syntax/blueprint_spec.rb +34 -0
  41. data/spec/factory_girl/syntax/generate_spec.rb +57 -0
  42. data/spec/factory_girl/syntax/make_spec.rb +35 -0
  43. data/spec/factory_girl/syntax/sham_spec.rb +35 -0
  44. data/spec/integration_spec.rb +304 -0
  45. data/spec/models.rb +43 -0
  46. data/spec/spec_helper.rb +18 -0
  47. metadata +218 -0
@@ -0,0 +1,432 @@
1
+ require 'action_controller/test_process'
2
+ class Factory
3
+
4
+ # Raised when a factory is defined that attempts to instantiate itself.
5
+ class AssociationDefinitionError < RuntimeError
6
+ end
7
+
8
+ # Raised when a callback is defined that has an invalid name
9
+ class InvalidCallbackNameError < RuntimeError
10
+ end
11
+
12
+ # Raised when a factory is defined with the same name as a previously-defined factory.
13
+ class DuplicateDefinitionError < RuntimeError
14
+ end
15
+
16
+ # Raised when paperclip do not get a proper file option.
17
+ class FileDefinitionError < RuntimeError
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :factories #:nodoc:
22
+
23
+ # An Array of strings specifying locations that should be searched for
24
+ # factory definitions. By default, factory_girl will attempt to require
25
+ # "factories," "test/factories," and "spec/factories." Only the first
26
+ # existing file will be loaded.
27
+ attr_accessor :definition_file_paths
28
+ end
29
+
30
+ self.factories = {}
31
+ self.definition_file_paths = %w(factories test/factories spec/factories)
32
+
33
+ attr_reader :factory_name #:nodoc:
34
+ attr_reader :attributes #:nodoc:
35
+
36
+ # Defines a new factory that can be used by the build strategies (create and
37
+ # build) to build new objects.
38
+ #
39
+ # Arguments:
40
+ # * name: +Symbol+ or +String+
41
+ # A unique name used to identify this factory.
42
+ # * options: +Hash+
43
+ #
44
+ # Options:
45
+ # * class: +Symbol+, +Class+, or +String+
46
+ # The class that will be used when generating instances for this factory. If not specified, the class will be guessed from the factory name.
47
+ # * parent: +Symbol+
48
+ # The parent factory. If specified, the attributes from the parent
49
+ # factory will be copied to the current one with an ability to override
50
+ # them.
51
+ # * default_strategy: +Symbol+
52
+ # The strategy that will be used by the Factory shortcut method.
53
+ # Defaults to :create.
54
+ #
55
+ # Yields: +Factory+
56
+ # The newly created factory.
57
+ def self.define (name, options = {})
58
+ instance = Factory.new(name, options)
59
+ yield(instance)
60
+ if parent = options.delete(:parent)
61
+ instance.inherit_from(Factory.factory_by_name(parent))
62
+ end
63
+ if self.factories[instance.factory_name]
64
+ raise DuplicateDefinitionError, "Factory already defined: #{name}"
65
+ end
66
+ self.factories[instance.factory_name] = instance
67
+ end
68
+
69
+ def class_name #:nodoc:
70
+ @options[:class] || factory_name
71
+ end
72
+
73
+ def build_class #:nodoc:
74
+ @build_class ||= class_for(class_name)
75
+ end
76
+
77
+ def default_strategy #:nodoc:
78
+ @options[:default_strategy] || :create
79
+ end
80
+
81
+ def initialize (name, options = {}) #:nodoc:
82
+ assert_valid_options(options)
83
+ @factory_name = factory_name_for(name)
84
+ @options = options
85
+ @attributes = []
86
+ end
87
+
88
+ def inherit_from(parent) #:nodoc:
89
+ @options[:class] ||= parent.class_name
90
+ parent.attributes.each do |attribute|
91
+ unless attribute_defined?(attribute.name)
92
+ @attributes << attribute.clone
93
+ end
94
+ end
95
+ end
96
+
97
+ # Adds an attribute that should be assigned on generated instances for this
98
+ # factory.
99
+ #
100
+ # This method should be called with either a value or block, but not both. If
101
+ # called with a block, the attribute will be generated "lazily," whenever an
102
+ # instance is generated. Lazy attribute blocks will not be called if that
103
+ # attribute is overriden for a specific instance.
104
+ #
105
+ # When defining lazy attributes, an instance of Factory::Proxy will
106
+ # be yielded, allowing associations to be built using the correct build
107
+ # strategy.
108
+ #
109
+ # Arguments:
110
+ # * name: +Symbol+ or +String+
111
+ # The name of this attribute. This will be assigned using :"#{name}=" for
112
+ # generated instances.
113
+ # * value: +Object+
114
+ # If no block is given, this value will be used for this attribute.
115
+ def add_attribute (name, value = nil, &block)
116
+ if block_given?
117
+ if value
118
+ raise AttributeDefinitionError, "Both value and block given"
119
+ else
120
+ attribute = Attribute::Dynamic.new(name, block)
121
+ end
122
+ else
123
+ attribute = Attribute::Static.new(name, value)
124
+ end
125
+
126
+ if attribute_defined?(attribute.name)
127
+ raise AttributeDefinitionError, "Attribute already defined: #{name}"
128
+ end
129
+
130
+ @attributes << attribute
131
+ end
132
+
133
+ # Calls add_attribute using the missing method name as the name of the
134
+ # attribute, so that:
135
+ #
136
+ # Factory.define :user do |f|
137
+ # f.name 'Billy Idol'
138
+ # end
139
+ #
140
+ # and:
141
+ #
142
+ # Factory.define :user do |f|
143
+ # f.add_attribute :name, 'Billy Idol'
144
+ # end
145
+ #
146
+ # are equivilent.
147
+ def method_missing (name, *args, &block)
148
+ add_attribute(name, *args, &block)
149
+ end
150
+
151
+ # Adds an attribute that builds an association. The associated instance will
152
+ # be built using the same build strategy as the parent instance.
153
+ #
154
+ # Example:
155
+ # Factory.define :user do |f|
156
+ # f.name 'Joey'
157
+ # end
158
+ #
159
+ # Factory.define :post do |f|
160
+ # f.association :author, :factory => :user
161
+ # end
162
+ #
163
+ # Arguments:
164
+ # * name: +Symbol+
165
+ # The name of this attribute.
166
+ # * options: +Hash+
167
+ #
168
+ # Options:
169
+ # * factory: +Symbol+ or +String+
170
+ # The name of the factory to use when building the associated instance.
171
+ # If no name is given, the name of the attribute is assumed to be the
172
+ # name of the factory. For example, a "user" association will by
173
+ # default use the "user" factory.
174
+ def association (name, options = {})
175
+ factory_name = options.delete(:factory) || name
176
+ if factory_name_for(factory_name) == self.factory_name
177
+ raise AssociationDefinitionError, "Self-referencing association '#{name}' in factory '#{self.factory_name}'"
178
+ end
179
+ @attributes << Attribute::Association.new(name, factory_name, options)
180
+ end
181
+
182
+ # Add a file to a paperclip association
183
+ # Example
184
+ # Factory.define :fact do |f|
185
+ # f.paperclip :image, 'path_to_image', 'image/jpeg'
186
+ # end
187
+ #
188
+ # Arguments:
189
+ # * attr: +Symbol+
190
+ # The name of the paperclip association.
191
+ # * file: +String+
192
+ # A path to the file
193
+ #
194
+ # Options:
195
+ # * content_type: +String+
196
+ # The content_type of the file
197
+ # * binary: +Boolean++
198
+ # whether it's binary or not
199
+ # default is false
200
+ # All options regarding to ActionController::TestUploadedFile
201
+ def paperclip(attr, file, *options)
202
+ raise FileDefinitionError unless file
203
+ file = ActionController::TestUploadedFile.new(file, *options)
204
+ add_attribute attr, file
205
+ end
206
+
207
+ # Adds an attribute that will have unique values generated by a sequence with
208
+ # a specified format.
209
+ #
210
+ # The result of:
211
+ # Factory.define :user do |f|
212
+ # f.sequence(:email) { |n| "person#{n}@example.com" }
213
+ # end
214
+ #
215
+ # Is equal to:
216
+ # Factory.sequence(:email) { |n| "person#{n}@example.com" }
217
+ #
218
+ # Factory.define :user do |f|
219
+ # f.email { Factory.next(:email) }
220
+ # end
221
+ #
222
+ # Except that no globally available sequence will be defined.
223
+ def sequence (name, &block)
224
+ s = Sequence.new(&block)
225
+ add_attribute(name) { s.next }
226
+ end
227
+
228
+ def after_build(&block)
229
+ callback(:after_build, &block)
230
+ end
231
+
232
+ def after_create(&block)
233
+ callback(:after_create, &block)
234
+ end
235
+
236
+ def after_stub(&block)
237
+ callback(:after_stub, &block)
238
+ end
239
+
240
+ def callback(name, &block)
241
+ unless [:after_build, :after_create, :after_stub].include?(name.to_sym)
242
+ raise InvalidCallbackNameError, "#{name} is not a valid callback name. Valid callback names are :after_build, :after_create, and :after_stub"
243
+ end
244
+ @attributes << Attribute::Callback.new(name.to_sym, block)
245
+ end
246
+
247
+ # Generates and returns a Hash of attributes from this factory. Attributes
248
+ # can be individually overridden by passing in a Hash of attribute => value
249
+ # pairs.
250
+ #
251
+ # Arguments:
252
+ # * name: +Symbol+ or +String+
253
+ # The name of the factory that should be used.
254
+ # * overrides: +Hash+
255
+ # Attributes to overwrite for this set.
256
+ #
257
+ # Returns: +Hash+
258
+ # A set of attributes that can be used to build an instance of the class
259
+ # this factory generates.
260
+ def self.attributes_for (name, overrides = {})
261
+ factory_by_name(name).run(Proxy::AttributesFor, overrides)
262
+ end
263
+
264
+ # Generates and returns an instance from this factory. Attributes can be
265
+ # individually overridden by passing in a Hash of attribute => value pairs.
266
+ #
267
+ # Arguments:
268
+ # * name: +Symbol+ or +String+
269
+ # The name of the factory that should be used.
270
+ # * overrides: +Hash+
271
+ # Attributes to overwrite for this instance.
272
+ #
273
+ # Returns: +Object+
274
+ # An instance of the class this factory generates, with generated attributes
275
+ # assigned.
276
+ def self.build (name, overrides = {})
277
+ factory_by_name(name).run(Proxy::Build, overrides)
278
+ end
279
+
280
+ # Generates, saves, and returns an instance from this factory. Attributes can
281
+ # be individually overridden by passing in a Hash of attribute => value
282
+ # pairs.
283
+ #
284
+ # Instances are saved using the +save!+ method, so ActiveRecord models will
285
+ # raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
286
+ #
287
+ # Arguments:
288
+ # * name: +Symbol+ or +String+
289
+ # The name of the factory that should be used.
290
+ # * overrides: +Hash+
291
+ # Attributes to overwrite for this instance.
292
+ #
293
+ # Returns: +Object+
294
+ # A saved instance of the class this factory generates, with generated
295
+ # attributes assigned.
296
+ def self.create (name, overrides = {})
297
+ factory_by_name(name).run(Proxy::Create, overrides)
298
+ end
299
+
300
+ # Generates and returns an object with all attributes from this factory
301
+ # stubbed out. Attributes can be individually overridden by passing in a Hash
302
+ # of attribute => value pairs.
303
+ #
304
+ # Arguments:
305
+ # * name: +Symbol+ or +String+
306
+ # The name of the factory that should be used.
307
+ # * overrides: +Hash+
308
+ # Attributes to overwrite for this instance.
309
+ #
310
+ # Returns: +Object+
311
+ # An object with generated attributes stubbed out.
312
+ def self.stub (name, overrides = {})
313
+ factory_by_name(name).run(Proxy::Stub, overrides)
314
+ end
315
+
316
+ # Executes the default strategy for the given factory. This is usually create,
317
+ # but it can be overridden for each factory.
318
+ #
319
+ # Arguments:
320
+ # * name: +Symbol+ or +String+
321
+ # The name of the factory that should be used.
322
+ # * overrides: +Hash+
323
+ # Attributes to overwrite for this instance.
324
+ #
325
+ # Returns: +Object+
326
+ # The result of the default strategy.
327
+ def self.default_strategy (name, overrides = {})
328
+ self.send(factory_by_name(name).default_strategy, name, overrides)
329
+ end
330
+
331
+ def self.find_definitions #:nodoc:
332
+ definition_file_paths.each do |path|
333
+ require("#{path}.rb") if File.exists?("#{path}.rb")
334
+
335
+ if File.directory? path
336
+ Dir[File.join(path, '*.rb')].each do |file|
337
+ require file
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ def run (proxy_class, overrides) #:nodoc:
344
+ proxy = proxy_class.new(build_class)
345
+ overrides = symbolize_keys(overrides)
346
+ overrides.each {|attr, val| proxy.set(attr, val) }
347
+ passed_keys = overrides.keys.collect {|k| Factory.aliases_for(k) }.flatten
348
+ @attributes.each do |attribute|
349
+ unless passed_keys.include?(attribute.name)
350
+ attribute.add_to(proxy)
351
+ end
352
+ end
353
+ proxy.result
354
+ end
355
+
356
+ def self.factory_by_name (name)
357
+ factories[name.to_sym] or raise ArgumentError.new("No such factory: #{name.to_s}")
358
+ end
359
+
360
+ def human_name(*args, &block)
361
+ if args.size == 0 && block.nil?
362
+ factory_name.to_s.gsub('_', ' ')
363
+ else
364
+ add_attribute(:human_name, *args, &block)
365
+ end
366
+ end
367
+
368
+ def associations
369
+ attributes.select {|attribute| attribute.is_a?(Attribute::Association) }
370
+ end
371
+
372
+ private
373
+
374
+ def class_for (class_or_to_s)
375
+ if class_or_to_s.respond_to?(:to_sym)
376
+ Object.const_get(variable_name_to_class_name(class_or_to_s))
377
+ else
378
+ class_or_to_s
379
+ end
380
+ end
381
+
382
+ def factory_name_for (class_or_to_s)
383
+ if class_or_to_s.respond_to?(:to_sym)
384
+ class_or_to_s.to_sym
385
+ else
386
+ class_name_to_variable_name(class_or_to_s).to_sym
387
+ end
388
+ end
389
+
390
+ def attribute_defined? (name)
391
+ !@attributes.detect {|attr| attr.name == name && !attr.is_a?(Factory::Attribute::Callback) }.nil?
392
+ end
393
+
394
+ def assert_valid_options(options)
395
+ invalid_keys = options.keys - [:class, :parent, :default_strategy]
396
+ unless invalid_keys == []
397
+ raise ArgumentError, "Unknown arguments: #{invalid_keys.inspect}"
398
+ end
399
+ assert_valid_strategy(options[:default_strategy]) if options[:default_strategy]
400
+ end
401
+
402
+ def assert_valid_strategy(strategy)
403
+ unless Factory::Proxy.const_defined? variable_name_to_class_name(strategy)
404
+ raise ArgumentError, "Unknown strategy: #{strategy}"
405
+ end
406
+ end
407
+
408
+ # Based on ActiveSupport's underscore inflector
409
+ def class_name_to_variable_name(name)
410
+ name.to_s.gsub(/::/, '/').
411
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
412
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
413
+ tr("-", "_").
414
+ downcase
415
+ end
416
+
417
+ # Based on ActiveSupport's camelize inflector
418
+ def variable_name_to_class_name(name)
419
+ name.to_s.
420
+ gsub(/\/(.?)/) { "::#{$1.upcase}" }.
421
+ gsub(/(?:^|_)(.)/) { $1.upcase }
422
+ end
423
+
424
+ # From ActiveSupport
425
+ def symbolize_keys(hash)
426
+ hash.inject({}) do |options, (key, value)|
427
+ options[(key.to_sym rescue key) || key] = value
428
+ options
429
+ end
430
+ end
431
+
432
+ end
@@ -0,0 +1,21 @@
1
+ class Factory
2
+ class Proxy #:nodoc:
3
+ class AttributesFor < Proxy #:nodoc:
4
+ def initialize(klass)
5
+ @hash = {}
6
+ end
7
+
8
+ def get(attribute)
9
+ @hash[attribute]
10
+ end
11
+
12
+ def set(attribute, value)
13
+ @hash[attribute] = value
14
+ end
15
+
16
+ def result
17
+ @hash
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ class Factory
2
+ class Proxy #:nodoc:
3
+ class Build < Proxy #:nodoc:
4
+ def initialize(klass)
5
+ @instance = klass.new
6
+ end
7
+
8
+ def get(attribute)
9
+ @instance.send(attribute)
10
+ end
11
+
12
+ def set(attribute, value)
13
+ @instance.send(:"#{attribute}=", value)
14
+ end
15
+
16
+ def associate(name, factory, attributes)
17
+ set(name, Factory.create(factory, attributes))
18
+ end
19
+
20
+ def association(factory, overrides = {})
21
+ Factory.create(factory, overrides)
22
+ end
23
+
24
+ def result
25
+ run_callbacks(:after_build)
26
+ @instance
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ class Factory
2
+ class Proxy #:nodoc:
3
+ class Create < Build #:nodoc:
4
+ def result
5
+ run_callbacks(:after_build)
6
+ @instance.save!
7
+ run_callbacks(:after_create)
8
+ @instance
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ class Factory
2
+ class Proxy
3
+ class Stub < Proxy #:nodoc:
4
+ @@next_id = 1000
5
+
6
+ def initialize(klass)
7
+ @instance = klass.new
8
+ @instance.id = next_id
9
+ @instance.instance_eval do
10
+ def new_record?
11
+ id.nil?
12
+ end
13
+
14
+ def connection
15
+ raise "stubbed models are not allowed to access the database"
16
+ end
17
+
18
+ def reload
19
+ raise "stubbed models are not allowed to access the database"
20
+ end
21
+ end
22
+ end
23
+
24
+ def next_id
25
+ @@next_id += 1
26
+ end
27
+
28
+ def get(attribute)
29
+ @instance.send(attribute)
30
+ end
31
+
32
+ def set(attribute, value)
33
+ @instance.send(:"#{attribute}=", value)
34
+ end
35
+
36
+ def associate(name, factory, attributes)
37
+ set(name, Factory.stub(factory, attributes))
38
+ end
39
+
40
+ def association(factory, overrides = {})
41
+ Factory.stub(factory, overrides)
42
+ end
43
+
44
+ def result
45
+ run_callbacks(:after_stub)
46
+ @instance
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,79 @@
1
+ class Factory
2
+
3
+ class Proxy #:nodoc:
4
+
5
+ attr_reader :callbacks
6
+
7
+ def initialize(klass)
8
+ end
9
+
10
+ def get(attribute)
11
+ nil
12
+ end
13
+
14
+ def set(attribute, value)
15
+ end
16
+
17
+ def associate(name, factory, attributes)
18
+ end
19
+
20
+ def add_callback(name, block)
21
+ @callbacks ||= {}
22
+ @callbacks[name] ||= []
23
+ @callbacks[name] << block
24
+ end
25
+
26
+ def run_callbacks(name)
27
+ if @callbacks && @callbacks[name]
28
+ @callbacks[name].each do |block|
29
+ block.arity.zero? ? block.call : block.call(@instance)
30
+ end
31
+ end
32
+ end
33
+
34
+ # Generates an association using the current build strategy.
35
+ #
36
+ # Arguments:
37
+ # name: (Symbol)
38
+ # The name of the factory that should be used to generate this
39
+ # association.
40
+ # attributes: (Hash)
41
+ # A hash of attributes that should be overridden for this association.
42
+ #
43
+ # Returns:
44
+ # The generated association for the current build strategy. Note that
45
+ # assocaitions are not generated for the attributes_for strategy. Returns
46
+ # nil in this case.
47
+ #
48
+ # Example:
49
+ #
50
+ # Factory.define :user do |f|
51
+ # # ...
52
+ # end
53
+ #
54
+ # Factory.define :post do |f|
55
+ # # ...
56
+ # f.author {|a| a.association :user, :name => 'Joe' }
57
+ # end
58
+ #
59
+ # # Builds (but doesn't save) a Post and a User
60
+ # Factory.build(:post)
61
+ #
62
+ # # Builds and saves a User, builds a Post, assigns the User to the
63
+ # # author association, and saves the User.
64
+ # Factory.create(:post)
65
+ #
66
+ def association(name, overrides = {})
67
+ nil
68
+ end
69
+
70
+ def method_missing(method, *args, &block)
71
+ get(method)
72
+ end
73
+
74
+ def result
75
+ raise NotImplementedError, "Strategies must return a result"
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,63 @@
1
+ class Factory
2
+
3
+ # Raised when calling Factory.sequence from a dynamic attribute block
4
+ class SequenceAbuseError < StandardError; end
5
+
6
+ # Sequences are defined using Factory.sequence. Sequence values are generated
7
+ # using next.
8
+ class Sequence
9
+
10
+ def initialize (&proc) #:nodoc:
11
+ @proc = proc
12
+ @value = 0
13
+ end
14
+
15
+ # Returns the next value for this sequence
16
+ def next
17
+ @value += 1
18
+ @proc.call(@value)
19
+ end
20
+
21
+ end
22
+
23
+ class << self
24
+ attr_accessor :sequences #:nodoc:
25
+ end
26
+ self.sequences = {}
27
+
28
+ # Defines a new sequence that can be used to generate unique values in a specific format.
29
+ #
30
+ # Arguments:
31
+ # name: (Symbol)
32
+ # A unique name for this sequence. This name will be referenced when
33
+ # calling next to generate new values from this sequence.
34
+ # block: (Proc)
35
+ # The code to generate each value in the sequence. This block will be
36
+ # called with a unique number each time a value in the sequence is to be
37
+ # generated. The block should return the generated value for the
38
+ # sequence.
39
+ #
40
+ # Example:
41
+ #
42
+ # Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
43
+ def self.sequence (name, &block)
44
+ self.sequences[name] = Sequence.new(&block)
45
+ end
46
+
47
+ # Generates and returns the next value in a sequence.
48
+ #
49
+ # Arguments:
50
+ # name: (Symbol)
51
+ # The name of the sequence that a value should be generated for.
52
+ #
53
+ # Returns:
54
+ # The next value in the sequence. (Object)
55
+ def self.next (sequence)
56
+ unless self.sequences.key?(sequence)
57
+ raise "No such sequence: #{sequence}"
58
+ end
59
+
60
+ self.sequences[sequence].next
61
+ end
62
+
63
+ end