dm-factory_girl 1.2.3

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