factory_girl 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog CHANGED
@@ -1,3 +1,6 @@
1
+ 1.1.3 (September 12, 2008)
2
+ Automatically pull in definitions from factories.rb, test/factories.rb, or
3
+ spec/factories.rb
1
4
  1.1.2 (July 30, 2008)
2
5
  Improved error handling for invalid and undefined factories/attributes
3
6
  Improved handling of strings vs symbols vs classes
data/Rakefile CHANGED
@@ -31,7 +31,7 @@ end
31
31
 
32
32
  spec = Gem::Specification.new do |s|
33
33
  s.name = %q{factory_girl}
34
- s.version = "1.1.2"
34
+ s.version = "1.1.3"
35
35
  s.summary = %q{factory_girl provides a framework and DSL for defining and
36
36
  using model instance factories.}
37
37
  s.description = %q{factory_girl provides a framework and DSL for defining and
data/lib/factory_girl.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'active_support'
2
2
  require 'factory_girl/factory'
3
3
  require 'factory_girl/attribute_proxy'
4
+ require 'factory_girl/attribute'
4
5
  require 'factory_girl/sequence'
6
+ require 'factory_girl/aliases'
5
7
 
6
8
  # Shortcut for Factory.create.
7
9
  #
@@ -10,3 +12,5 @@ require 'factory_girl/sequence'
10
12
  def Factory (name, attrs = {})
11
13
  Factory.create(name, attrs)
12
14
  end
15
+
16
+ Factory.find_definitions
@@ -0,0 +1,37 @@
1
+ class Factory
2
+
3
+ cattr_accessor :aliases #:nodoc:
4
+ self.aliases = [
5
+ [/(.*)_id/, '\1'],
6
+ [/(.*)/, '\1_id']
7
+ ]
8
+
9
+ # Defines a new alias for attributes
10
+ #
11
+ # Arguments:
12
+ # pattern: (Regexp)
13
+ # A pattern that will be matched against attributes when looking for
14
+ # aliases. Contents captured in the pattern can be used in the alias.
15
+ # replace: (String)
16
+ # The alias that results from the matched pattern. Captured strings can
17
+ # be insert like String#sub.
18
+ #
19
+ # Example:
20
+ #
21
+ # Factory.alias /(.*)_confirmation/, '\1'
22
+ def self.alias (pattern, replace)
23
+ self.aliases << [pattern, replace]
24
+ end
25
+
26
+ def self.aliases_for (attribute) #:nodoc:
27
+ aliases.collect do |params|
28
+ pattern, replace = *params
29
+ if pattern.match(attribute.to_s)
30
+ attribute.to_s.sub(pattern, replace).to_sym
31
+ else
32
+ nil
33
+ end
34
+ end.compact << attribute
35
+ end
36
+
37
+ end
@@ -0,0 +1,38 @@
1
+ class Factory
2
+
3
+ class AttributeDefinitionError < RuntimeError
4
+ end
5
+
6
+ class Attribute #:nodoc:
7
+
8
+ attr_reader :name
9
+
10
+ def initialize (name, static_value, lazy_block)
11
+ name = name.to_sym
12
+
13
+ if name.to_s =~ /=$/
14
+ raise AttributeDefinitionError,
15
+ "factory_girl uses 'f.#{name.to_s.chop} value' syntax " +
16
+ "rather than 'f.#{name} = value'"
17
+ end
18
+
19
+ unless static_value.nil? || lazy_block.nil?
20
+ raise AttributeDefinitionError, "Both value and block given"
21
+ end
22
+
23
+ @name = name
24
+ @static_value = static_value
25
+ @lazy_block = lazy_block
26
+ end
27
+
28
+ def value (proxy)
29
+ if @lazy_block.nil?
30
+ @static_value
31
+ else
32
+ @lazy_block.call(proxy)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -1,11 +1,14 @@
1
1
  class Factory
2
2
 
3
- class AttributeDefinitionError < RuntimeError
4
- end
5
-
6
- cattr_accessor :factories, :sequences #:nodoc:
3
+ cattr_accessor :factories #:nodoc:
7
4
  self.factories = {}
8
- self.sequences = {}
5
+
6
+ # An Array of strings specifying locations that should be searched for
7
+ # factory definitions. By default, factory_girl will attempt to require
8
+ # "factories," "test/factories," and "spec/factories." Only the first
9
+ # existing file will be loaded.
10
+ cattr_accessor :definition_file_paths
11
+ self.definition_file_paths = %w(factories test/factories spec/factories)
9
12
 
10
13
  attr_reader :factory_name
11
14
 
@@ -28,41 +31,6 @@ class Factory
28
31
  self.factories[instance.factory_name] = instance
29
32
  end
30
33
 
31
- # Defines a new sequence that can be used to generate unique values in a specific format.
32
- #
33
- # Arguments:
34
- # name: (Symbol)
35
- # A unique name for this sequence. This name will be referenced when
36
- # calling next to generate new values from this sequence.
37
- # block: (Proc)
38
- # The code to generate each value in the sequence. This block will be
39
- # called with a unique number each time a value in the sequence is to be
40
- # generated. The block should return the generated value for the
41
- # sequence.
42
- #
43
- # Example:
44
- #
45
- # Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
46
- def self.sequence (name, &block)
47
- self.sequences[name] = Sequence.new(&block)
48
- end
49
-
50
- # Generates and returns the next value in a sequence.
51
- #
52
- # Arguments:
53
- # name: (Symbol)
54
- # The name of the sequence that a value should be generated for.
55
- #
56
- # Returns:
57
- # The next value in the sequence. (Object)
58
- def self.next (sequence)
59
- unless self.sequences.key?(sequence)
60
- raise "No such sequence: #{sequence}"
61
- end
62
-
63
- self.sequences[sequence].next
64
- end
65
-
66
34
  def build_class #:nodoc:
67
35
  @build_class ||= class_for(@options[:class] || factory_name)
68
36
  end
@@ -71,10 +39,7 @@ class Factory
71
39
  options.assert_valid_keys(:class)
72
40
  @factory_name = factory_name_for(name)
73
41
  @options = options
74
-
75
- @static_attributes = {}
76
- @lazy_attribute_blocks = {}
77
- @lazy_attribute_names = []
42
+ @attributes = []
78
43
  end
79
44
 
80
45
  # Adds an attribute that should be assigned on generated instances for this
@@ -96,23 +61,13 @@ class Factory
96
61
  # value: (Object)
97
62
  # If no block is given, this value will be used for this attribute.
98
63
  def add_attribute (name, value = nil, &block)
99
- name = name.to_sym
64
+ attribute = Attribute.new(name, value, block)
100
65
 
101
- if name.to_s =~ /=$/
102
- raise AttributeDefinitionError,
103
- "factory_girl uses 'f.#{name.to_s.chop} #{value}' syntax " +
104
- "rather than 'f.#{name} #{value}'"
105
- end
106
-
107
- if block_given?
108
- unless value.nil?
109
- raise ArgumentError, "Both value and block given"
110
- end
111
- @lazy_attribute_blocks[name] = block
112
- @lazy_attribute_names << name
113
- else
114
- @static_attributes[name] = value
66
+ if attribute_defined?(attribute.name)
67
+ raise AttributeDefinitionError, "Attribute already defined: #{name}"
115
68
  end
69
+
70
+ @attributes << attribute
116
71
  end
117
72
 
118
73
  # Calls add_attribute using the missing method name as the name of the
@@ -225,6 +180,16 @@ class Factory
225
180
  factory_by_name(name).create(attrs)
226
181
  end
227
182
 
183
+ def find_definitions #:nodoc:
184
+ definition_file_paths.each do |path|
185
+ begin
186
+ require(path)
187
+ break
188
+ rescue LoadError
189
+ end
190
+ end
191
+ end
192
+
228
193
  private
229
194
 
230
195
  def factory_by_name (name)
@@ -235,14 +200,16 @@ class Factory
235
200
 
236
201
  private
237
202
 
238
- def build_attributes_hash (override, strategy)
239
- override = override.symbolize_keys
240
- result = @static_attributes.merge(override)
241
- @lazy_attribute_names.each do |name|
242
- proxy = AttributeProxy.new(self, name, strategy, result)
243
- result[name] = @lazy_attribute_blocks[name].call(proxy) unless override.key?(name)
203
+ def build_attributes_hash (values, strategy)
204
+ values = values.symbolize_keys
205
+ passed_keys = values.keys.collect {|key| Factory.aliases_for(key) }.flatten
206
+ @attributes.each do |attribute|
207
+ unless passed_keys.include?(attribute.name)
208
+ proxy = AttributeProxy.new(self, attribute.name, strategy, values)
209
+ values[attribute.name] = attribute.value(proxy)
210
+ end
244
211
  end
245
- result
212
+ values
246
213
  end
247
214
 
248
215
  def build_instance (override, strategy)
@@ -270,4 +237,8 @@ class Factory
270
237
  end
271
238
  end
272
239
 
240
+ def attribute_defined? (name)
241
+ !@attributes.detect {|attr| attr.name == name }.nil?
242
+ end
243
+
273
244
  end
@@ -2,11 +2,12 @@ class Factory
2
2
 
3
3
  class Sequence
4
4
 
5
- def initialize (&proc)
5
+ def initialize (&proc) #:nodoc:
6
6
  @proc = proc
7
7
  @value = 0
8
8
  end
9
9
 
10
+ # Returns the next value for this sequence
10
11
  def next
11
12
  @value += 1
12
13
  @proc.call(@value)
@@ -14,4 +15,42 @@ class Factory
14
15
 
15
16
  end
16
17
 
18
+ cattr_accessor :sequences #:nodoc:
19
+ self.sequences = {}
20
+
21
+ # Defines a new sequence that can be used to generate unique values in a specific format.
22
+ #
23
+ # Arguments:
24
+ # name: (Symbol)
25
+ # A unique name for this sequence. This name will be referenced when
26
+ # calling next to generate new values from this sequence.
27
+ # block: (Proc)
28
+ # The code to generate each value in the sequence. This block will be
29
+ # called with a unique number each time a value in the sequence is to be
30
+ # generated. The block should return the generated value for the
31
+ # sequence.
32
+ #
33
+ # Example:
34
+ #
35
+ # Factory.sequence(:email) {|n| "somebody_#{n}@example.com" }
36
+ def self.sequence (name, &block)
37
+ self.sequences[name] = Sequence.new(&block)
38
+ end
39
+
40
+ # Generates and returns the next value in a sequence.
41
+ #
42
+ # Arguments:
43
+ # name: (Symbol)
44
+ # The name of the sequence that a value should be generated for.
45
+ #
46
+ # Returns:
47
+ # The next value in the sequence. (Object)
48
+ def self.next (sequence)
49
+ unless self.sequences.key?(sequence)
50
+ raise "No such sequence: #{sequence}"
51
+ end
52
+
53
+ self.sequences[sequence].next
54
+ end
55
+
17
56
  end
@@ -0,0 +1,29 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class AliasesTest < Test::Unit::TestCase
4
+
5
+ should "include an attribute as an alias for itself by default" do
6
+ assert Factory.aliases_for(:test).include?(:test)
7
+ end
8
+
9
+ should "include the root of a foreign key as an alias by default" do
10
+ assert Factory.aliases_for(:test_id).include?(:test)
11
+ end
12
+
13
+ should "include an attribute's foreign key as an alias by default" do
14
+ assert Factory.aliases_for(:test).include?(:test_id)
15
+ end
16
+
17
+ context "after adding an alias" do
18
+
19
+ setup do
20
+ Factory.alias(/(.*)_suffix/, '\1')
21
+ end
22
+
23
+ should "return the alias in the aliases list" do
24
+ assert Factory.aliases_for(:test_suffix).include?(:test)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,70 @@
1
+ require(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class AttributeTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @proxy = mock('attribute-proxy')
7
+ end
8
+
9
+ context "an attribute" do
10
+
11
+ setup do
12
+ @name = :user
13
+ @attr = Factory::Attribute.new(@name, 'test', nil)
14
+ end
15
+
16
+ should "have a name" do
17
+ assert_equal @name, @attr.name
18
+ end
19
+
20
+ end
21
+
22
+ context "an attribute with a static value" do
23
+
24
+ setup do
25
+ @value = 'test'
26
+ @attr = Factory::Attribute.new(:user, @value, nil)
27
+ end
28
+
29
+ should "return the value" do
30
+ assert_equal @value, @attr.value(@proxy)
31
+ end
32
+
33
+ end
34
+
35
+ context "an attribute with a lazy value" do
36
+
37
+ setup do
38
+ @block = lambda { 'value' }
39
+ @attr = Factory::Attribute.new(:user, nil, @block)
40
+ end
41
+
42
+ should "call the block to return a value" do
43
+ assert_equal 'value', @attr.value(@proxy)
44
+ end
45
+
46
+ should "yield the attribute proxy to the block" do
47
+ @block = lambda {|a| a }
48
+ @attr = Factory::Attribute.new(:user, nil, @block)
49
+ assert_equal @proxy, @attr.value(@proxy)
50
+ end
51
+
52
+ end
53
+
54
+ should "raise an error when defining an attribute writer" do
55
+ assert_raise Factory::AttributeDefinitionError do
56
+ Factory::Attribute.new('test=', nil, nil)
57
+ end
58
+ end
59
+
60
+ should "not allow attributes to be added with both a value parameter and a block" do
61
+ assert_raise(Factory::AttributeDefinitionError) do
62
+ Factory::Attribute.new(:name, 'value', lambda {})
63
+ end
64
+ end
65
+
66
+ should "convert names to symbols" do
67
+ assert_equal :name, Factory::Attribute.new('name', nil, nil).name
68
+ end
69
+
70
+ end
data/test/factory_test.rb CHANGED
@@ -53,36 +53,6 @@ class FactoryTest < Test::Unit::TestCase
53
53
 
54
54
  end
55
55
 
56
- should "raise an error when defining a factory when using attribute setters" do
57
- assert_raise Factory::AttributeDefinitionError do
58
- Factory.define(:user) do |f|
59
- f.name = 'test'
60
- end
61
- end
62
- end
63
-
64
- context "defining a sequence" do
65
-
66
- setup do
67
- @sequence = mock('sequence')
68
- @name = :count
69
- Factory::Sequence.stubs(:new).returns(@sequence)
70
- end
71
-
72
- should "create a new sequence" do
73
- Factory::Sequence.expects(:new).with().returns(@sequence)
74
- Factory.sequence(@name)
75
- end
76
-
77
- should "use the supplied block as the sequence generator" do
78
- Factory::Sequence.stubs(:new).yields(1)
79
- yielded = false
80
- Factory.sequence(@name) {|n| yielded = true }
81
- assert yielded
82
- end
83
-
84
- end
85
-
86
56
  context "a factory" do
87
57
 
88
58
  setup do
@@ -99,6 +69,12 @@ class FactoryTest < Test::Unit::TestCase
99
69
  assert_equal @class, @factory.build_class
100
70
  end
101
71
 
72
+ should "not allow the same attribute to be added twice" do
73
+ assert_raise(Factory::AttributeDefinitionError) do
74
+ 2.times { @factory.add_attribute @name }
75
+ end
76
+ end
77
+
102
78
  context "when adding an attribute with a value parameter" do
103
79
 
104
80
  setup do
@@ -223,12 +199,6 @@ class FactoryTest < Test::Unit::TestCase
223
199
  assert_equal @value, @factory.attributes_for[@attr]
224
200
  end
225
201
 
226
- should "not allow attributes to be added with both a value parameter and a block" do
227
- assert_raise(ArgumentError) do
228
- @factory.add_attribute(:name, 'value') {}
229
- end
230
- end
231
-
232
202
  should "allow attributes to be added with strings as names" do
233
203
  @factory.add_attribute('name', 'value')
234
204
  assert_equal 'value', @factory.attributes_for[:name]
@@ -260,6 +230,24 @@ class FactoryTest < Test::Unit::TestCase
260
230
 
261
231
  end
262
232
 
233
+ context "overriding an attribute with an alias" do
234
+
235
+ setup do
236
+ @factory.add_attribute(:test, 'original')
237
+ Factory.alias(/(.*)_alias/, '\1')
238
+ @result = @factory.attributes_for(:test_alias => 'new')
239
+ end
240
+
241
+ should "use the passed in value for the alias" do
242
+ assert_equal 'new', @result[:test_alias]
243
+ end
244
+
245
+ should "discard the predefined value for the attribute" do
246
+ assert_nil @result[:test]
247
+ end
248
+
249
+ end
250
+
263
251
  should "guess the build class from the factory name" do
264
252
  assert_equal User, @factory.build_class
265
253
  end
@@ -427,29 +415,20 @@ class FactoryTest < Test::Unit::TestCase
427
415
 
428
416
  end
429
417
 
430
- context "after defining a sequence" do
431
-
432
- setup do
433
- @sequence = mock('sequence')
434
- @name = :test
435
- @value = '1 2 5'
436
-
437
- @sequence. stubs(:next).returns(@value)
438
- Factory::Sequence.stubs(:new). returns(@sequence)
439
-
440
- Factory.sequence(@name) {}
441
- end
442
-
443
- should "call next on the sequence when sent next" do
444
- @sequence.expects(:next)
445
-
446
- Factory.next(@name)
447
- end
448
-
449
- should "return the value from the sequence" do
450
- assert_equal @value, Factory.next(@name)
418
+ Factory.definition_file_paths.each do |file|
419
+ should "automatically load definitions from #{file}.rb" do
420
+ Factory.stubs(:require).raises(LoadError)
421
+ Factory.expects(:require).with(file)
422
+ Factory.find_definitions
451
423
  end
424
+ end
452
425
 
426
+ should "only load the first set of factories detected" do
427
+ first, second, third = Factory.definition_file_paths
428
+ Factory.expects(:require).with(first).raises(LoadError)
429
+ Factory.expects(:require).with(second)
430
+ Factory.expects(:require).with(third).never
431
+ Factory.find_definitions
453
432
  end
454
433
 
455
434
  end
@@ -74,6 +74,10 @@ class IntegrationTest < Test::Unit::TestCase
74
74
  assert @instance.author.new_record?
75
75
  end
76
76
 
77
+ should "not assign both an association and its foreign key" do
78
+ assert_equal 1, Factory.build(:post, :author_id => 1).author_id
79
+ end
80
+
77
81
  end
78
82
 
79
83
  context "a created instance" do
@@ -26,4 +26,51 @@ class SequenceTest < Test::Unit::TestCase
26
26
 
27
27
  end
28
28
 
29
+ context "defining a sequence" do
30
+
31
+ setup do
32
+ @sequence = mock('sequence')
33
+ @name = :count
34
+ Factory::Sequence.stubs(:new).returns(@sequence)
35
+ end
36
+
37
+ should "create a new sequence" do
38
+ Factory::Sequence.expects(:new).with().returns(@sequence)
39
+ Factory.sequence(@name)
40
+ end
41
+
42
+ should "use the supplied block as the sequence generator" do
43
+ Factory::Sequence.stubs(:new).yields(1)
44
+ yielded = false
45
+ Factory.sequence(@name) {|n| yielded = true }
46
+ assert yielded
47
+ end
48
+
49
+ end
50
+
51
+ context "after defining a sequence" do
52
+
53
+ setup do
54
+ @sequence = mock('sequence')
55
+ @name = :test
56
+ @value = '1 2 5'
57
+
58
+ @sequence. stubs(:next).returns(@value)
59
+ Factory::Sequence.stubs(:new). returns(@sequence)
60
+
61
+ Factory.sequence(@name) {}
62
+ end
63
+
64
+ should "call next on the sequence when sent next" do
65
+ @sequence.expects(:next)
66
+
67
+ Factory.next(@name)
68
+ end
69
+
70
+ should "return the value from the sequence" do
71
+ assert_equal @value, Factory.next(@name)
72
+ end
73
+
74
+ end
75
+
29
76
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_girl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Ferris
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-07-30 00:00:00 -04:00
12
+ date: 2008-09-12 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -35,11 +35,15 @@ files:
35
35
  - LICENSE
36
36
  - Rakefile
37
37
  - README.textile
38
+ - lib/factory_girl/aliases.rb
39
+ - lib/factory_girl/attribute.rb
38
40
  - lib/factory_girl/attribute_proxy.rb
39
41
  - lib/factory_girl/factory.rb
40
42
  - lib/factory_girl/sequence.rb
41
43
  - lib/factory_girl.rb
44
+ - test/aliases_test.rb
42
45
  - test/attribute_proxy_test.rb
46
+ - test/attribute_test.rb
43
47
  - test/factory_test.rb
44
48
  - test/integration_test.rb
45
49
  - test/models.rb
@@ -77,7 +81,9 @@ signing_key:
77
81
  specification_version: 2
78
82
  summary: factory_girl provides a framework and DSL for defining and using model instance factories.
79
83
  test_files:
84
+ - test/aliases_test.rb
80
85
  - test/attribute_proxy_test.rb
86
+ - test/attribute_test.rb
81
87
  - test/factory_test.rb
82
88
  - test/integration_test.rb
83
89
  - test/sequence_test.rb