olfactory 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/README.md +432 -11
- data/lib/olfactory/template.rb +200 -42
- data/lib/olfactory/template_definition.rb +93 -28
- data/lib/olfactory/version.rb +1 -1
- data/lib/olfactory.rb +5 -0
- data/olfactory.gemspec +4 -4
- data/spec/olfactory/template_spec.rb +1080 -0
- data/spec/spec_helper.rb +8 -3
- data/spec/support/saveable.rb +14 -0
- metadata +21 -16
data/.rspec
ADDED
data/README.md
CHANGED
@@ -3,15 +3,16 @@ Olfactory
|
|
3
3
|
|
4
4
|
### Introduction
|
5
5
|
|
6
|
-
Olfactory is a factory extension for creating complex object sets, as a supplement to
|
6
|
+
Olfactory is a factory extension for creating complex object sets, as a supplement to `factory_girl`, `fabrication`, or other factories.
|
7
7
|
|
8
|
-
It introduces the concept of **templates**: an abstract group of objects. These can be used to make test setup much quicker and easier.
|
8
|
+
It introduces the concept of **templates**: an abstract group of objects. You define what objects (or other templates) your template can contain (not unlike a factory), then you can create instances of it by invoking the build or create functions. These templates can be used to make test setup much quicker and easier. Templates are not intended to replace factories, but bridge the gap where `factory_girl` and `fabrication` factories fall short.
|
9
9
|
|
10
|
-
|
11
|
-
-
|
12
|
-
- You require a collection of objects that are not directly related (or easily built using factories)
|
13
|
-
- You'd prefer short-hand notation
|
14
|
-
|
10
|
+
They are most useful when:
|
11
|
+
- Your models are weakly related or non-relational (e.g. not joined by ActiveRecord associations)
|
12
|
+
- You require a collection of objects that are not directly related (or easily built using factories & associations)
|
13
|
+
- You'd prefer short-hand notation
|
14
|
+
- You'd like to abstract Factory implementation out of your tests
|
15
|
+
- e.g. Define a set of generic factories other programmers can use, without requiring them to understand the underlying class or relational structure.
|
15
16
|
|
16
17
|
For example, given:
|
17
18
|
```ruby
|
@@ -35,17 +36,437 @@ end
|
|
35
36
|
|
36
37
|
This is a lot of setup for a simple test case. We could write a bunch of named factories, but then the test logic simply ends up in a factory file rather than our test; not good.
|
37
38
|
|
38
|
-
Instead, we can use templates to
|
39
|
+
Instead, we can use templates to define shorthand notation:
|
39
40
|
```ruby
|
40
41
|
context "networkable people" do
|
41
42
|
let(:user_group) do
|
42
43
|
Olfactory.create_template :user_group do |group|
|
43
|
-
group.user :desktop_user { |user| user.phone { |phone| phone.
|
44
|
+
group.user :desktop_user { |user| user.phone { |phone| phone.apps :facebook, :twitter } }
|
44
45
|
group.user :tablet_user { |user| user.tablet { |tablet| tablet.app :facebook } }
|
45
46
|
group.user :phone_user { |user| user.desktop { |desktop| desktop.app :twitter } }
|
46
47
|
end
|
47
48
|
end
|
48
|
-
|
49
|
-
it { expect(
|
49
|
+
subject { user_group[:users] }
|
50
|
+
it { expect(subject[:desktop_user].can_network_with(subject[:tablet_user], subject[:phone_user]).to be_true }
|
50
51
|
end
|
51
52
|
```
|
53
|
+
|
54
|
+
### Usage
|
55
|
+
|
56
|
+
##### Defining templates
|
57
|
+
|
58
|
+
Templates are defined in `spec/templates/**/*.rb` files. Define a template using the `Olfactory#template` method.
|
59
|
+
|
60
|
+
Olfactory.template :computer do |t|
|
61
|
+
...
|
62
|
+
end
|
63
|
+
|
64
|
+
Once defined, these templates can be instantiated using `build_template` and `create_template`, which are analogous to the same `factory_girl`/`fabrication` methods
|
65
|
+
|
66
|
+
Olfactory.build_template :computer # Creates objects, but does not implictly save them
|
67
|
+
Olfactory.create_template :computer # Creates objects, and attempts to save all items that respond to #save!
|
68
|
+
|
69
|
+
##### #has_one
|
70
|
+
|
71
|
+
Defines a field containing a single object.
|
72
|
+
|
73
|
+
Definition:
|
74
|
+
> **has_one** :name *[, :alias => alias_name]*
|
75
|
+
|
76
|
+
- `:alias` defines an alias keyword for setting the item value.
|
77
|
+
|
78
|
+
When using:
|
79
|
+
> **name|alias_name** object|&block
|
80
|
+
|
81
|
+
Sample:
|
82
|
+
|
83
|
+
# Template definition
|
84
|
+
Olfactory.template :computer do |t|
|
85
|
+
t.has_one :keyboard
|
86
|
+
t.has_one :mouse
|
87
|
+
t.has_one :cpu, :alias => :processor
|
88
|
+
end
|
89
|
+
# Build instance of template
|
90
|
+
Olfactory.build_template :computer do |c|
|
91
|
+
c.keyboard "X4 Sidewinder"
|
92
|
+
c.mouse { FactoryGirl::build(:mouse) }
|
93
|
+
c.processor "Intel Xeon"
|
94
|
+
end
|
95
|
+
# Result
|
96
|
+
{
|
97
|
+
:keyboard => "X4 Sidewinder",
|
98
|
+
:mouse => <Mouse>,
|
99
|
+
:cpu => "Intel Xeon"
|
100
|
+
}
|
101
|
+
|
102
|
+
##### #has_many
|
103
|
+
|
104
|
+
Defines a collection of objects. Each invocation appends the resulting items to the collection. Collection is `nil` by default (if no items are added.)
|
105
|
+
|
106
|
+
Definition:
|
107
|
+
> **has_many** :name *[, :alias => alias_name,
|
108
|
+
> :singular => singular_name,
|
109
|
+
> :named => true|false]*
|
110
|
+
|
111
|
+
- `:alias` defines an alias keyword for adding mutiple items to the collection.
|
112
|
+
- `:singular` defines the keyword for adding singular items to the collection.
|
113
|
+
- `:named` converts the collection to a `Hash`. When true, all invocations must provide a name (first argument.)
|
114
|
+
|
115
|
+
When using as generic collection (`named == false`):
|
116
|
+
> Singular:
|
117
|
+
>
|
118
|
+
> **singular_name** Object
|
119
|
+
>
|
120
|
+
> **singular_name** { &block }
|
121
|
+
>
|
122
|
+
> Plural:
|
123
|
+
>
|
124
|
+
> **name|alias_name** quantity:Integer { &block }
|
125
|
+
>
|
126
|
+
> **name|alias_name** objects:Array
|
127
|
+
>
|
128
|
+
> **name|alias_name** object1, object2...
|
129
|
+
|
130
|
+
When using as a named collection(`named == true`):
|
131
|
+
> Singular:
|
132
|
+
>
|
133
|
+
> **singular_name** variable_name, Object
|
134
|
+
>
|
135
|
+
> **singular_name** variable_name { &block }
|
136
|
+
>
|
137
|
+
> Plural:
|
138
|
+
>
|
139
|
+
> **name|alias_name** Hash
|
140
|
+
|
141
|
+
Sample:
|
142
|
+
|
143
|
+
# Template definition
|
144
|
+
Olfactory.template :computer do |t|
|
145
|
+
# Generic
|
146
|
+
t.has_many :cpus
|
147
|
+
t.has_many :memory_sticks, :singular => :memory_stick
|
148
|
+
|
149
|
+
t.has_many :usb_ports
|
150
|
+
|
151
|
+
# Named
|
152
|
+
t.has_many :drives, :singular => :drive, :named => true
|
153
|
+
end
|
154
|
+
# Build instance of template
|
155
|
+
Olfactory.build_template :computer do |c|
|
156
|
+
# Generic
|
157
|
+
c.cpus "Intel i7", "Onboard graphics"
|
158
|
+
c.memory_stick "2GB"
|
159
|
+
c.memory_stick "2GB"
|
160
|
+
c.usb_ports 2 do
|
161
|
+
"2.0"
|
162
|
+
end
|
163
|
+
c.usb_ports ["2.0", "2.0"]
|
164
|
+
c.usb_ports "3.0", "3.0"
|
165
|
+
|
166
|
+
# Named
|
167
|
+
c.drive :ssd "Samsung"
|
168
|
+
c.drives { :hdd => "Seagate", :optical => "Memorex" }
|
169
|
+
end
|
170
|
+
# Result
|
171
|
+
{
|
172
|
+
:cpus => ["Intel i7", "Onboard graphics"],
|
173
|
+
:memory_sticks => ["2GB", "2GB"],
|
174
|
+
:drives => {
|
175
|
+
:ssd => "Samsung",
|
176
|
+
:hdd => "Seagate",
|
177
|
+
:optical => "Memorex"
|
178
|
+
},
|
179
|
+
:usb_ports => ["2.0", "2.0", "2.0", "2.0", "3.0", "3.0"]
|
180
|
+
}
|
181
|
+
|
182
|
+
##### #embeds_one
|
183
|
+
|
184
|
+
Defines a field containing an embedded template.
|
185
|
+
|
186
|
+
Definition:
|
187
|
+
> **embeds_one** :name *[, :alias => alias_name, :template => template_name]*
|
188
|
+
|
189
|
+
- `:alias` defines an alias keyword for setting the embedded template value.
|
190
|
+
- `:template` defines the actual name of the template used, if it does not match the name.
|
191
|
+
|
192
|
+
When using:
|
193
|
+
> **name|alias_name** *[preset_name, { &block }]*
|
194
|
+
|
195
|
+
Sample:
|
196
|
+
|
197
|
+
# Template definition
|
198
|
+
Olfactory.template :computer do |t|
|
199
|
+
t.embeds_one :cpu
|
200
|
+
t.embeds_one :gpu, :template => :cpu
|
201
|
+
end
|
202
|
+
Olfactory.template :cpu do |t|
|
203
|
+
t.has_many :cores
|
204
|
+
preset :amd do |p|
|
205
|
+
p.cores "AMD Core", "AMD Core"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
# Build instance of template
|
209
|
+
Olfactory.build_template :computer do |computer|
|
210
|
+
computer.cpu do |cpu|
|
211
|
+
cpu.cores "Intel Core", "Intel Core"
|
212
|
+
end
|
213
|
+
computer.gpu :amd
|
214
|
+
end
|
215
|
+
# Result
|
216
|
+
{
|
217
|
+
:cpu => {
|
218
|
+
:cpu_core => ["Intel Core", "Intel Core"]
|
219
|
+
},
|
220
|
+
:gpu => {
|
221
|
+
:gpu_core => ["AMD Core", "AMD Core"]
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
##### #embeds_many
|
226
|
+
|
227
|
+
Defines a collection of templates. Each invocation appends the resulting templates to the collection. Collection is `nil` by default (if no templates are added.)
|
228
|
+
|
229
|
+
Definition:
|
230
|
+
> **has_many** :name *[, :alias => alias_name,
|
231
|
+
> :template => template_name,
|
232
|
+
> :singular => singular_name,
|
233
|
+
> :named => true|false]*
|
234
|
+
|
235
|
+
- `:alias` defines an alias keyword for adding mutiple templates to the collection.
|
236
|
+
- `:template` defines the actual name of the template used, if it does not match the name.
|
237
|
+
- `:singular` defines the keyword for adding singular templates to the collection.
|
238
|
+
- `:named` converts the collection to a `Hash`. When true, all invocations must provide a name (first argument.)
|
239
|
+
|
240
|
+
When using as generic collection (`named == false`):
|
241
|
+
> Singular:
|
242
|
+
>
|
243
|
+
> **singular_name**
|
244
|
+
>
|
245
|
+
> **singular_name** preset_name
|
246
|
+
>
|
247
|
+
> **singular_name** { &block }
|
248
|
+
>
|
249
|
+
> Plural:
|
250
|
+
>
|
251
|
+
> **name|alias_name** quantity:Integer
|
252
|
+
>
|
253
|
+
> **name|alias_name** preset_name, quantity:Integer
|
254
|
+
>
|
255
|
+
> **name|alias_name** quantity:Integer, preset_name
|
256
|
+
>
|
257
|
+
> **name|alias_name** quantity:Integer { &block }
|
258
|
+
|
259
|
+
When using as a named collection(`named == true`):
|
260
|
+
> Singular:
|
261
|
+
>
|
262
|
+
> **singular_name** variable_name
|
263
|
+
>
|
264
|
+
> **singular_name** variable_name, preset_name
|
265
|
+
>
|
266
|
+
> **singular_name** variable_name { &block }
|
267
|
+
>
|
268
|
+
> Plural:
|
269
|
+
>
|
270
|
+
> (None)
|
271
|
+
|
272
|
+
Sample:
|
273
|
+
|
274
|
+
# Template definition
|
275
|
+
Olfactory.template :computer do |t|
|
276
|
+
# Generic
|
277
|
+
t.embeds_many :cpus, :singular => :cpu
|
278
|
+
t.embeds_many :drives, :singular => :drive, :named => true
|
279
|
+
end
|
280
|
+
Olfactory.template :cpu do |t|
|
281
|
+
# Generic
|
282
|
+
t.has_many :cores, :singular => :core
|
283
|
+
t.preset :amd do |p|
|
284
|
+
p.cores "AMD Core", "AMD Core"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
Olfactory.template :drive do |t|
|
288
|
+
# Generic
|
289
|
+
t.has_one :storage_size
|
290
|
+
t.has_one :manufacturer
|
291
|
+
t.preset :samsung_512gb do |p|
|
292
|
+
p.storage_size 512000
|
293
|
+
p.manufacturer "Samsung"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
# Build instance of template
|
297
|
+
Olfactory.build_template :computer do |c|
|
298
|
+
# Generic
|
299
|
+
computer.cpu :amd
|
300
|
+
computer.cpu do |cpu|
|
301
|
+
cpu.cores "Intel Core", "Intel Core"
|
302
|
+
end
|
303
|
+
computer.cpus 2 # Creates 2 :cpu templates with defaults
|
304
|
+
computer.cpus :amd, 2
|
305
|
+
computer.cpus 2, :amd
|
306
|
+
computer.cpus 2 do |cpu|
|
307
|
+
cpu.cores "Intel Core", "Intel Core"
|
308
|
+
end
|
309
|
+
|
310
|
+
# Named
|
311
|
+
computer.drive :hdd # Creates :drive template with defaults
|
312
|
+
computer.drive :ssd, :samsung_512gb
|
313
|
+
computer.drive :optical do |drive|
|
314
|
+
drive.manufacturer "Memorex"
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
##### #preset
|
319
|
+
|
320
|
+
Defines a preset of values.
|
321
|
+
|
322
|
+
Definition:
|
323
|
+
> **preset** :name { |instance| &block }
|
324
|
+
|
325
|
+
When using:
|
326
|
+
> Olfactory.build_template template_name, :preset => preset_name, :quantity => quantity:Integer
|
327
|
+
|
328
|
+
See above sections for usage within templates.
|
329
|
+
|
330
|
+
Sample:
|
331
|
+
|
332
|
+
# Template definition
|
333
|
+
Olfactory.template :computer do |t|
|
334
|
+
t.embeds_many :cpus
|
335
|
+
t.preset :dual_core do |p|
|
336
|
+
p.cpus 2 do |cpu|
|
337
|
+
cpu.core "Intel Core"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
Olfactory.template :cpu do |t|
|
342
|
+
t.has_one :cpu_core
|
343
|
+
end
|
344
|
+
# Build instance of template
|
345
|
+
Olfactory.build_template :computer, :preset => :dual_core
|
346
|
+
# Result
|
347
|
+
{
|
348
|
+
:cpus => [{
|
349
|
+
:cpu_core => "Intel Core"
|
350
|
+
},
|
351
|
+
{
|
352
|
+
:cpu_core => "Intel Core"
|
353
|
+
}]
|
354
|
+
}
|
355
|
+
|
356
|
+
##### #macro
|
357
|
+
|
358
|
+
Similar to a preset, `macro` defines a 'function' that can be used within a template, which can accept parameters, and set variables or invoke code. The result of this macro will *not* be stored on the resulting template.
|
359
|
+
|
360
|
+
Definition:
|
361
|
+
> **macro** :name { |instance, *args..| &block }
|
362
|
+
|
363
|
+
When using:
|
364
|
+
> **name** arg1, arg2...
|
365
|
+
|
366
|
+
Sample:
|
367
|
+
|
368
|
+
# Template defintion
|
369
|
+
Olfactory.template :computer do |t|
|
370
|
+
t.has_one :memory_size
|
371
|
+
t.macro :memory_sticks do |d, num|
|
372
|
+
d.memory_size "{num*4}GB"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
# Build instance of template
|
376
|
+
Olfactory.build_template :computer do |c|
|
377
|
+
c.memory_sticks 2
|
378
|
+
end
|
379
|
+
# Result
|
380
|
+
{
|
381
|
+
:memory_size => "8GB"
|
382
|
+
}
|
383
|
+
|
384
|
+
##### #transient
|
385
|
+
|
386
|
+
Similar to `factory_girl`'s transients, `transient` defines a temporary variable. You can store values in here to compose conditional logic or more sophisticated templates. When a template contains an embedded template, it will pass down all of its transients to the embedded template. Invoking `transients` on an instance of a template will return a hash of its transient variables.
|
387
|
+
|
388
|
+
Usage:
|
389
|
+
> **transient** name, Object
|
390
|
+
>
|
391
|
+
> **transients**[name]
|
392
|
+
|
393
|
+
Sample:
|
394
|
+
|
395
|
+
# Template defintion
|
396
|
+
Olfactory.template :computer do |t|
|
397
|
+
t.has_one :memory_size
|
398
|
+
t.embeds_one :cpu
|
399
|
+
t.macro :memory_sticks do |d, num|
|
400
|
+
d.memory_size "{num*(d.transients[:memory_stick_size] || 4)}GB"
|
401
|
+
end
|
402
|
+
t.macro :memory_stick_size do |m, size|
|
403
|
+
t.transient :memory_stick_size, size
|
404
|
+
end
|
405
|
+
end
|
406
|
+
Olfactory.template :cpu do |t|
|
407
|
+
t.has_one :available_memory
|
408
|
+
end
|
409
|
+
# Build instance of template
|
410
|
+
Olfactory.build_template :computer do |c|
|
411
|
+
c.memory_stick_size 2
|
412
|
+
c.memory_sticks 2
|
413
|
+
c.cpu do |cpu|
|
414
|
+
cpu.memory_module_size "#{cpu.transients[:memory_stick_size]}GB"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
# Result
|
418
|
+
{
|
419
|
+
:memory_size => "4GB",
|
420
|
+
:cpu => {
|
421
|
+
:memory_module_size => "2GB"
|
422
|
+
}
|
423
|
+
}
|
424
|
+
|
425
|
+
##### Defaults: #before & #after
|
426
|
+
|
427
|
+
Defines default values, which are used to fill in any empty `has`, `embeds` or `transient` fields, before and after respectively. They will *not* overwrite any non-nil value.
|
428
|
+
|
429
|
+
Definition:
|
430
|
+
> **before** { |instance| &block }
|
431
|
+
>
|
432
|
+
> **after** { |instance| &block }
|
433
|
+
|
434
|
+
Sample:
|
435
|
+
|
436
|
+
# Template defintion
|
437
|
+
Olfactory.template :computer do |t|
|
438
|
+
t.has_one :cpu
|
439
|
+
t.has_one :memory_size
|
440
|
+
t.before do |d|
|
441
|
+
d.cpu "Intel Xeon"
|
442
|
+
d.memory_size "4GB"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
# Build instance of template
|
446
|
+
Olfactory.build_template :computer do |c|
|
447
|
+
c.cpu "AMD Athlon"
|
448
|
+
end
|
449
|
+
# Result
|
450
|
+
{
|
451
|
+
:cpu => "AMD Athlon",
|
452
|
+
:memory_size => "4GB"
|
453
|
+
}
|
454
|
+
|
455
|
+
# Template defintion
|
456
|
+
Olfactory.template :phone do |t|
|
457
|
+
t.has_one :cpu
|
458
|
+
t.has_one :memory_size
|
459
|
+
t.after do |d|
|
460
|
+
d.cpu "ARM"
|
461
|
+
d.memory_size "2GB"
|
462
|
+
end
|
463
|
+
end
|
464
|
+
# Build instance of template
|
465
|
+
Olfactory.build_template :phone do |c|
|
466
|
+
c.memory_size "1GB"
|
467
|
+
end
|
468
|
+
# Result
|
469
|
+
{
|
470
|
+
:cpu => "ARM",
|
471
|
+
:memory_size => "1GB"
|
472
|
+
}
|