mconnell-object_daddy 0.4.2

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Flawed Logic, OG Consulting, Rick Bradley, Yossef Mendelssohn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,314 @@
1
+ Object Daddy
2
+ ============
3
+ _Version 0.4.1 (April 28, 2009)_
4
+
5
+ __Authors:__ [Rick Bradley](mailto:blogicx@rickbradley.com), [Yossef Mendelssohn](mailto:ymendel@pobox.com)
6
+
7
+ __Copyright:__ Copyright (c) 2007, Flawed Logic, OG Consulting, Rick Bradley, Yossef Mendelssohn
8
+
9
+ __License:__ MIT License. See MIT-LICENSE file for more details.
10
+
11
+ Object Daddy is a library (as well as a Ruby on Rails plugin) designed to
12
+ assist in automating testing of large collections of objects, especially webs
13
+ of ActiveRecord models. It is a descendent of the "Object Mother" pattern for
14
+ creating objects for testing, and is related to the concept of an "object
15
+ exemplar" or _stereotype_.
16
+
17
+ **WARNING** This code is very much at an _alpha_ development stage. Usage, APIs,
18
+ etc., are all subject to change.
19
+
20
+ See [http://b.logi.cx/2007/11/26/object-daddy](http://b.logi.cx/2007/11/26/object-daddy) for inspiration, historical drama, and too much reading.
21
+
22
+ ## Installation
23
+
24
+ Presuming your version of Rails has git plugin installation support:
25
+
26
+ script/plugin install git://github.com/flogic/object_daddy.git
27
+
28
+ Otherwise, you can install object_daddy by hand:
29
+
30
+ 1. Unpack the object_daddy directory into vendor/plugins/ in your rails project.
31
+ 2. Run the object_daddy/install.rb Ruby script.
32
+
33
+
34
+ ## Testing
35
+
36
+ Install the rspec gem and cd into the object_daddy directory. Type `spec
37
+ spec/` and you should see all specs run successfully. If you have autotest
38
+ from the ZenTest gem installed you can run autotest in that directory.
39
+
40
+ ## Using Object Daddy
41
+
42
+
43
+ Object Daddy adds a `.generate` method to every ActiveRecord model which can be
44
+ called to generate a valid instance object of that model class, for use in
45
+ testing:
46
+
47
+ it "should have a comment for every forum the user posts to" do
48
+ @user = User.generate
49
+ @post = Post.generate
50
+ @post.comments << Comment.generate
51
+ @user.should have(1).comments
52
+ end
53
+
54
+ This allows us to generate custom model objects without relying on fixtures,
55
+ and without knowing, in our various widespread tests and specs, the details of
56
+ creating a User, Post, Comment, etc. Not having to know this information means
57
+ the information isn't coded into dozens (or hundreds) of tests, and won't need
58
+ to be changed when the User (Post, Comment, ...) model is refactored later.
59
+
60
+ Object Daddy will identify associated classes that need to be instantiated to
61
+ make the main model valid. E.g., given the following models:
62
+
63
+ class User < ActiveRecord::Base
64
+ belongs_to :login
65
+ validates_presence_of :login
66
+ end
67
+
68
+ class Login < ActiveRecord::Base
69
+ has_one :user
70
+ end
71
+
72
+ A call to `User.generate` will also make a call to `Login.generate` so that
73
+ `User#login` is present, and therefore valid.
74
+
75
+ If all models were able to be created in a valid form by the default Model.new
76
+ call with no knowledge of the model itself, there'd be no need for Object
77
+ Daddy. So, when we deal with models which have validity requirements,
78
+ requiring fields which have format constraints, we need a means of expressing
79
+ how to create those models -- how to satisfy those validity constraints.
80
+
81
+ Object Daddy provides a `generator_for` method which allows the developer to
82
+ specify, for a specific model attribute, how to make a valid value. Note that
83
+ `validates_uniqueness_of` can require that, even if we make 100,000 instances
84
+ of a model that unique attributes cannot have the same values.
85
+
86
+ Object Daddy's `generator_for` method can take three main forms corresponding to
87
+ the means of finding a value for the associated attribute: a block, a method
88
+ call, or using a generator class.
89
+
90
+ class User < ActiveRecord::Base
91
+ validates_presence_of :email
92
+ validates_uniqueness_of :email
93
+ validates_format_of :email,
94
+ :with => /^[-a-z_+0-9.]+@(?:[-a-z_+0-9.]\.)+[a-z]+$/i
95
+ validates_presence_of :username
96
+ validates_format_of :username, :with => /^[a-z0-9_]{4,12}$/i
97
+
98
+ generator_for :email, :start => 'test@domain.com' do |prev|
99
+ user, domain = prev.split('@')
100
+ user.succ + '@' + domain
101
+ end
102
+
103
+ generator_for :username, :method => :next_user
104
+
105
+ generator_for :ssn, :class => SSNGenerator
106
+
107
+ def self.next_user
108
+ @last_username ||= 'testuser'
109
+ @last_username.succ
110
+ end
111
+ end
112
+
113
+ class SSNGenerator
114
+ def self.next
115
+ @last ||= '000-00-0000'
116
+ @last = ("%09d" % (@last.gsub('-', '').to_i + 1)).sub(/^(\d{3})(\d{2})(\d{4})$/, '\1-\2-\3')
117
+ end
118
+ end
119
+
120
+ Note that the block method of invocation (as used with _:email_ above) takes an
121
+ optional _:start_ argument, to specify the value of that attribute on the first
122
+ run. The block will be called thereafter with the previous value of the
123
+ attribute and will generate the next attribute value to be used.
124
+
125
+ A simple default block is provided for any generator with a :start value.
126
+
127
+ class User < ActiveRecord::Base
128
+ generator_for :name, :start => 'Joe' do |prev|
129
+ prev.succ
130
+ end
131
+
132
+ generator_for :name, :start => 'Joe' # equivalent to the above
133
+ end
134
+
135
+ The _:method_ form takes a symbol naming a class method in the model class to be
136
+ called to generate a new value for the attribute in question. If the method
137
+ takes a single argument, it will act much like the block method of invocation,
138
+ being called with the previous value and generating the next.
139
+
140
+ The _:class_ form calls the .next class method on the named class to generate a
141
+ new value for the attribute in question.
142
+
143
+ The argument (previous value) to the block invocation form can be omitted if
144
+ it's going to be ignored, and simple invocation forms are provided for literal
145
+ values.
146
+
147
+ class User < ActiveRecord::Base
148
+ generator_for(:start_time) { Time.now }
149
+ generator_for :name, 'Joe'
150
+ generator_for :age => 25
151
+ end
152
+
153
+ The developer would then simply call `User.generate` when testing.
154
+
155
+ If some attribute values are known (or are being controlled during testing)
156
+ then these can simply be passed in to `.generate`:
157
+
158
+ @bad_login = Login.generate(:expiry => 1.week.ago)
159
+ @expired_user = User.generate(:login => @bad_login)
160
+
161
+ A `.generate!` method is also provided. The _generate/generate!_ pair of methods
162
+ can be thought of as analogs to create/create!, one merely providing an instance
163
+ that may or may not be valid and the other raising an exception if any
164
+ problem comes up.
165
+
166
+ Finally, a `.spawn` method is provided that only gives a new, unsaved object. Note
167
+ that this is the only method of the three that is available if you happen to be
168
+ using Object Daddy outside of Rails.
169
+
170
+ ## Exemplars
171
+
172
+ In the examples given above we are using `generator_for` in the bodies of the
173
+ models themselves. Given that Object Daddy is primarily geared towards
174
+ annotating models with information useful for testing, we anticipate that
175
+ `generator_for` should not normally be included inline in models. Rather, we
176
+ will provide a place where model classes can be re-opened and `generator_for`
177
+ calls (and support methods) can be written without polluting the model files
178
+ with Object Daddy information.
179
+
180
+ Object Daddy, when installed as a Rails plugin, will create
181
+ *RAILS_ROOT/spec/exemplars/* as a place to hold __exemplar__ files for Rails model
182
+ classes. (We are seeking perhaps some better terminology)
183
+
184
+ An __exemplar__ for the User model would then be found in
185
+ *RAILS_ROOT/spec/exemplars/user_exemplar.rb* (when you are using a testing tool
186
+ which works from *RAILS_ROOT/test*, Object Daddy will create
187
+ *RAILS_ROOT/test/exemplars* and look for your exemplars in that directory
188
+ instead). Exemplar files are completely optional, and no model need have
189
+ exemplar files. The `.generate` method will still exist and be callable, and
190
+ `generator_for` can be declared in the model files themselves. If an exemplar
191
+ file is available when `.generate` is called on a model, the exemplar file will
192
+ be loaded and used. An example *user_exemplar.rb* appears below:
193
+
194
+ require 'ssn_generator'
195
+
196
+ class User < ActiveRecord::Base
197
+ generator_for :email, :start => 'test@domain.com' do |prev|
198
+ user, domain = prev.split('@')
199
+ user.succ + '@' + domain
200
+ end
201
+
202
+ generator_for :username, :method => :next_user
203
+
204
+ generator_for :ssn, :class => SSNGenerator
205
+
206
+ def self.next_user
207
+ @last_username ||= 'testuser'
208
+ @last_username.succ
209
+ end
210
+ end
211
+
212
+ ## Blocks
213
+
214
+ The `spawn`, `generate` and `generate!` methods can all accept a block, to which
215
+ they'll yield the generated object. This provides a nice scoping mechanism in
216
+ your code examples. Consider:
217
+
218
+ describe "admin user" do
219
+ it "should be authorized to create company profiles"
220
+ admin_user = User.generate!
221
+ admin_user.activate!
222
+ admin_user.add_role("admin")
223
+
224
+ admin_user.should be_authorized(:create, Company)
225
+ end
226
+ end
227
+
228
+ This could be refactored to:
229
+
230
+ describe "admin user" do
231
+ it "should be authorized to create company profiles" do
232
+ admin_user = User.generate! do |user|
233
+ user.activate!
234
+ user.add_role("admin")
235
+ end
236
+
237
+ admin_user.should be_authorized(:create, Company)
238
+ end
239
+ end
240
+
241
+ Or:
242
+
243
+ describe "admin user" do
244
+ it "should be authorized to create company profiles"
245
+ User.generate! do |user|
246
+ user.activate!
247
+ user.add_role("admin")
248
+ end.should be_authorized(:create, Company)
249
+ end
250
+ end
251
+
252
+ Or even:
253
+
254
+ describe "admin user" do
255
+ def admin_user
256
+ @admin_user ||= User.generate! do |user|
257
+ user.activate!
258
+ user.add_role("admin")
259
+ end
260
+ end
261
+
262
+ it "should be authorized to create company profiles"
263
+ admin_user.should be_authorized(:create, Company)
264
+ end
265
+ end
266
+
267
+ This last refactoring allows you to reuse the admin_user method across
268
+ multiple code examples, balancing DRY with local data.
269
+
270
+ ## Object Daddy and Fixtures
271
+
272
+ While Object Daddy is meant to obviate the hellish devilspawn that are test
273
+ fixtures, Object Daddy should work alongside fixtures just fine. To each his
274
+ own, I suppose.
275
+
276
+ ## Known Issues
277
+
278
+ The simple invocation forms for `generator_for` when using literal values do not
279
+ work if the literal value is a Hash. Don't do that.
280
+
281
+ class User < ActiveRecord::Base
282
+ generator_for :thing_hash, { 'some key' => 'some value' }
283
+ generator_for :other_hash => { 'other key' => 'other value' }
284
+ end
285
+
286
+ I'm not sure why this would even ever come up, but seriously, don't.
287
+
288
+ Required `belongs_to` associations are automatically generated when generating an instance,
289
+ but only if necessary.
290
+
291
+ class Category < ActiveRecord::Base
292
+ has_many :items
293
+ end
294
+
295
+ class Item < ActiveRecord::Base
296
+ belongs_to :category
297
+ validates_presence_of :category
298
+ end
299
+
300
+ `Item.generate` will generate a new category, but `some_category.items.generate` will not.
301
+ Unless, of course, you are foolish enough to define a generator in the exemplar.
302
+
303
+ class Item
304
+ generator_for(:category) { Category.generate }
305
+ end
306
+
307
+ Once again, don't do that.
308
+
309
+ ## Rails _surprises_
310
+
311
+ Due to the way Rails handles associations, cascading generations (as a result of
312
+ required associations) are always generated-and-saved, even if the original generation
313
+ call was a mere `spawn` (`new`). This may come as a surprise, but it would probably be more
314
+ of a surprise if `User.spawn.save` and `User.generate` weren't comparable.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+
4
+ gem_spec = eval(File.read('object_daddy.gemspec'))
5
+
6
+ Rake::GemPackageTask.new(gem_spec) do |p|
7
+ p.need_tar = false
8
+ p.need_zip = false
9
+ end
10
+
data/init.rb ADDED
@@ -0,0 +1,12 @@
1
+ unless ActiveRecord::Base.respond_to? :inherited_with_object_daddy
2
+ class ActiveRecord::Base
3
+ def self.inherited_with_object_daddy(subclass)
4
+ self.inherited_without_object_daddy(subclass)
5
+ subclass.send(:include, ObjectDaddy) unless subclass < ObjectDaddy
6
+ end
7
+
8
+ class << self
9
+ alias_method_chain :inherited, :object_daddy
10
+ end
11
+ end
12
+ end
data/install.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'fileutils'
2
+
3
+ def readme_contents
4
+ IO.read(File.join(File.dirname(__FILE__), 'README.markdown'))
5
+ end
6
+
7
+ rails_root = File.dirname(__FILE__) + '/../../../'
8
+
9
+ if File.directory?(rails_root + 'spec')
10
+ unless File.directory?(rails_root + 'spec/exemplars')
11
+ puts "Creating directory [#{rails_root + 'spec/exemplars'}]"
12
+ FileUtils.mkdir(rails_root + 'spec/exemplars')
13
+ end
14
+ else
15
+ if File.directory?(rails_root + 'test')
16
+ unless File.directory?(rails_root + 'test/exemplars')
17
+ puts "Creating directory [#{rails_root + 'test/exemplars'}]"
18
+ FileUtils.mkdir(rails_root + 'test/exemplars')
19
+ end
20
+ else
21
+ puts "Creating directory [#{rails_root + 'spec'}]"
22
+ FileUtils.mkdir(rails_root + 'spec')
23
+ puts "Creating directory [#{rails_root + 'spec/exemplars'}]"
24
+ FileUtils.mkdir(rails_root + 'spec/exemplars')
25
+ end
26
+ end
27
+
28
+ puts readme_contents
@@ -0,0 +1,227 @@
1
+ module ObjectDaddy
2
+
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ if defined? ActiveRecord and klass < ActiveRecord::Base
6
+ klass.extend RailsClassMethods
7
+
8
+ class << klass
9
+ alias_method :validates_presence_of_without_object_daddy, :validates_presence_of
10
+ alias_method :validates_presence_of, :validates_presence_of_with_object_daddy
11
+ end
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ attr_accessor :exemplars_generated, :exemplar_path, :generators
17
+ attr_reader :presence_validated_attributes
18
+ protected :exemplars_generated=
19
+
20
+ # :call-seq:
21
+ # spawn()
22
+ # spawn() do |obj| ... end
23
+ # spawn(args)
24
+ # spawn(args) do |obj| ... end
25
+ #
26
+ # Creates a valid instance of this class, using any known generators. The
27
+ # generated instance is yielded to a block if provided.
28
+ def spawn(args = {})
29
+ gather_exemplars
30
+ if @concrete_subclass_name
31
+ return block_given? \
32
+ ? const_get(@concrete_subclass_name).spawn(args) {|instance| yield instance} \
33
+ : const_get(@concrete_subclass_name).spawn(args)
34
+ end
35
+ generate_values(args)
36
+ instance = new(args)
37
+ yield instance if block_given?
38
+ instance
39
+ end
40
+
41
+ # register a generator for an attribute of this class
42
+ # generator_for :foo do |prev| ... end
43
+ # generator_for :foo do ... end
44
+ # generator_for :foo, value
45
+ # generator_for :foo => value
46
+ # generator_for :foo, :class => GeneratorClass
47
+ # generator_for :foo, :method => :method_name
48
+ def generator_for(handle, args = {}, &block)
49
+ if handle.is_a?(Hash)
50
+ raise ArgumentError, "only specify one attr => value pair at a time" unless handle.keys.length == 1
51
+ gen_data = handle
52
+ handle = gen_data.keys.first
53
+ args = gen_data[handle]
54
+ end
55
+
56
+ raise ArgumentError, "an attribute name must be specified" unless handle = handle.to_sym
57
+
58
+ unless args.is_a?(Hash)
59
+ unless block
60
+ retval = args
61
+ block = lambda { retval } # lambda { args } results in returning the empty hash that args gets changed to
62
+ end
63
+ args = {} # args is assumed to be a hash for the rest of the method
64
+ end
65
+
66
+ if args[:start]
67
+ block ||= lambda { |prev| prev.succ }
68
+ end
69
+
70
+ if args[:method]
71
+ h = { :method => args[:method].to_sym }
72
+ h[:start] = args[:start] if args[:start]
73
+ record_generator_for(handle, h)
74
+ elsif args[:class]
75
+ raise ArgumentError, "generator class [#{args[:class].name}] does not have a :next method" unless args[:class].respond_to?(:next)
76
+ record_generator_for(handle, :class => args[:class])
77
+ elsif block
78
+ raise ArgumentError, "generator block must take an optional single argument" unless (-1..1).include?(block.arity) # NOTE: lambda {} has an arity of -1, while lambda {||} has an arity of 0
79
+ h = { :block => block }
80
+ h[:start] = args[:start] if args[:start]
81
+ record_generator_for(handle, h)
82
+ else
83
+ raise ArgumentError, "a block, :class generator, :method generator, or value must be specified to generator_for"
84
+ end
85
+ end
86
+
87
+ def generates_subclass(subclass_name)
88
+ @concrete_subclass_name = subclass_name.to_s
89
+ end
90
+
91
+ def gather_exemplars
92
+ return if exemplars_generated
93
+ if superclass.respond_to?(:gather_exemplars)
94
+ superclass.gather_exemplars
95
+ self.generators = (superclass.generators || {}).dup
96
+ end
97
+
98
+ exemplar_path.each do |raw_path|
99
+ path = File.join(raw_path, "#{underscore(name)}_exemplar.rb")
100
+ load(path) if File.exists?(path)
101
+ end
102
+ self.exemplars_generated = true
103
+ end
104
+
105
+ def presence_validated_attributes
106
+ @presence_validated_attributes ||= {}
107
+ attrs = @presence_validated_attributes
108
+ if superclass.respond_to?(:presence_validated_attributes)
109
+ attrs = superclass.presence_validated_attributes.merge(attrs)
110
+ end
111
+ attrs
112
+ end
113
+
114
+ protected
115
+
116
+ # we define an underscore helper ourselves since the ActiveSupport isn't available if we're not using Rails
117
+ def underscore(string)
118
+ string.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
119
+ end
120
+
121
+ def record_generator_for(handle, generator)
122
+ self.generators ||= {}
123
+ raise ArgumentError, "a generator for attribute [:#{handle}] has already been specified" if (generators[handle] || {})[:source] == self
124
+ generators[handle] = { :generator => generator, :source => self }
125
+ end
126
+
127
+ private
128
+
129
+ def generate_values(args)
130
+ (generators || {}).each_pair do |handle, gen_data|
131
+ next if args.include?(handle) or args.include?(handle.to_s)
132
+
133
+ generator = gen_data[:generator]
134
+ if generator[:block]
135
+ process_generated_value(args, handle, generator, generator[:block])
136
+ elsif generator[:method]
137
+ method = method(generator[:method])
138
+ if method.arity == 1
139
+ process_generated_value(args, handle, generator, method)
140
+ else
141
+ args[handle] = method.call
142
+ end
143
+ elsif generator[:class]
144
+ args[handle] = generator[:class].next
145
+ end
146
+ end
147
+
148
+ generate_missing(args)
149
+ end
150
+
151
+ def process_generated_value(args, handle, generator, block)
152
+ if generator[:start]
153
+ value = generator[:start]
154
+ generator.delete(:start)
155
+ else
156
+ value = block.call(generator[:prev])
157
+ end
158
+ generator[:prev] = args[handle] = value
159
+ end
160
+
161
+ def generate_missing(args)
162
+ if presence_validated_attributes and !presence_validated_attributes.empty?
163
+ req = {}
164
+ (presence_validated_attributes.keys - args.keys).each {|a| req[a.to_s] = true } # find attributes required by validates_presence_of not already set
165
+
166
+ belongs_to_associations = reflect_on_all_associations(:belongs_to).to_a
167
+ missing = belongs_to_associations.select { |a| req[a.name.to_s] or req[a.primary_key_name.to_s] }
168
+ if create_scope = scope(:create)
169
+ missing.reject! { |a| create_scope.include?(a.primary_key_name) }
170
+ end
171
+ missing.reject! { |a| [a.name, a.primary_key_name].any? { |n| args.stringify_keys.include?(n.to_s) } }
172
+ missing.each {|a| args[a.name] = a.class_name.constantize.generate }
173
+ end
174
+ end
175
+ end
176
+
177
+ module RailsClassMethods
178
+ def exemplar_path
179
+ dir = File.directory?(File.join(RAILS_ROOT, 'spec')) ? 'spec' : 'test'
180
+ File.join(RAILS_ROOT, dir, 'exemplars')
181
+ end
182
+
183
+ def validates_presence_of_with_object_daddy(*attr_names)
184
+ @presence_validated_attributes ||= {}
185
+ new_attr = attr_names.dup
186
+ new_attr.pop if new_attr.last.is_a?(Hash)
187
+ new_attr.each {|a| @presence_validated_attributes[a] = true }
188
+ validates_presence_of_without_object_daddy(*attr_names)
189
+ end
190
+
191
+ # :call-seq:
192
+ # generate()
193
+ # generate() do |obj| ... end
194
+ # generate(args)
195
+ # generate(args) do |obj| ... end
196
+ #
197
+ # Creates and tries to save an instance of this class, using any known
198
+ # generators. The generated instance is yielded to a block if provided.
199
+ #
200
+ # This will not raise errors on a failed save. Use generate! if you
201
+ # want errors raised.
202
+ def generate(args = {})
203
+ spawn(args) do |instance|
204
+ instance.save
205
+ yield instance if block_given?
206
+ end
207
+ end
208
+
209
+ # :call-seq:
210
+ # generate()
211
+ # generate() do |obj| ... end
212
+ # generate(args)
213
+ # generate(args) do |obj| ... end
214
+ #
215
+ # Creates and tries to save! an instance of this class, using any known
216
+ # generators. The generated instance is yielded to a block if provided.
217
+ #
218
+ # This will raise errors on a failed save. Use generate if you do not want
219
+ # errors raised.
220
+ def generate!(args = {})
221
+ spawn(args) do |instance|
222
+ instance.save!
223
+ yield instance if block_given?
224
+ end
225
+ end
226
+ end
227
+ end