factory_girl 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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