lacomartincik-factory_girl 1.2.1.1

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