daru 0.1.3.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -1
  4. data/.rspec_formatter.rb +33 -0
  5. data/.rubocop.yml +26 -2
  6. data/History.md +38 -0
  7. data/README.md +22 -13
  8. data/Rakefile +50 -2
  9. data/benchmarks/csv_reading.rb +22 -0
  10. data/daru.gemspec +9 -2
  11. data/lib/daru.rb +36 -4
  12. data/lib/daru/accessors/array_wrapper.rb +6 -1
  13. data/lib/daru/accessors/dataframe_by_row.rb +10 -2
  14. data/lib/daru/accessors/gsl_wrapper.rb +1 -3
  15. data/lib/daru/accessors/nmatrix_wrapper.rb +9 -0
  16. data/lib/daru/category.rb +935 -0
  17. data/lib/daru/core/group_by.rb +29 -38
  18. data/lib/daru/core/merge.rb +186 -145
  19. data/lib/daru/core/query.rb +22 -11
  20. data/lib/daru/dataframe.rb +976 -885
  21. data/lib/daru/date_time/index.rb +166 -166
  22. data/lib/daru/date_time/offsets.rb +66 -77
  23. data/lib/daru/formatters/table.rb +54 -0
  24. data/lib/daru/helpers/array.rb +40 -0
  25. data/lib/daru/index.rb +476 -73
  26. data/lib/daru/io/io.rb +66 -45
  27. data/lib/daru/io/sql_data_source.rb +33 -62
  28. data/lib/daru/iruby/helpers.rb +38 -0
  29. data/lib/daru/iruby/templates/dataframe.html.erb +52 -0
  30. data/lib/daru/iruby/templates/dataframe_mi.html.erb +58 -0
  31. data/lib/daru/iruby/templates/multi_index.html.erb +12 -0
  32. data/lib/daru/iruby/templates/vector.html.erb +27 -0
  33. data/lib/daru/iruby/templates/vector_mi.html.erb +36 -0
  34. data/lib/daru/maths/arithmetic/dataframe.rb +16 -18
  35. data/lib/daru/maths/arithmetic/vector.rb +4 -6
  36. data/lib/daru/maths/statistics/dataframe.rb +8 -15
  37. data/lib/daru/maths/statistics/vector.rb +120 -98
  38. data/lib/daru/monkeys.rb +12 -40
  39. data/lib/daru/plotting/gruff.rb +3 -0
  40. data/lib/daru/plotting/gruff/category.rb +49 -0
  41. data/lib/daru/plotting/gruff/dataframe.rb +91 -0
  42. data/lib/daru/plotting/gruff/vector.rb +57 -0
  43. data/lib/daru/plotting/nyaplot.rb +3 -0
  44. data/lib/daru/plotting/nyaplot/category.rb +34 -0
  45. data/lib/daru/plotting/nyaplot/dataframe.rb +187 -0
  46. data/lib/daru/plotting/nyaplot/vector.rb +46 -0
  47. data/lib/daru/vector.rb +694 -421
  48. data/lib/daru/version.rb +1 -1
  49. data/profile/_base.rb +23 -0
  50. data/profile/df_to_a.rb +10 -0
  51. data/profile/filter.rb +13 -0
  52. data/profile/joining.rb +13 -0
  53. data/profile/sorting.rb +12 -0
  54. data/profile/vector_each_with_index.rb +9 -0
  55. data/spec/accessors/wrappers_spec.rb +2 -4
  56. data/spec/categorical_spec.rb +1734 -0
  57. data/spec/core/group_by_spec.rb +52 -2
  58. data/spec/core/merge_spec.rb +63 -2
  59. data/spec/core/query_spec.rb +236 -80
  60. data/spec/dataframe_spec.rb +1373 -79
  61. data/spec/date_time/data_spec.rb +3 -5
  62. data/spec/date_time/index_spec.rb +154 -17
  63. data/spec/date_time/offsets_spec.rb +3 -4
  64. data/spec/fixtures/empties.dat +2 -0
  65. data/spec/fixtures/strings.dat +2 -0
  66. data/spec/formatters/table_formatter_spec.rb +99 -0
  67. data/spec/helpers_spec.rb +8 -0
  68. data/spec/index/categorical_index_spec.rb +168 -0
  69. data/spec/index/index_spec.rb +283 -0
  70. data/spec/index/multi_index_spec.rb +570 -0
  71. data/spec/io/io_spec.rb +31 -4
  72. data/spec/io/sql_data_source_spec.rb +0 -1
  73. data/spec/iruby/dataframe_spec.rb +172 -0
  74. data/spec/iruby/helpers_spec.rb +49 -0
  75. data/spec/iruby/multi_index_spec.rb +37 -0
  76. data/spec/iruby/vector_spec.rb +107 -0
  77. data/spec/math/arithmetic/dataframe_spec.rb +71 -13
  78. data/spec/math/arithmetic/vector_spec.rb +8 -10
  79. data/spec/math/statistics/dataframe_spec.rb +3 -5
  80. data/spec/math/statistics/vector_spec.rb +45 -55
  81. data/spec/monkeys_spec.rb +32 -9
  82. data/spec/plotting/dataframe_spec.rb +386 -0
  83. data/spec/plotting/vector_spec.rb +230 -0
  84. data/spec/shared/vector_display_spec.rb +215 -0
  85. data/spec/spec_helper.rb +23 -0
  86. data/spec/vector_spec.rb +905 -138
  87. metadata +143 -11
  88. data/.rubocop_todo.yml +0 -44
  89. data/lib/daru/plotting/dataframe.rb +0 -104
  90. data/lib/daru/plotting/vector.rb +0 -38
  91. data/spec/daru_spec.rb +0 -58
  92. data/spec/index_spec.rb +0 -375
@@ -0,0 +1,570 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Daru::MultiIndex do
4
+ before(:each) do
5
+ @index_tuples = [
6
+ [:a,:one,:bar],
7
+ [:a,:one,:baz],
8
+ [:a,:two,:bar],
9
+ [:a,:two,:baz],
10
+ [:b,:one,:bar],
11
+ [:b,:two,:bar],
12
+ [:b,:two,:baz],
13
+ [:b,:one,:foo],
14
+ [:c,:one,:bar],
15
+ [:c,:one,:baz],
16
+ [:c,:two,:foo],
17
+ [:c,:two,:bar]
18
+ ]
19
+ @multi_mi = Daru::MultiIndex.from_tuples(@index_tuples)
20
+ end
21
+
22
+ context ".initialize" do
23
+ it "accepts labels and levels as arguments" do
24
+ mi = Daru::MultiIndex.new(
25
+ levels: [[:a,:b,:c], [:one, :two]],
26
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]])
27
+
28
+ expect(mi[:a, :two]).to eq(1)
29
+ end
30
+
31
+ it "raises error for wrong number of labels or levels" do
32
+ expect {
33
+ Daru::MultiIndex.new(
34
+ levels: [[:a,:a,:b,:b,:c,:c], [:one, :two]],
35
+ labels: [[0,0,1,1,2,2]])
36
+ }.to raise_error
37
+ end
38
+ end
39
+
40
+ context ".from_tuples" do
41
+ it "creates 2 layer MultiIndex from tuples" do
42
+ tuples = [
43
+ [:a, :one],
44
+ [:a, :two],
45
+ [:b, :one],
46
+ [:b, :two],
47
+ [:c, :one],
48
+ [:c, :two]
49
+ ]
50
+ mi = Daru::MultiIndex.from_tuples(tuples)
51
+ expect(mi.levels).to eq([[:a, :b, :c], [:one,:two]])
52
+ expect(mi.labels).to eq([[0,0,1,1,2,2], [0,1,0,1,0,1]])
53
+ end
54
+
55
+ it "creates a triple layer MultiIndex from tuples" do
56
+ expect(@multi_mi.levels).to eq([[:a,:b,:c], [:one, :two],[:bar,:baz,:foo]])
57
+ expect(@multi_mi.labels).to eq([
58
+ [0,0,0,0,1,1,1,1,2,2,2,2],
59
+ [0,0,1,1,0,1,1,0,0,0,1,1],
60
+ [0,1,0,1,0,0,1,2,0,1,2,0]
61
+ ])
62
+ end
63
+ end
64
+
65
+ context '.try_from_tuples' do
66
+ it 'creates MultiIndex, if there are tuples' do
67
+ tuples = [
68
+ [:a, :one],
69
+ [:a, :two],
70
+ [:b, :one],
71
+ [:b, :two],
72
+ [:c, :one],
73
+ [:c, :two]
74
+ ]
75
+ mi = Daru::MultiIndex.try_from_tuples(tuples)
76
+ expect(mi).to be_a Daru::MultiIndex
77
+ end
78
+
79
+ it 'returns nil, if MultiIndex can not be created' do
80
+ mi = Daru::MultiIndex.try_from_tuples([:a, :b, :c])
81
+ expect(mi).to be_nil
82
+ end
83
+ end
84
+
85
+ context "#size" do
86
+ it "returns size of MultiIndex" do
87
+ expect(@multi_mi.size).to eq(12)
88
+ end
89
+ end
90
+
91
+ context "#[]" do
92
+ it "returns the row number when specifying the complete tuple" do
93
+ expect(@multi_mi[:a, :one, :baz]).to eq(1)
94
+ end
95
+
96
+ it "returns MultiIndex when specifying incomplete tuple" do
97
+ expect(@multi_mi[:b]).to eq(Daru::MultiIndex.from_tuples([
98
+ [:b,:one,:bar],
99
+ [:b,:two,:bar],
100
+ [:b,:two,:baz],
101
+ [:b,:one,:foo]
102
+ ]))
103
+ expect(@multi_mi[:b, :one]).to eq(Daru::MultiIndex.from_tuples([
104
+ [:b,:one,:bar],
105
+ [:b,:one,:foo]
106
+ ]))
107
+ # TODO: Return Daru::Index if a single layer of indexes is present.
108
+ end
109
+
110
+ it "returns MultiIndex when specifying wholly numeric ranges" do
111
+ expect(@multi_mi[3..6]).to eq(Daru::MultiIndex.from_tuples([
112
+ [:a,:two,:baz],
113
+ [:b,:one,:bar],
114
+ [:b,:two,:bar],
115
+ [:b,:two,:baz]
116
+ ]))
117
+ end
118
+
119
+ it "raises error when specifying invalid index" do
120
+ expect { @multi_mi[:a, :three] }.to raise_error IndexError
121
+ expect { @multi_mi[:a, :one, :xyz] }.to raise_error IndexError
122
+ expect { @multi_mi[:x] }.to raise_error IndexError
123
+ expect { @multi_mi[:x, :one] }.to raise_error IndexError
124
+ expect { @multi_mi[:x, :one, :bar] }.to raise_error IndexError
125
+ end
126
+
127
+ it "works with numerical first levels" do
128
+ mi = Daru::MultiIndex.from_tuples([
129
+ [2000, 'M'],
130
+ [2000, 'F'],
131
+ [2001, 'M'],
132
+ [2001, 'F']
133
+ ])
134
+
135
+ expect(mi[2000]).to eq(Daru::MultiIndex.from_tuples([
136
+ [2000, 'M'],
137
+ [2000, 'F']
138
+ ]))
139
+
140
+ expect(mi[2000,'M']).to eq(0)
141
+ end
142
+ end
143
+
144
+ context "#include?" do
145
+ it "checks if a completely specified tuple exists" do
146
+ expect(@multi_mi.include?([:a,:one,:bar])).to eq(true)
147
+ end
148
+
149
+ it "checks if a top layer incomplete tuple exists" do
150
+ expect(@multi_mi.include?([:a])).to eq(true)
151
+ end
152
+
153
+ it "checks if a middle layer incomplete tuple exists" do
154
+ expect(@multi_mi.include?([:a, :one])).to eq(true)
155
+ end
156
+
157
+ it "checks for non-existence of a tuple" do
158
+ expect(@multi_mi.include?([:boo])).to eq(false)
159
+ end
160
+ end
161
+
162
+ context "#key" do
163
+ it "returns the tuple of the specified number" do
164
+ expect(@multi_mi.key(3)).to eq([:a,:two,:baz])
165
+ end
166
+
167
+ it "returns nil for non-existent pointer number" do
168
+ expect {
169
+ @multi_mi.key(100)
170
+ }.to raise_error ArgumentError
171
+ end
172
+ end
173
+
174
+ context "#to_a" do
175
+ it "returns tuples as an Array" do
176
+ expect(@multi_mi.to_a).to eq(@index_tuples)
177
+ end
178
+ end
179
+
180
+ context "#dup" do
181
+ it "completely duplicates the object" do
182
+ duplicate = @multi_mi.dup
183
+ expect(duplicate) .to eq(@multi_mi)
184
+ expect(duplicate.object_id).to_not eq(@multi_mi.object_id)
185
+ end
186
+ end
187
+
188
+ context "#inspect" do
189
+ context 'small index' do
190
+ subject {
191
+ Daru::MultiIndex.from_tuples [
192
+ [:a,:one,:bar],
193
+ [:a,:one,:baz],
194
+ [:a,:two,:bar],
195
+ [:a,:two,:baz],
196
+ [:b,:one,:bar],
197
+ [:b,:two,:bar],
198
+ [:b,:two,:baz],
199
+ [:b,:one,:foo],
200
+ [:c,:one,:bar],
201
+ [:c,:one,:baz],
202
+ [:c,:two,:foo],
203
+ [:c,:two,:bar]
204
+ ]
205
+ }
206
+
207
+ its(:inspect) { is_expected.to eq %Q{
208
+ |#<Daru::MultiIndex(12x3)>
209
+ | a one bar
210
+ | baz
211
+ | two bar
212
+ | baz
213
+ | b one bar
214
+ | two bar
215
+ | baz
216
+ | one foo
217
+ | c one bar
218
+ | baz
219
+ | two foo
220
+ | bar
221
+ }.unindent
222
+ }
223
+ end
224
+
225
+ context 'large index' do
226
+ subject {
227
+ Daru::MultiIndex.from_tuples(
228
+ (1..100).map { |i| %w[a b c].map { |c| [i, c] } }.flatten(1)
229
+ )
230
+ }
231
+
232
+ its(:inspect) { is_expected.to eq %Q{
233
+ |#<Daru::MultiIndex(300x2)>
234
+ | 1 a
235
+ | b
236
+ | c
237
+ | 2 a
238
+ | b
239
+ | c
240
+ | 3 a
241
+ | b
242
+ | c
243
+ | 4 a
244
+ | b
245
+ | c
246
+ | 5 a
247
+ | b
248
+ | c
249
+ | 6 a
250
+ | b
251
+ | c
252
+ | 7 a
253
+ | b
254
+ | ... ...
255
+ }.unindent
256
+ }
257
+ end
258
+ end
259
+
260
+ context "#==" do
261
+ it "returns false for unequal MultiIndex comparisons" do
262
+ mi1 = Daru::MultiIndex.from_tuples([
263
+ [:a, :one, :bar],
264
+ [:a, :two, :baz],
265
+ [:b, :one, :foo],
266
+ [:b, :two, :bar]
267
+ ])
268
+ mi2 = Daru::MultiIndex.from_tuples([
269
+ [:a, :two, :bar],
270
+ [:b, :one, :foo],
271
+ [:a, :one, :baz],
272
+ [:b, :two, :baz]
273
+ ])
274
+
275
+ expect(mi1 == mi2).to eq(false)
276
+ end
277
+ end
278
+
279
+ context "#values" do
280
+ it "returns an array of indices in order" do
281
+ mi = Daru::MultiIndex.from_tuples([
282
+ [:a, :one, :bar],
283
+ [:a, :two, :baz],
284
+ [:b, :one, :foo],
285
+ [:b, :two, :bar]
286
+ ])
287
+
288
+ expect(mi.values).to eq([0,1,2,3])
289
+ end
290
+ end
291
+
292
+ context "#|" do
293
+ before do
294
+ @mi1 = Daru::MultiIndex.from_tuples([
295
+ [:a, :one, :bar],
296
+ [:a, :two, :baz],
297
+ [:b, :one, :foo],
298
+ [:b, :two, :bar]
299
+ ])
300
+ @mi2 = Daru::MultiIndex.from_tuples([
301
+ [:a, :two, :bar],
302
+ [:b, :one, :foo],
303
+ [:a, :one, :baz],
304
+ [:b, :two, :baz]
305
+ ])
306
+ end
307
+
308
+ it "returns a union of two MultiIndex objects" do
309
+ expect(@mi1 | @mi2).to eq(Daru::MultiIndex.new(
310
+ levels: [[:a, :b], [:one, :two], [:bar, :baz, :foo]],
311
+ labels: [
312
+ [0, 0, 1, 1, 0, 0, 1],
313
+ [0, 1, 0, 1, 1, 0, 1],
314
+ [0, 1, 2, 0, 0, 1, 1]
315
+ ])
316
+ )
317
+ end
318
+ end
319
+
320
+ context "#&" do
321
+ before do
322
+ @mi1 = Daru::MultiIndex.from_tuples([
323
+ [:a, :one],
324
+ [:a, :two],
325
+ [:b, :two]
326
+ ])
327
+ @mi2 = Daru::MultiIndex.from_tuples([
328
+ [:a, :two],
329
+ [:b, :one],
330
+ [:b, :three]
331
+ ])
332
+ end
333
+
334
+ it "returns the intersection of two MI objects" do
335
+ expect(@mi1 & @mi2).to eq(Daru::MultiIndex.from_tuples([
336
+ [:a, :two],
337
+ ]))
338
+ end
339
+ end
340
+
341
+ context "#empty?" do
342
+ it "returns true if nothing present in MultiIndex" do
343
+ expect(Daru::MultiIndex.new(labels: [[]], levels: [[]]).empty?).to eq(true)
344
+ end
345
+ end
346
+
347
+ context "#drop_left_level" do
348
+ it "drops the leftmost level" do
349
+ expect(
350
+ Daru::MultiIndex.from_tuples([
351
+ [:c,:one,:bar],
352
+ [:c,:one,:baz],
353
+ [:c,:two,:foo],
354
+ [:c,:two,:bar]
355
+ ]).drop_left_level).to eq(
356
+ Daru::MultiIndex.from_tuples([
357
+ [:one,:bar],
358
+ [:one,:baz],
359
+ [:two,:foo],
360
+ [:two,:bar]
361
+ ])
362
+ )
363
+ end
364
+ end
365
+
366
+ context 'other forms of tuple list representation' do
367
+ let(:index) {
368
+ Daru::MultiIndex.from_tuples [
369
+ [:a,:one,:bar],
370
+ [:a,:one,:baz],
371
+ [:a,:two,:bar],
372
+ [:a,:two,:baz],
373
+ [:b,:one,:bar],
374
+ [:b,:two,:bar],
375
+ [:b,:two,:baz],
376
+ [:b,:one,:foo],
377
+ [:c,:one,:bar],
378
+ [:c,:one,:baz],
379
+ [:c,:two,:foo],
380
+ [:c,:two,:bar]
381
+ ]
382
+ }
383
+
384
+ context '#sparse_tuples' do
385
+ subject { index.sparse_tuples }
386
+
387
+ it { is_expected.to eq [
388
+ [:a ,:one,:bar],
389
+ [nil, nil,:baz],
390
+ [nil,:two,:bar],
391
+ [nil, nil,:baz],
392
+ [:b ,:one,:bar],
393
+ [nil,:two,:bar],
394
+ [nil, nil,:baz],
395
+ [nil,:one,:foo],
396
+ [:c ,:one,:bar],
397
+ [nil, nil,:baz],
398
+ [nil,:two,:foo],
399
+ [nil, nil,:bar]
400
+ ]}
401
+ end
402
+ end
403
+
404
+ context "#pos" do
405
+ let(:idx) do
406
+ described_class.from_tuples([
407
+ [:b,:one,:bar],
408
+ [:b,:two,:bar],
409
+ [:b,:two,:baz],
410
+ [:b,:one,:foo]
411
+ ])
412
+ end
413
+
414
+ context "single index" do
415
+ it { expect(idx.pos :b, :one, :bar).to eq 0 }
416
+ end
417
+
418
+ context "multiple indexes" do
419
+ subject { idx.pos :b, :one }
420
+
421
+ it { is_expected.to be_a Array }
422
+ its(:size) { is_expected.to eq 2 }
423
+ it { is_expected.to eq [0, 3] }
424
+ end
425
+
426
+ context "single positional index" do
427
+ it { expect(idx.pos 0).to eq 0 }
428
+ end
429
+
430
+ context "multiple positional indexes" do
431
+ subject { idx.pos 0, 1 }
432
+
433
+ it { is_expected.to be_a Array }
434
+ its(:size) { is_expected.to eq 2 }
435
+ it { is_expected.to eq [0, 1] }
436
+ end
437
+
438
+ # TODO: Add specs for IndexError
439
+ end
440
+
441
+ context "#subset" do
442
+ let(:idx) do
443
+ described_class.from_tuples([
444
+ [:b, :one, :bar],
445
+ [:b, :two, :bar],
446
+ [:b, :two, :baz],
447
+ [:b, :one, :foo]
448
+ ])
449
+ end
450
+
451
+ context "multiple indexes" do
452
+ subject { idx.subset :b, :one }
453
+
454
+ it { is_expected.to be_a described_class }
455
+ its(:size) { is_expected.to eq 2 }
456
+ its(:to_a) { is_expected.to eq [[:bar], [:foo]] }
457
+ end
458
+
459
+ context "multiple positional indexes" do
460
+ subject { idx.subset 0, 1 }
461
+
462
+ it { is_expected.to be_a described_class }
463
+ its(:size) { is_expected.to eq 2 }
464
+ its(:to_a) { is_expected.to eq [[:b, :one, :bar], [:b, :two, :bar]] }
465
+ end
466
+
467
+ # TODO: Checks for invalid indexes
468
+ end
469
+
470
+ context "at" do
471
+ let(:idx) do
472
+ described_class.from_tuples([
473
+ [:b, :one, :bar],
474
+ [:b, :two, :bar],
475
+ [:b, :two, :baz],
476
+ [:b, :one, :foo]
477
+ ])
478
+ end
479
+
480
+ context "single position" do
481
+ it { expect(idx.at 2).to eq [:b, :two, :baz] }
482
+ end
483
+
484
+ context "multiple positions" do
485
+ subject { idx.at 1, 2 }
486
+
487
+ it { is_expected.to be_a described_class }
488
+ its(:size) { is_expected.to eq 2 }
489
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
490
+ [:b, :two, :baz]] }
491
+ end
492
+
493
+ context "range" do
494
+ subject { idx.at 1..2 }
495
+
496
+ it { is_expected.to be_a described_class }
497
+ its(:size) { is_expected.to eq 2 }
498
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
499
+ [:b, :two, :baz]] }
500
+ end
501
+
502
+ context "range with negative integers" do
503
+ subject { idx.at 1..-2 }
504
+
505
+ it { is_expected.to be_a described_class }
506
+ its(:size) { is_expected.to eq 2 }
507
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
508
+ [:b, :two, :baz]] }
509
+ end
510
+
511
+ context "rangle with single element" do
512
+ subject { idx.at 1..1 }
513
+
514
+ it { is_expected.to be_a described_class }
515
+ its(:size) { is_expected.to eq 1 }
516
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar]] }
517
+ end
518
+
519
+ context "invalid position" do
520
+ it { expect { idx.at 4 }.to raise_error IndexError }
521
+ end
522
+
523
+ context "invalid positions" do
524
+ it { expect { idx.at 2, 4 }.to raise_error IndexError }
525
+ end
526
+ end
527
+
528
+ context "#add" do
529
+ let(:idx) do
530
+ described_class.from_tuples [
531
+ [:a, :one, :bar],
532
+ [:a, :two, :bar],
533
+ [:b, :two, :baz],
534
+ [:b, :one, :foo]
535
+ ]
536
+ end
537
+
538
+ context "single index" do
539
+ subject { idx.add :b, :two, :baz }
540
+
541
+ its(:to_a) { is_expected.to eq [
542
+ [:a, :one, :bar],
543
+ [:a, :two, :bar],
544
+ [:b, :two, :baz],
545
+ [:b, :one, :foo],
546
+ [:b, :two, :baz]] }
547
+ end
548
+ end
549
+
550
+ context "#valid?" do
551
+ let(:idx) do
552
+ described_class.from_tuples [
553
+ [:a, :one, :bar],
554
+ [:a, :two, :bar],
555
+ [:b, :two, :baz],
556
+ [:b, :one, :foo]
557
+ ]
558
+ end
559
+
560
+ context "single index" do
561
+ it { expect(idx.valid? :a, :one, :bar).to eq true }
562
+ it { expect(idx.valid? :b, :two, :three).to eq false }
563
+ end
564
+
565
+ context "multiple indexes" do
566
+ it { expect(idx.valid? :a, :one).to eq true }
567
+ it { expect(idx.valid? :a, :three).to eq false }
568
+ end
569
+ end
570
+ end