edavis10-object_daddy 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +326 -0
- data/VERSION.yml +4 -0
- data/init.rb +1 -0
- data/install.rb +28 -0
- data/lib/object_daddy.rb +254 -0
- data/rails/init.rb +12 -0
- data/spec/install_spec.rb +123 -0
- data/spec/object_daddy_spec.rb +894 -0
- data/spec/resources/config/database.yml +3 -0
- data/spec/resources/schema +35 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +22 -0
- metadata +71 -0
data/README.markdown
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
Object Daddy
|
2
|
+
============
|
3
|
+
_Version 0.4.3 (February 5, 2010)_
|
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
|
+
|
25
|
+
## As Gem
|
26
|
+
|
27
|
+
sudo gem install object_daddy
|
28
|
+
|
29
|
+
config/enviroments/test.rb
|
30
|
+
|
31
|
+
gem.config "object_daddy"
|
32
|
+
|
33
|
+
|
34
|
+
## As Plugin
|
35
|
+
|
36
|
+
Presuming your version of Rails has git plugin installation support:
|
37
|
+
|
38
|
+
script/plugin install git://github.com/flogic/object_daddy.git
|
39
|
+
|
40
|
+
Otherwise, you can install object_daddy by hand:
|
41
|
+
|
42
|
+
1. Unpack the object_daddy directory into vendor/plugins/ in your rails project.
|
43
|
+
2. Run the object_daddy/install.rb Ruby script.
|
44
|
+
|
45
|
+
|
46
|
+
## Testing
|
47
|
+
|
48
|
+
Install the rspec gem and cd into the object_daddy directory. Type `spec
|
49
|
+
spec/` and you should see all specs run successfully. If you have autotest
|
50
|
+
from the ZenTest gem installed you can run autotest in that directory.
|
51
|
+
|
52
|
+
## Using Object Daddy
|
53
|
+
|
54
|
+
|
55
|
+
Object Daddy adds a `.generate` method to every ActiveRecord model which can be
|
56
|
+
called to generate a valid instance object of that model class, for use in
|
57
|
+
testing:
|
58
|
+
|
59
|
+
it "should have a comment for every forum the user posts to" do
|
60
|
+
@user = User.generate
|
61
|
+
@post = Post.generate
|
62
|
+
@post.comments << Comment.generate
|
63
|
+
@user.should have(1).comments
|
64
|
+
end
|
65
|
+
|
66
|
+
This allows us to generate custom model objects without relying on fixtures,
|
67
|
+
and without knowing, in our various widespread tests and specs, the details of
|
68
|
+
creating a User, Post, Comment, etc. Not having to know this information means
|
69
|
+
the information isn't coded into dozens (or hundreds) of tests, and won't need
|
70
|
+
to be changed when the User (Post, Comment, ...) model is refactored later.
|
71
|
+
|
72
|
+
Object Daddy will identify associated classes that need to be instantiated to
|
73
|
+
make the main model valid. E.g., given the following models:
|
74
|
+
|
75
|
+
class User < ActiveRecord::Base
|
76
|
+
belongs_to :login
|
77
|
+
validates_presence_of :login
|
78
|
+
end
|
79
|
+
|
80
|
+
class Login < ActiveRecord::Base
|
81
|
+
has_one :user
|
82
|
+
end
|
83
|
+
|
84
|
+
A call to `User.generate` will also make a call to `Login.generate` so that
|
85
|
+
`User#login` is present, and therefore valid.
|
86
|
+
|
87
|
+
If all models were able to be created in a valid form by the default Model.new
|
88
|
+
call with no knowledge of the model itself, there'd be no need for Object
|
89
|
+
Daddy. So, when we deal with models which have validity requirements,
|
90
|
+
requiring fields which have format constraints, we need a means of expressing
|
91
|
+
how to create those models -- how to satisfy those validity constraints.
|
92
|
+
|
93
|
+
Object Daddy provides a `generator_for` method which allows the developer to
|
94
|
+
specify, for a specific model attribute, how to make a valid value. Note that
|
95
|
+
`validates_uniqueness_of` can require that, even if we make 100,000 instances
|
96
|
+
of a model that unique attributes cannot have the same values.
|
97
|
+
|
98
|
+
Object Daddy's `generator_for` method can take three main forms corresponding to
|
99
|
+
the means of finding a value for the associated attribute: a block, a method
|
100
|
+
call, or using a generator class.
|
101
|
+
|
102
|
+
class User < ActiveRecord::Base
|
103
|
+
validates_presence_of :email
|
104
|
+
validates_uniqueness_of :email
|
105
|
+
validates_format_of :email,
|
106
|
+
:with => /^[-a-z_+0-9.]+@(?:[-a-z_+0-9.]\.)+[a-z]+$/i
|
107
|
+
validates_presence_of :username
|
108
|
+
validates_format_of :username, :with => /^[a-z0-9_]{4,12}$/i
|
109
|
+
|
110
|
+
generator_for :email, :start => 'test@domain.com' do |prev|
|
111
|
+
user, domain = prev.split('@')
|
112
|
+
user.succ + '@' + domain
|
113
|
+
end
|
114
|
+
|
115
|
+
generator_for :username, :method => :next_user
|
116
|
+
|
117
|
+
generator_for :ssn, :class => SSNGenerator
|
118
|
+
|
119
|
+
def self.next_user
|
120
|
+
@last_username ||= 'testuser'
|
121
|
+
@last_username.succ
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class SSNGenerator
|
126
|
+
def self.next
|
127
|
+
@last ||= '000-00-0000'
|
128
|
+
@last = ("%09d" % (@last.gsub('-', '').to_i + 1)).sub(/^(\d{3})(\d{2})(\d{4})$/, '\1-\2-\3')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Note that the block method of invocation (as used with _:email_ above) takes an
|
133
|
+
optional _:start_ argument, to specify the value of that attribute on the first
|
134
|
+
run. The block will be called thereafter with the previous value of the
|
135
|
+
attribute and will generate the next attribute value to be used.
|
136
|
+
|
137
|
+
A simple default block is provided for any generator with a :start value.
|
138
|
+
|
139
|
+
class User < ActiveRecord::Base
|
140
|
+
generator_for :name, :start => 'Joe' do |prev|
|
141
|
+
prev.succ
|
142
|
+
end
|
143
|
+
|
144
|
+
generator_for :name, :start => 'Joe' # equivalent to the above
|
145
|
+
end
|
146
|
+
|
147
|
+
The _:method_ form takes a symbol naming a class method in the model class to be
|
148
|
+
called to generate a new value for the attribute in question. If the method
|
149
|
+
takes a single argument, it will act much like the block method of invocation,
|
150
|
+
being called with the previous value and generating the next.
|
151
|
+
|
152
|
+
The _:class_ form calls the .next class method on the named class to generate a
|
153
|
+
new value for the attribute in question.
|
154
|
+
|
155
|
+
The argument (previous value) to the block invocation form can be omitted if
|
156
|
+
it's going to be ignored, and simple invocation forms are provided for literal
|
157
|
+
values.
|
158
|
+
|
159
|
+
class User < ActiveRecord::Base
|
160
|
+
generator_for(:start_time) { Time.now }
|
161
|
+
generator_for :name, 'Joe'
|
162
|
+
generator_for :age => 25
|
163
|
+
end
|
164
|
+
|
165
|
+
The developer would then simply call `User.generate` when testing.
|
166
|
+
|
167
|
+
If some attribute values are known (or are being controlled during testing)
|
168
|
+
then these can simply be passed in to `.generate`:
|
169
|
+
|
170
|
+
@bad_login = Login.generate(:expiry => 1.week.ago)
|
171
|
+
@expired_user = User.generate(:login => @bad_login)
|
172
|
+
|
173
|
+
A `.generate!` method is also provided. The _generate/generate!_ pair of methods
|
174
|
+
can be thought of as analogs to create/create!, one merely providing an instance
|
175
|
+
that may or may not be valid and the other raising an exception if any
|
176
|
+
problem comes up.
|
177
|
+
|
178
|
+
Finally, a `.spawn` method is provided that only gives a new, unsaved object. Note
|
179
|
+
that this is the only method of the three that is available if you happen to be
|
180
|
+
using Object Daddy outside of Rails.
|
181
|
+
|
182
|
+
## Exemplars
|
183
|
+
|
184
|
+
In the examples given above we are using `generator_for` in the bodies of the
|
185
|
+
models themselves. Given that Object Daddy is primarily geared towards
|
186
|
+
annotating models with information useful for testing, we anticipate that
|
187
|
+
`generator_for` should not normally be included inline in models. Rather, we
|
188
|
+
will provide a place where model classes can be re-opened and `generator_for`
|
189
|
+
calls (and support methods) can be written without polluting the model files
|
190
|
+
with Object Daddy information.
|
191
|
+
|
192
|
+
Object Daddy, when installed as a Rails plugin, will create
|
193
|
+
*RAILS_ROOT/spec/exemplars/* as a place to hold __exemplar__ files for Rails model
|
194
|
+
classes. (We are seeking perhaps some better terminology)
|
195
|
+
|
196
|
+
An __exemplar__ for the User model would then be found in
|
197
|
+
*RAILS_ROOT/spec/exemplars/user_exemplar.rb* (when you are using a testing tool
|
198
|
+
which works from *RAILS_ROOT/test*, Object Daddy will create
|
199
|
+
*RAILS_ROOT/test/exemplars* and look for your exemplars in that directory
|
200
|
+
instead). Exemplar files are completely optional, and no model need have
|
201
|
+
exemplar files. The `.generate` method will still exist and be callable, and
|
202
|
+
`generator_for` can be declared in the model files themselves. If an exemplar
|
203
|
+
file is available when `.generate` is called on a model, the exemplar file will
|
204
|
+
be loaded and used. An example *user_exemplar.rb* appears below:
|
205
|
+
|
206
|
+
require 'ssn_generator'
|
207
|
+
|
208
|
+
class User < ActiveRecord::Base
|
209
|
+
generator_for :email, :start => 'test@domain.com' do |prev|
|
210
|
+
user, domain = prev.split('@')
|
211
|
+
user.succ + '@' + domain
|
212
|
+
end
|
213
|
+
|
214
|
+
generator_for :username, :method => :next_user
|
215
|
+
|
216
|
+
generator_for :ssn, :class => SSNGenerator
|
217
|
+
|
218
|
+
def self.next_user
|
219
|
+
@last_username ||= 'testuser'
|
220
|
+
@last_username.succ
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
## Blocks
|
225
|
+
|
226
|
+
The `spawn`, `generate` and `generate!` methods can all accept a block, to which
|
227
|
+
they'll yield the generated object. This provides a nice scoping mechanism in
|
228
|
+
your code examples. Consider:
|
229
|
+
|
230
|
+
describe "admin user" do
|
231
|
+
it "should be authorized to create company profiles"
|
232
|
+
admin_user = User.generate!
|
233
|
+
admin_user.activate!
|
234
|
+
admin_user.add_role("admin")
|
235
|
+
|
236
|
+
admin_user.should be_authorized(:create, Company)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
This could be refactored to:
|
241
|
+
|
242
|
+
describe "admin user" do
|
243
|
+
it "should be authorized to create company profiles" do
|
244
|
+
admin_user = User.generate! do |user|
|
245
|
+
user.activate!
|
246
|
+
user.add_role("admin")
|
247
|
+
end
|
248
|
+
|
249
|
+
admin_user.should be_authorized(:create, Company)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
Or:
|
254
|
+
|
255
|
+
describe "admin user" do
|
256
|
+
it "should be authorized to create company profiles"
|
257
|
+
User.generate! do |user|
|
258
|
+
user.activate!
|
259
|
+
user.add_role("admin")
|
260
|
+
end.should be_authorized(:create, Company)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
Or even:
|
265
|
+
|
266
|
+
describe "admin user" do
|
267
|
+
def admin_user
|
268
|
+
@admin_user ||= User.generate! do |user|
|
269
|
+
user.activate!
|
270
|
+
user.add_role("admin")
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should be authorized to create company profiles"
|
275
|
+
admin_user.should be_authorized(:create, Company)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
This last refactoring allows you to reuse the admin_user method across
|
280
|
+
multiple code examples, balancing DRY with local data.
|
281
|
+
|
282
|
+
## Object Daddy and Fixtures
|
283
|
+
|
284
|
+
While Object Daddy is meant to obviate the hellish devilspawn that are test
|
285
|
+
fixtures, Object Daddy should work alongside fixtures just fine. To each his
|
286
|
+
own, I suppose.
|
287
|
+
|
288
|
+
## Known Issues
|
289
|
+
|
290
|
+
The simple invocation forms for `generator_for` when using literal values do not
|
291
|
+
work if the literal value is a Hash. Don't do that.
|
292
|
+
|
293
|
+
class User < ActiveRecord::Base
|
294
|
+
generator_for :thing_hash, { 'some key' => 'some value' }
|
295
|
+
generator_for :other_hash => { 'other key' => 'other value' }
|
296
|
+
end
|
297
|
+
|
298
|
+
I'm not sure why this would even ever come up, but seriously, don't.
|
299
|
+
|
300
|
+
Required `belongs_to` associations are automatically generated when generating an instance,
|
301
|
+
but only if necessary.
|
302
|
+
|
303
|
+
class Category < ActiveRecord::Base
|
304
|
+
has_many :items
|
305
|
+
end
|
306
|
+
|
307
|
+
class Item < ActiveRecord::Base
|
308
|
+
belongs_to :category
|
309
|
+
validates_presence_of :category
|
310
|
+
end
|
311
|
+
|
312
|
+
`Item.generate` will generate a new category, but `some_category.items.generate` will not.
|
313
|
+
Unless, of course, you are foolish enough to define a generator in the exemplar.
|
314
|
+
|
315
|
+
class Item
|
316
|
+
generator_for(:category) { Category.generate }
|
317
|
+
end
|
318
|
+
|
319
|
+
Once again, don't do that.
|
320
|
+
|
321
|
+
## Rails _surprises_
|
322
|
+
|
323
|
+
Due to the way Rails handles associations, cascading generations (as a result of
|
324
|
+
required associations) are always generated-and-saved, even if the original generation
|
325
|
+
call was a mere `spawn` (`new`). This may come as a surprise, but it would probably be more
|
326
|
+
of a surprise if `User.spawn.save` and `User.generate` weren't comparable.
|
data/VERSION.yml
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init"
|
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
|
data/lib/object_daddy.rb
ADDED
@@ -0,0 +1,254 @@
|
|
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
|
37
|
+
args.each_pair do |attribute, value|
|
38
|
+
instance.send("#{attribute}=", value) # support setting of mass-assignment protected attributes
|
39
|
+
end
|
40
|
+
yield instance if block_given?
|
41
|
+
instance
|
42
|
+
end
|
43
|
+
|
44
|
+
# register a generator for an attribute of this class
|
45
|
+
# generator_for :foo do |prev| ... end
|
46
|
+
# generator_for :foo do ... end
|
47
|
+
# generator_for :foo, value
|
48
|
+
# generator_for :foo => value
|
49
|
+
# generator_for :foo, :class => GeneratorClass
|
50
|
+
# generator_for :foo, :method => :method_name
|
51
|
+
def generator_for(handle, args = {}, &block)
|
52
|
+
if handle.is_a?(Hash)
|
53
|
+
raise ArgumentError, "only specify one attr => value pair at a time" unless handle.keys.length == 1
|
54
|
+
gen_data = handle
|
55
|
+
handle = gen_data.keys.first
|
56
|
+
args = gen_data[handle]
|
57
|
+
end
|
58
|
+
|
59
|
+
raise ArgumentError, "an attribute name must be specified" unless handle = handle.to_sym
|
60
|
+
|
61
|
+
unless args.is_a?(Hash)
|
62
|
+
unless block
|
63
|
+
retval = args
|
64
|
+
block = lambda { retval } # lambda { args } results in returning the empty hash that args gets changed to
|
65
|
+
end
|
66
|
+
args = {} # args is assumed to be a hash for the rest of the method
|
67
|
+
end
|
68
|
+
|
69
|
+
if args[:start]
|
70
|
+
block ||= lambda { |prev| prev.succ }
|
71
|
+
end
|
72
|
+
|
73
|
+
if args[:method]
|
74
|
+
h = { :method => args[:method].to_sym }
|
75
|
+
h[:start] = args[:start] if args[:start]
|
76
|
+
record_generator_for(handle, h)
|
77
|
+
elsif args[:class]
|
78
|
+
raise ArgumentError, "generator class [#{args[:class].name}] does not have a :next method" unless args[:class].respond_to?(:next)
|
79
|
+
record_generator_for(handle, :class => args[:class])
|
80
|
+
elsif block
|
81
|
+
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
|
82
|
+
h = { :block => block }
|
83
|
+
h[:start] = args[:start] if args[:start]
|
84
|
+
record_generator_for(handle, h)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "a block, :class generator, :method generator, or value must be specified to generator_for"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def generates_subclass(subclass_name)
|
91
|
+
@concrete_subclass_name = subclass_name.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def gather_exemplars
|
95
|
+
return if exemplars_generated
|
96
|
+
|
97
|
+
self.generators ||= {}
|
98
|
+
if superclass.respond_to?(:gather_exemplars)
|
99
|
+
superclass.gather_exemplars
|
100
|
+
self.generators = (superclass.generators).merge(generators).dup
|
101
|
+
end
|
102
|
+
|
103
|
+
exemplar_path.each do |raw_path|
|
104
|
+
path = File.join(raw_path, "#{underscore(name)}_exemplar.rb")
|
105
|
+
load(path) if File.exists?(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
self.exemplars_generated = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def presence_validated_attributes
|
112
|
+
@presence_validated_attributes ||= {}
|
113
|
+
attrs = @presence_validated_attributes
|
114
|
+
if superclass.respond_to?(:presence_validated_attributes)
|
115
|
+
attrs = superclass.presence_validated_attributes.merge(attrs)
|
116
|
+
end
|
117
|
+
attrs
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
# we define an underscore helper ourselves since the ActiveSupport isn't available if we're not using Rails
|
123
|
+
def underscore(string)
|
124
|
+
string.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
125
|
+
end
|
126
|
+
|
127
|
+
def record_generator_for(handle, generator)
|
128
|
+
self.generators ||= {}
|
129
|
+
raise ArgumentError, "a generator for attribute [:#{handle}] has already been specified" if (generators[handle] || {})[:source] == self
|
130
|
+
generators[handle] = { :generator => generator, :source => self }
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def generate_values(args)
|
136
|
+
(generators || {}).each_pair do |handle, gen_data|
|
137
|
+
next if args.include?(handle) or args.include?(handle.to_s)
|
138
|
+
|
139
|
+
generator = gen_data[:generator]
|
140
|
+
if generator[:block]
|
141
|
+
process_generated_value(args, handle, generator, generator[:block])
|
142
|
+
elsif generator[:method]
|
143
|
+
method = method(generator[:method])
|
144
|
+
if method.arity == 1
|
145
|
+
process_generated_value(args, handle, generator, method)
|
146
|
+
else
|
147
|
+
args[handle] = method.call
|
148
|
+
end
|
149
|
+
elsif generator[:class]
|
150
|
+
args[handle] = generator[:class].next
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
generate_missing(args)
|
155
|
+
end
|
156
|
+
|
157
|
+
def process_generated_value(args, handle, generator, block)
|
158
|
+
if generator[:start]
|
159
|
+
value = generator[:start]
|
160
|
+
generator.delete(:start)
|
161
|
+
else
|
162
|
+
if block.arity == 0
|
163
|
+
value = block.call
|
164
|
+
else
|
165
|
+
value = block.call(generator[:prev])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
generator[:prev] = args[handle] = value
|
169
|
+
end
|
170
|
+
|
171
|
+
def generate_missing(args)
|
172
|
+
if presence_validated_attributes and !presence_validated_attributes.empty?
|
173
|
+
req = {}
|
174
|
+
(presence_validated_attributes.keys - args.keys).each {|a| req[a.to_s] = true } # find attributes required by validates_presence_of not already set
|
175
|
+
|
176
|
+
belongs_to_associations = reflect_on_all_associations(:belongs_to).to_a
|
177
|
+
missing = belongs_to_associations.select { |a| req[a.name.to_s] or req[a.primary_key_name.to_s] }
|
178
|
+
if create_scope = scope(:create)
|
179
|
+
missing.reject! { |a| create_scope.include?(a.primary_key_name) }
|
180
|
+
end
|
181
|
+
missing.reject! { |a| [a.name, a.primary_key_name].any? { |n| args.stringify_keys.include?(n.to_s) } }
|
182
|
+
missing.each {|a| args[a.name] = a.class_name.constantize.generate }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
module RailsClassMethods
|
188
|
+
def exemplar_path
|
189
|
+
paths = ['spec', 'test'].inject([]) do |array, dir|
|
190
|
+
if File.directory?(File.join(RAILS_ROOT, dir))
|
191
|
+
array << File.join(RAILS_ROOT, dir, 'exemplars')
|
192
|
+
end
|
193
|
+
array
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def validates_presence_of_with_object_daddy(*attr_names)
|
198
|
+
@presence_validated_attributes ||= {}
|
199
|
+
new_attr = attr_names.dup
|
200
|
+
new_attr.pop if new_attr.last.is_a?(Hash)
|
201
|
+
new_attr.each {|a| @presence_validated_attributes[a] = true }
|
202
|
+
validates_presence_of_without_object_daddy(*attr_names)
|
203
|
+
end
|
204
|
+
|
205
|
+
# :call-seq:
|
206
|
+
# generate()
|
207
|
+
# generate() do |obj| ... end
|
208
|
+
# generate(args)
|
209
|
+
# generate(args) do |obj| ... end
|
210
|
+
#
|
211
|
+
# Creates and tries to save an instance of this class, using any known
|
212
|
+
# generators. The generated instance is yielded to a block if provided.
|
213
|
+
#
|
214
|
+
# This will not raise errors on a failed save. Use generate! if you
|
215
|
+
# want errors raised.
|
216
|
+
def generate(args = {})
|
217
|
+
spawn(args) do |instance|
|
218
|
+
instance.save
|
219
|
+
yield instance if block_given?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# :call-seq:
|
224
|
+
# generate()
|
225
|
+
# generate() do |obj| ... end
|
226
|
+
# generate(args)
|
227
|
+
# generate(args) do |obj| ... end
|
228
|
+
#
|
229
|
+
# Creates and tries to save! an instance of this class, using any known
|
230
|
+
# generators. The generated instance is yielded to a block if provided.
|
231
|
+
#
|
232
|
+
# This will raise errors on a failed save. Use generate if you do not want
|
233
|
+
# errors raised.
|
234
|
+
def generate!(args = {})
|
235
|
+
spawn(args) do |instance|
|
236
|
+
instance.save!
|
237
|
+
yield instance if block_given?
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
unless ActiveRecord::Base.respond_to? :inherited_with_object_daddy
|
244
|
+
class ActiveRecord::Base
|
245
|
+
def self.inherited_with_object_daddy(subclass)
|
246
|
+
self.inherited_without_object_daddy(subclass)
|
247
|
+
subclass.send(:include, ObjectDaddy) unless subclass < ObjectDaddy
|
248
|
+
end
|
249
|
+
|
250
|
+
class << self
|
251
|
+
alias_method_chain :inherited, :object_daddy
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|