agibralter-factory_girl 1.2.1

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