factory_girl 1.1.5 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.rdoc +228 -0
  2. data/Rakefile +7 -5
  3. data/lib/factory_girl.rb +11 -4
  4. data/lib/factory_girl/aliases.rb +18 -7
  5. data/lib/factory_girl/attribute.rb +10 -20
  6. data/lib/factory_girl/attribute/association.rb +18 -0
  7. data/lib/factory_girl/attribute/dynamic.rb +17 -0
  8. data/lib/factory_girl/attribute/static.rb +17 -0
  9. data/lib/factory_girl/factory.rb +199 -119
  10. data/lib/factory_girl/proxy.rb +62 -0
  11. data/lib/factory_girl/proxy/attributes_for.rb +21 -0
  12. data/lib/factory_girl/proxy/build.rb +29 -0
  13. data/lib/factory_girl/proxy/create.rb +10 -0
  14. data/lib/factory_girl/proxy/stub.rb +28 -0
  15. data/lib/factory_girl/sequence.rb +2 -0
  16. data/lib/factory_girl/syntax.rb +12 -0
  17. data/lib/factory_girl/syntax/blueprint.rb +42 -0
  18. data/lib/factory_girl/syntax/generate.rb +68 -0
  19. data/lib/factory_girl/syntax/make.rb +39 -0
  20. data/lib/factory_girl/syntax/sham.rb +42 -0
  21. data/test/aliases_test.rb +1 -1
  22. data/test/association_attribute_test.rb +31 -0
  23. data/test/attribute_test.rb +8 -46
  24. data/test/attributes_for_strategy_test.rb +55 -0
  25. data/test/build_strategy_test.rb +79 -0
  26. data/test/create_strategy_test.rb +90 -0
  27. data/test/dynamic_attribute_test.rb +41 -0
  28. data/test/factory_test.rb +255 -243
  29. data/test/integration_test.rb +91 -3
  30. data/test/models.rb +1 -0
  31. data/test/sequence_test.rb +1 -1
  32. data/test/static_attribute_test.rb +33 -0
  33. data/test/strategy_test.rb +33 -0
  34. data/test/stub_strategy_test.rb +52 -0
  35. data/test/syntax/blueprint_test.rb +39 -0
  36. data/test/syntax/generate_test.rb +63 -0
  37. data/test/syntax/make_test.rb +39 -0
  38. data/test/syntax/sham_test.rb +40 -0
  39. data/test/test_helper.rb +1 -0
  40. metadata +42 -8
  41. data/README.textile +0 -151
  42. data/lib/factory_girl/attribute_proxy.rb +0 -92
  43. data/test/attribute_proxy_test.rb +0 -121
@@ -0,0 +1,228 @@
1
+ = factory_girl
2
+
3
+ factory_girl is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritence.
4
+
5
+ == Download
6
+
7
+ Github: http://github.com/thoughtbot/factory_girl/tree/master
8
+
9
+ Gem:
10
+ gem install thoughtbot-factory_girl --source http://gems.github.com
11
+
12
+ Note: if you install factory_girl using the gem from Github, you'll need this
13
+ in your environment.rb if you want to use Rails 2.1+'s dependency manager:
14
+
15
+ config.gem "thoughtbot-factory_girl",
16
+ :lib => "factory_girl",
17
+ :source => "http://gems.github.com"
18
+
19
+ == Defining factories
20
+
21
+ Each factory has a name and a set of attributes. The name is used to guess the class of the object by default, but it's possible to excplicitly specify it:
22
+
23
+ # This will guess the User class
24
+ Factory.define :user do |u|
25
+ u.first_name 'John'
26
+ u.last_name 'Doe'
27
+ u.admin false
28
+ end
29
+
30
+ # This will use the User class (Admin would have been guessed)
31
+ Factory.define :admin, :class => User do |u|
32
+ u.first_name 'Admin'
33
+ u.last_name 'User'
34
+ u.admin true
35
+ end
36
+
37
+ # The same, but using a string instead of class constant
38
+ Factory.define :admin, :class => 'user' do |u|
39
+ u.first_name 'Admin'
40
+ u.last_name 'User'
41
+ u.admin true
42
+ end
43
+
44
+ It is highly recommended that you have one factory for each class that provides the simplest set of attributes necessary to create an instance of that class. If you're creating ActiveRecord objects, that means that you should only provide attributes that are required through validations and that do not have defaults. Other factories can be created through inheritence to cover common scenarios for each class.
45
+
46
+ Factories can either be defined anywhere, but will automatically be loaded if they are defined in files at the following locations:
47
+
48
+ test/factories.rb
49
+ spec/factories.rb
50
+ test/factories/*.rb
51
+ spec/factories/*.rb
52
+
53
+ == Using factories
54
+
55
+ factory_girl supports several different build strategies: build, create, attributes_for and stub:
56
+
57
+ # Returns a User instance that's not saved
58
+ user = Factory.build(:user)
59
+
60
+ # Returns a saved User instance
61
+ user = Factory.create(:user)
62
+
63
+ # Returns a hash of attributes that can be used to build a User instance:
64
+ attrs = Factory.attributes_for(:user)
65
+
66
+ # Returns an object with all defined attributes stubbed out:
67
+ stub = Factory.stub(:user)
68
+
69
+ You can use the Factory method as a shortcut for the default build strategy:
70
+
71
+ # Same as Factory.create :user:
72
+ user = Factory(:user)
73
+
74
+ The default strategy can be overriden:
75
+
76
+ # Now same as Factory.build(:user)
77
+ Factory.define :user, :default_strategy => :build do |u|
78
+ ...
79
+ end
80
+
81
+ user = Factory(:user)
82
+
83
+ No matter which startegy is used, it's possible to override the defined attributes by passing a hash:
84
+
85
+ # Build a User instance and override the first_name property
86
+ user = Factory.build(:user, :first_name => 'Joe')
87
+ user.first_name
88
+ # => "Joe"
89
+
90
+ == Lazy Attributes
91
+
92
+ Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added by passing a block instead of a parameter:
93
+
94
+ Factory.define :user do |u|
95
+ # ...
96
+ u.activation_code { User.generate_activation_code }
97
+ end
98
+
99
+ == Dependent Attributes
100
+
101
+ Attributes can be based on the values of other attributes using the proxy that is yieled to lazy attribute blocks:
102
+
103
+ Factory.define :user do |u|
104
+ u.first_name 'Joe'
105
+ u.last_name 'Blow'
106
+ u.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase }
107
+ end
108
+
109
+ Factory(:user, :last_name => 'Doe').email
110
+ # => "joe.doe@example.com"
111
+
112
+ == Associations
113
+
114
+ Associated instances can be generated by using the association method when
115
+ defining a lazy attribute:
116
+
117
+ Factory.define :post do |p|
118
+ # ...
119
+ p.author {|author| author.association(:user, :last_name => 'Writely') }
120
+ end
121
+
122
+ The behavior of the association method varies depending on the build strategy used for the parent object.
123
+
124
+ # Builds and saves a User and a Post
125
+ post = Factory(:post)
126
+ post.new_record? # => false
127
+ post.author.new_record # => false
128
+
129
+ # Builds and saves a User, and then builds but does not save a Post
130
+ Factory.build(:post)
131
+ post.new_record? # => true
132
+ post.author.new_record # => false
133
+
134
+ Because this pattern is so common, a prettier syntax is available for defining
135
+ associations:
136
+
137
+ # The following definitions are equivilent:
138
+ Factory.define :post do |p|
139
+ p.author {|a| a.association(:user) }
140
+ end
141
+
142
+ Factory.define :post do |p|
143
+ p.association :author, :factory => :user
144
+ end
145
+
146
+ If the factory name is the same as the association name, the factory name can
147
+ be left out.
148
+
149
+ == Inheritance
150
+
151
+ You can easily create multiple factories for the same class without repeating common attributes by using inheritence:
152
+
153
+ Factory.define :post do |p|
154
+ # the 'title' attribute is required for all posts
155
+ p.title 'A title'
156
+ end
157
+
158
+ Factory.define :approved_post, :parent => :post do |p|
159
+ p.approved true
160
+ # the 'approver' association is required for an approved post
161
+ p.association :approver, :factory => :user
162
+ end
163
+
164
+ == Sequences
165
+
166
+ Unique values in a specific format (for example, e-mail addresses) can be
167
+ generated using sequences. Sequences are defined by calling Factory.sequence,
168
+ and values in a sequence are generated by calling Factory.next:
169
+
170
+ # Defines a new sequence
171
+ Factory.sequence :email do |n|
172
+ "person#{n}@example.com"
173
+ end
174
+
175
+ Factory.next :email
176
+ # => "person1@example.com"
177
+
178
+ Factory.next :email
179
+ # => "person2@example.com"
180
+
181
+ Sequences can be used in lazy attributes:
182
+
183
+ Factory.define :user do |f|
184
+ f.email { Factory.next(:email) }
185
+ end
186
+
187
+ And it's also possible to define an in-line sequence that is only used in
188
+ a particular factory:
189
+
190
+ Factory.define :user do |f|
191
+ f.sequence :email {|n| "person#{n}@example.com" }
192
+ end
193
+
194
+ == Alternate Syntaxes
195
+
196
+ Users' tastes for syntax vary dramatically, but most users are looking for a common feature set. Because of this, factory_girl supports "syntax layers" which provide alternate interfaces. See Factory::Syntax for information about the various layers available.
197
+
198
+ == More Information
199
+
200
+ Our blog: http://giantrobots.thoughtbot.com
201
+
202
+ factory_girl rdoc: http://dev.thoughtbot.com/factory_girl
203
+
204
+ Mailing list: http://groups.google.com/group/factory_girl
205
+
206
+ factory_girl tickets: http://thoughtbot.lighthouseapp.com/projects/14354-factory_girl
207
+
208
+ == Contributing
209
+
210
+ Please read the contribution guidelines before submitting patches or pull requests.
211
+
212
+ == Author
213
+
214
+ factory_girl was written by Joe Ferris with contributions from several authors, including:
215
+ * Alex Sharp
216
+ * Eugene Bolshakov
217
+ * Jon Yurek
218
+ * Josh Nichols
219
+ * Josh Owens
220
+
221
+ The syntax layers are derived from software written by the following authors:
222
+ * Pete Yandell
223
+ * Rick Bradley
224
+ * Yossef Mendelssohn
225
+
226
+ Thanks to all members of thoughtbot for inspiration, ideas, and funding.
227
+
228
+ Copyright 2008-2009 Joe Ferris and thoughtbot[http://www.thoughtbot.com], inc.
data/Rakefile CHANGED
@@ -12,6 +12,7 @@ task :default => :test
12
12
  desc 'Test the factory_girl plugin.'
13
13
  Rake::TestTask.new(:test) do |t|
14
14
  t.libs << 'lib'
15
+ t.libs << 'test'
15
16
  t.pattern = 'test/**/*_test.rb'
16
17
  t.verbose = true
17
18
  end
@@ -27,8 +28,9 @@ desc 'Generate documentation for the factory_girl plugin.'
27
28
  Rake::RDocTask.new(:rdoc) do |rdoc|
28
29
  rdoc.rdoc_dir = 'rdoc'
29
30
  rdoc.title = 'Factory Girl'
30
- rdoc.options << '--line-numbers' << '--inline-source' << "--main" << "README.textile"
31
- rdoc.rdoc_files.include('README.textile')
31
+ rdoc.options << '--line-numbers' << '--inline-source' << "--main" << "README.rdoc"
32
+ rdoc.rdoc_files.include('README.rdoc')
33
+ rdoc.rdoc_files.include('CONTRIBUTION_GUIDELINES.rdoc')
32
34
  rdoc.rdoc_files.include('lib/**/*.rb')
33
35
  end
34
36
 
@@ -39,7 +41,7 @@ end
39
41
 
40
42
  spec = Gem::Specification.new do |s|
41
43
  s.name = %q{factory_girl}
42
- s.version = "1.1.5"
44
+ s.version = "1.2.0"
43
45
  s.summary = %q{factory_girl provides a framework and DSL for defining and
44
46
  using model instance factories.}
45
47
  s.description = %q{factory_girl provides a framework and DSL for defining and
@@ -51,8 +53,8 @@ spec = Gem::Specification.new do |s|
51
53
  s.test_files = Dir[*['test/**/*_test.rb']]
52
54
 
53
55
  s.has_rdoc = true
54
- s.extra_rdoc_files = ["README.textile"]
55
- s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.textile"]
56
+ s.extra_rdoc_files = ["README.rdoc"]
57
+ s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.rdoc"]
56
58
 
57
59
  s.authors = ["Joe Ferris"]
58
60
  s.email = %q{jferris@thoughtbot.com}
@@ -1,19 +1,26 @@
1
1
  require 'active_support'
2
+ require 'factory_girl/proxy'
3
+ require 'factory_girl/proxy/build'
4
+ require 'factory_girl/proxy/create'
5
+ require 'factory_girl/proxy/attributes_for'
6
+ require 'factory_girl/proxy/stub'
2
7
  require 'factory_girl/factory'
3
- require 'factory_girl/attribute_proxy'
4
8
  require 'factory_girl/attribute'
9
+ require 'factory_girl/attribute/static'
10
+ require 'factory_girl/attribute/dynamic'
11
+ require 'factory_girl/attribute/association'
5
12
  require 'factory_girl/sequence'
6
13
  require 'factory_girl/aliases'
7
14
 
8
- # Shortcut for Factory.create.
15
+ # Shortcut for Factory.default_strategy.
9
16
  #
10
17
  # Example:
11
18
  # Factory(:user, :name => 'Joe')
12
19
  def Factory (name, attrs = {})
13
- Factory.create(name, attrs)
20
+ Factory.default_strategy(name, attrs)
14
21
  end
15
22
 
16
- if defined? Rails
23
+ if defined? Rails.configuration
17
24
  Rails.configuration.after_initialize do
18
25
  Factory.definition_file_paths = [
19
26
  File.join(RAILS_ROOT, 'test', 'factories'),
@@ -8,19 +8,30 @@ class Factory
8
8
  [/(.*)/, '\1_id']
9
9
  ]
10
10
 
11
- # Defines a new alias for attributes
11
+ # Defines a new alias for attributes.
12
12
  #
13
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 insert like String#sub.
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
20
  #
21
21
  # Example:
22
22
  #
23
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)
24
35
  def self.alias (pattern, replace)
25
36
  self.aliases << [pattern, replace]
26
37
  end
@@ -1,5 +1,9 @@
1
1
  class Factory
2
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
3
7
  class AttributeDefinitionError < RuntimeError
4
8
  end
5
9
 
@@ -7,32 +11,18 @@ class Factory
7
11
 
8
12
  attr_reader :name
9
13
 
10
- def initialize (name, static_value, lazy_block)
11
- name = name.to_sym
14
+ def initialize(name)
15
+ @name = name.to_sym
12
16
 
13
- if name.to_s =~ /=$/
17
+ if @name.to_s =~ /=$/
14
18
  raise AttributeDefinitionError,
15
- "factory_girl uses 'f.#{name.to_s.chop} value' syntax " +
16
- "rather than 'f.#{name} = value'"
19
+ "factory_girl uses 'f.#{@name.to_s.chop} value' syntax " +
20
+ "rather than 'f.#{@name} = value'"
17
21
  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
22
  end
27
23
 
28
- def value (proxy)
29
- if @lazy_block.nil?
30
- @static_value
31
- else
32
- @lazy_block.call(proxy)
33
- end
24
+ def add_to(proxy)
34
25
  end
35
-
36
26
  end
37
27
 
38
28
  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,17 @@
1
+ class Factory
2
+ class Attribute #:nodoc:
3
+
4
+ class Dynamic < Attribute #:nodoc:
5
+
6
+ def initialize(name, block)
7
+ super(name)
8
+ @block = block
9
+ end
10
+
11
+ def add_to(proxy)
12
+ proxy.set(name, @block.call(proxy))
13
+ end
14
+ end
15
+
16
+ end
17
+ 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
@@ -1,4 +1,8 @@
1
1
  class Factory
2
+
3
+ # Raised when a factory is defined that attempts to instantiate itself.
4
+ class AssociationDefinitionError < RuntimeError
5
+ end
2
6
 
3
7
  class << self
4
8
  attr_accessor :factories #:nodoc:
@@ -13,37 +17,66 @@ class Factory
13
17
  self.factories = {}
14
18
  self.definition_file_paths = %w(factories test/factories spec/factories)
15
19
 
16
- attr_reader :factory_name
20
+ attr_reader :factory_name #:nodoc:
21
+ attr_reader :attributes #:nodoc:
17
22
 
18
23
  # Defines a new factory that can be used by the build strategies (create and
19
24
  # build) to build new objects.
20
25
  #
21
26
  # Arguments:
22
- # name: (Symbol)
23
- # A unique name used to identify this factory.
24
- # options: (Hash)
25
- # class: the class that will be used when generating instances for this
26
- # factory. If not specified, the class will be guessed from the
27
- # factory name.
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.
28
41
  #
29
- # Yields:
30
- # The newly created factory (Factory)
42
+ # Yields: +Factory+
43
+ # The newly created factory.
31
44
  def self.define (name, options = {})
32
45
  instance = Factory.new(name, options)
33
46
  yield(instance)
47
+ if parent = options.delete(:parent)
48
+ instance.inherit_from(Factory.factory_by_name(parent))
49
+ end
34
50
  self.factories[instance.factory_name] = instance
35
51
  end
52
+
53
+ def class_name #:nodoc:
54
+ @options[:class] || factory_name
55
+ end
36
56
 
37
57
  def build_class #:nodoc:
38
- @build_class ||= class_for(@options[:class] || factory_name)
58
+ @build_class ||= class_for(class_name)
59
+ end
60
+
61
+ def default_strategy #:nodoc:
62
+ @options[:default_strategy] || :create
39
63
  end
40
64
 
41
65
  def initialize (name, options = {}) #:nodoc:
42
66
  assert_valid_options(options)
43
67
  @factory_name = factory_name_for(name)
44
- @options = options
68
+ @options = options
45
69
  @attributes = []
46
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
47
80
 
48
81
  # Adds an attribute that should be assigned on generated instances for this
49
82
  # factory.
@@ -53,18 +86,26 @@ class Factory
53
86
  # instance is generated. Lazy attribute blocks will not be called if that
54
87
  # attribute is overriden for a specific instance.
55
88
  #
56
- # When defining lazy attributes, an instance of Factory::AttributeProxy will
89
+ # When defining lazy attributes, an instance of Factory::Proxy will
57
90
  # be yielded, allowing associations to be built using the correct build
58
91
  # strategy.
59
92
  #
60
93
  # Arguments:
61
- # name: (Symbol)
62
- # The name of this attribute. This will be assigned using :"#{name}=" for
63
- # generated instances.
64
- # value: (Object)
65
- # If no block is given, this value will be used for this attribute.
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.
66
99
  def add_attribute (name, value = nil, &block)
67
- attribute = Attribute.new(name, value, 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
68
109
 
69
110
  if attribute_defined?(attribute.name)
70
111
  raise AttributeDefinitionError, "Attribute already defined: #{name}"
@@ -104,128 +145,160 @@ class Factory
104
145
  # end
105
146
  #
106
147
  # Arguments:
107
- # name: (Symbol)
108
- # The name of this attribute.
109
- # options: (Hash)
110
- # factory: (Symbol)
111
- # The name of the factory to use when building the associated instance.
112
- # If no name is given, the name of the attribute is assumed to be the
113
- # name of the factory. For example, a "user" association will by
114
- # default use the "user" factory.
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.
115
158
  def association (name, options = {})
116
- name = name.to_sym
117
- options = symbolize_keys(options)
118
- association_factory = options[:factory] || name
119
-
120
- add_attribute(name) {|a| a.association(association_factory) }
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)
121
164
  end
122
165
 
123
- def attributes_for (attrs = {}) #:nodoc:
124
- build_attributes_hash(attrs, :attributes_for)
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 }
125
185
  end
126
-
127
- def build (attrs = {}) #:nodoc:
128
- build_instance(attrs, :build)
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)
129
202
  end
130
203
 
131
- def create (attrs = {}) #:nodoc:
132
- instance = build_instance(attrs, :create)
133
- instance.save!
134
- instance
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)
135
218
  end
136
219
 
137
- class << self
138
-
139
- # Generates and returns a Hash of attributes from this factory. Attributes
140
- # can be individually overridden by passing in a Hash of attribute => value
141
- # pairs.
142
- #
143
- # Arguments:
144
- # attrs: (Hash)
145
- # Attributes to overwrite for this set.
146
- #
147
- # Returns:
148
- # A set of attributes that can be used to build an instance of the class
149
- # this factory generates. (Hash)
150
- def attributes_for (name, attrs = {})
151
- factory_by_name(name).attributes_for(attrs)
152
- end
153
-
154
- # Generates and returns an instance from this factory. Attributes can be
155
- # individually overridden by passing in a Hash of attribute => value pairs.
156
- #
157
- # Arguments:
158
- # attrs: (Hash)
159
- # See attributes_for
160
- #
161
- # Returns:
162
- # An instance of the class this factory generates, with generated
163
- # attributes assigned.
164
- def build (name, attrs = {})
165
- factory_by_name(name).build(attrs)
166
- end
167
-
168
- # Generates, saves, and returns an instance from this factory. Attributes can
169
- # be individually overridden by passing in a Hash of attribute => value
170
- # pairs.
171
- #
172
- # If the instance is not valid, an ActiveRecord::Invalid exception will be
173
- # raised.
174
- #
175
- # Arguments:
176
- # attrs: (Hash)
177
- # See attributes_for
178
- #
179
- # Returns:
180
- # A saved instance of the class this factory generates, with generated
181
- # attributes assigned.
182
- def create (name, attrs = {})
183
- factory_by_name(name).create(attrs)
184
- end
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
185
270
 
186
- def find_definitions #:nodoc:
187
- definition_file_paths.each do |path|
188
- require("#{path}.rb") if File.exists?("#{path}.rb")
271
+ def self.find_definitions #:nodoc:
272
+ definition_file_paths.each do |path|
273
+ require("#{path}.rb") if File.exists?("#{path}.rb")
189
274
 
190
- if File.directory? path
191
- Dir[File.join(path, '*.rb')].each do |file|
192
- require file
193
- end
275
+ if File.directory? path
276
+ Dir[File.join(path, '*.rb')].each do |file|
277
+ require file
194
278
  end
195
279
  end
196
280
  end
197
-
198
- private
199
-
200
- def factory_by_name (name)
201
- factories[name.to_sym] or raise ArgumentError.new("No such factory: #{name.to_s}")
202
- end
203
-
204
281
  end
205
282
 
206
- private
207
-
208
- def build_attributes_hash (values, strategy)
209
- values = symbolize_keys(values)
210
- passed_keys = values.keys.collect {|key| Factory.aliases_for(key) }.flatten
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
211
288
  @attributes.each do |attribute|
212
289
  unless passed_keys.include?(attribute.name)
213
- proxy = AttributeProxy.new(self, attribute.name, strategy, values)
214
- values[attribute.name] = attribute.value(proxy)
290
+ attribute.add_to(proxy)
215
291
  end
216
292
  end
217
- values
293
+ proxy.result
218
294
  end
219
295
 
220
- def build_instance (override, strategy)
221
- instance = build_class.new
222
- attrs = build_attributes_hash(override, strategy)
223
- attrs.each do |attr, value|
224
- instance.send(:"#{attr}=", value)
225
- end
226
- instance
227
- end
296
+ private
228
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
+
229
302
  def class_for (class_or_to_s)
230
303
  if class_or_to_s.respond_to?(:to_sym)
231
304
  Object.const_get(variable_name_to_class_name(class_or_to_s))
@@ -247,10 +320,17 @@ class Factory
247
320
  end
248
321
 
249
322
  def assert_valid_options(options)
250
- invalid_keys = options.keys - [:class]
323
+ invalid_keys = options.keys - [:class, :parent, :default_strategy]
251
324
  unless invalid_keys == []
252
325
  raise ArgumentError, "Unknown arguments: #{invalid_keys.inspect}"
253
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
254
334
  end
255
335
 
256
336
  # Based on ActiveSupport's underscore inflector