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 ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
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 *factory_girl*, *fabrication*, or other factories.
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
- Templates are not intended to replace most factories. They are most useful when:
11
- - Subjects 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)
13
- - You'd prefer short-hand notation, or to abstract Factory implementation out of your tests
14
- - e.g. Define a set of generic factories other programmers can use, without requiring them to understand the underlying details
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 simplify our definitions:
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.app :facebook, :twitter } }
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(user_group[:desktop_user].can_network_with(user_group[:tablet_user], user_group[:phone_user]).to be_true }
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
+ }