zermelo 1.2.1 → 1.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +22 -21
- data/lib/zermelo/associations/multiple.rb +1 -1
- data/lib/zermelo/backends/influxdb.rb +3 -1
- data/lib/zermelo/backends/redis.rb +2 -1
- data/lib/zermelo/filter.rb +19 -1
- data/lib/zermelo/filters/influxdb.rb +11 -3
- data/lib/zermelo/filters/redis.rb +21 -24
- data/lib/zermelo/filters/steps/set_step.rb +54 -21
- data/lib/zermelo/ordered_set.rb +11 -0
- data/lib/zermelo/records/class_methods.rb +1 -1
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +165 -41
- data/spec/lib/zermelo/filter_spec.rb +21 -21
- data/spec/support/mock_logger.rb +9 -6
- data/spec/support/profile_all_formatter.rb +12 -11
- data/spec/support/uncolored_doc_formatter.rb +6 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69911858d3394494076bade1249037f111eb5e7b
|
4
|
+
data.tar.gz: 5b2c9e463d7fb0b71a7135bad76b481e4e18629a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2b62aabc2f4421d99e53bc298e260550d2124cb957786c6ef1bce894cb0fd43c0cd704a6378e6a6acfbc2cfebffda52412984756fc314f98322a2ea63cb7a23
|
7
|
+
data.tar.gz: 69d7a165a1da92e759c6923828d12340d793eead6ee20681a0e22c9f7dde901b0421323ce0fd4c960bc708f5d84e9ebfab79bf166f638a3768032c8b75214074
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -256,21 +256,21 @@ Classes that include `Zermelo::Record` have the following class methods made ava
|
|
256
256
|
|
257
257
|
|Name | Arguments | Returns |
|
258
258
|
|-------------------------|---------------|---------|
|
259
|
-
|`all` | | Returns
|
260
|
-
|`each` | | Yields all records to the provided block, returns the same
|
261
|
-
|`collect` / `map` | | Yields all records to the provided block, returns an Array with the values returned from the block: [
|
262
|
-
|`select` / `find_all` | | Yields all records to the provided block, returns an Array with each record where the block returned true: [
|
263
|
-
|`reject` | | Yields all records to the provided block, returns an Array with each record where the block returned false: [
|
264
|
-
|`ids` | | Returns
|
259
|
+
|`all` | | Returns a Set of all the records stored for this class |
|
260
|
+
|`each` | | Yields all records to the provided block, returns the same Set as .all(): [Enumerable#each](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-each) |
|
261
|
+
|`collect` / `map` | | Yields all records to the provided block, returns an Array with the values returned from the block: [Enumerable#collect](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-collect) |
|
262
|
+
|`select` / `find_all` | | Yields all records to the provided block, returns an Array with each record where the block returned true: [Enumerable#select](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-select) |
|
263
|
+
|`reject` | | Yields all records to the provided block, returns an Array with each record where the block returned false: [Enumerable#reject](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-reject) |
|
264
|
+
|`ids` | | Returns a Set with the ids of all stored records |
|
265
265
|
|`count` | | Returns an Integer count of the number of stored records |
|
266
266
|
|`empty?` | | Returns true if no records are stored, false otherwise |
|
267
267
|
|`destroy_all` | | Removes all stored records |
|
268
268
|
|`exists?` | ID | Returns true if the record with the id is present, false if not |
|
269
269
|
|`find_by_id` | ID | Returns the instantiated record for the id, or nil if not present |
|
270
|
-
|`find_by_ids` | ID, ID, ... | Returns
|
270
|
+
|`find_by_ids` | ID, ID, ... | Returns a Set of instantiated records for the ids, with nils if the respective record is not present |
|
271
271
|
|`find_by_id!` | ID | Returns the instantiated record for the id, or raises a Zermelo::Records::RecordNotFound exception if not present |
|
272
|
-
|`find_by_ids!` | ID, ID, ... | Returns
|
273
|
-
|`associated_ids_for`
|
272
|
+
|`find_by_ids!` | ID, ID, ... | Returns a Set of instantiated records for the ids, or raises a Zermelo::Records::RecordsNotFound exception if any are not present |
|
273
|
+
|`associated_ids_for` & `associations_for` | association | (Defined in the `Associations` section below) |
|
274
274
|
|
275
275
|
### Instance methods
|
276
276
|
|
@@ -373,16 +373,16 @@ comment1.save
|
|
373
373
|
comment2 = Comment.new(:id => '2')
|
374
374
|
comment2.save
|
375
375
|
|
376
|
-
p post.comments.ids # ==
|
377
|
-
p Comment.ids # ==
|
376
|
+
p post.comments.ids # == #<Set: {}>
|
377
|
+
p Comment.ids # == #<Set: {'1', '2'}>
|
378
378
|
post.comments << comment1
|
379
|
-
p post.comments.ids # ==
|
379
|
+
p post.comments.ids # == #<Set: {'1'}>
|
380
380
|
```
|
381
381
|
|
382
382
|
`.associated_ids_for` is somewhat of a special case; it uses the simplest queries possible to get the ids of the associated records of a set of records, e.g. for the data directly above:
|
383
383
|
|
384
384
|
```ruby
|
385
|
-
Post.associated_ids_for(:comments) # => {'a' =>
|
385
|
+
Post.associated_ids_for(:comments) # => {'a' => #<Set: {'1'}>}
|
386
386
|
|
387
387
|
post_b = Post.new(:id => 'b')
|
388
388
|
post_b.save
|
@@ -391,16 +391,18 @@ comment3 = Comment.new(:id => '3')
|
|
391
391
|
comment3.save
|
392
392
|
post.comments << comment3
|
393
393
|
|
394
|
-
Post.associated_ids_for(:comments) # => {'a' =>
|
394
|
+
Post.associated_ids_for(:comments) # => {'a' => #<Set: {'1', '3'}>, 'b' => #<Set: {'2'}>}
|
395
395
|
```
|
396
396
|
|
397
397
|
For `belongs to` associations, you may pass an extra option to `associated_ids_for`, `:inversed => true`, and you'll get the data back as if it were applied from the inverse side; however the data will only cover that used as the query root. Again, assuming the data from the last two code blocks, e.g.
|
398
398
|
|
399
399
|
```ruby
|
400
400
|
Comment.associated_ids_for(:post) # => {'1' => 'a', '2' => 'b', '3' => 'a'}
|
401
|
-
Comment.associated_ids_for(:post, :inversed => true) # => {'a' =>
|
401
|
+
Comment.associated_ids_for(:post, :inversed => true) # => {'a' => #<Set: {'1', '3'}>, 'b' => #<Set: {'2'}>}
|
402
402
|
```
|
403
403
|
|
404
|
+
`.associations_for` returns chainable Zermelo association proxy objects, rather than sets of ids, as the Hash values. Please note, `.associations_for` only works with multiple associations (`has_many`, `has_and_belongs_to_many`, `has_sorted_set`).
|
405
|
+
|
404
406
|
### Class data indexing
|
405
407
|
|
406
408
|
Simple instance attributes, as defined above, can be indexed by value (and those indices can be queried).
|
@@ -466,7 +468,7 @@ are both valid, and the `Comment` instances returned by the first query would be
|
|
466
468
|
The chained queries are only executed when the results are invoked (lazy evaluation) by the addition of one of the class methods listed above; e.g.
|
467
469
|
|
468
470
|
```ruby
|
469
|
-
Comment.intersect(:title => 'Interesting').all # ->
|
471
|
+
Comment.intersect(:title => 'Interesting').all # -> #<Set: {Comment, Comment, ...}>
|
470
472
|
Comment.intersect(:title => 'Interesting', :promoted => true).count # -> Integer
|
471
473
|
```
|
472
474
|
|
@@ -477,7 +479,7 @@ SINTER comment::attrs:ids comment::indices:by_title:string:Interesting
|
|
477
479
|
HGET comment:ca9e427d-4d81-47f8-bcfe-bb614d40528c:attrs title
|
478
480
|
```
|
479
481
|
|
480
|
-
with the result being
|
482
|
+
with the result being a Set with one member, a Comment record with `{:id => 'ca9e427d-4d81-47f8-bcfe-bb614d40528c', :title => 'Interesting'}`
|
481
483
|
|
482
484
|
and the second (`.count`) will execute these Redis commands.
|
483
485
|
|
@@ -507,9 +509,8 @@ comment2 = Comment.new(:id => '2', :created_at => t - 60)
|
|
507
509
|
comment2.save
|
508
510
|
|
509
511
|
range = Zermelo::Filters::IndexRange.new(t - 90, t, :by_score => true)
|
510
|
-
Comment.ids #
|
511
|
-
Comment.intersect(:created_at => range).ids #
|
512
|
-
|
512
|
+
Comment.ids # #<Set: {'1', '2'}>
|
513
|
+
Comment.intersect(:created_at => range).ids # #<Set: {'2'}>
|
513
514
|
```
|
514
515
|
|
515
516
|
### Future
|
@@ -519,7 +520,7 @@ Some possible changes:
|
|
519
520
|
* pluggable id generation strategies
|
520
521
|
* pluggable key naming strategies
|
521
522
|
* instrumentation for benchmarking etc.
|
522
|
-
* multiple data backends
|
523
|
+
* multiple data backends
|
523
524
|
|
524
525
|
## License
|
525
526
|
|
@@ -12,7 +12,7 @@ module Zermelo
|
|
12
12
|
:all, :each, :collect, :map,
|
13
13
|
:select, :find_all, :reject, :destroy_all,
|
14
14
|
:ids, :count, :empty?, :exists?,
|
15
|
-
:associated_ids_for
|
15
|
+
:associated_ids_for, :associations_for
|
16
16
|
|
17
17
|
def initialize(type, parent_klass, parent_id, name)
|
18
18
|
@type = type
|
@@ -2,6 +2,8 @@ require 'zermelo/backend'
|
|
2
2
|
|
3
3
|
require 'zermelo/filters/influxdb'
|
4
4
|
|
5
|
+
require 'zermelo/ordered_set'
|
6
|
+
|
5
7
|
# NB influxdb doesn't support individually addressable deletes, so
|
6
8
|
# this backend only works to write new records
|
7
9
|
# (it could just write the new state of the record, and query id by newest limit 1,
|
@@ -59,7 +61,7 @@ module Zermelo
|
|
59
61
|
when :hash
|
60
62
|
{}
|
61
63
|
when :set
|
62
|
-
Set.new
|
64
|
+
Set.new
|
63
65
|
else nil
|
64
66
|
end
|
65
67
|
else
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'zermelo/backend'
|
2
2
|
|
3
3
|
require 'zermelo/filters/redis'
|
4
|
+
require 'zermelo/ordered_set'
|
4
5
|
require 'zermelo/locks/redis_lock'
|
5
6
|
|
6
7
|
module Zermelo
|
@@ -54,7 +55,7 @@ module Zermelo
|
|
54
55
|
end
|
55
56
|
when :sorted_set
|
56
57
|
if attr_key.accessor.nil?
|
57
|
-
Zermelo.redis.zrange(redis_attr_key, 0, -1)
|
58
|
+
Zermelo::OrderedSet.new(Zermelo.redis.zrange(redis_attr_key, 0, -1))
|
58
59
|
else
|
59
60
|
# TODO
|
60
61
|
end
|
data/lib/zermelo/filter.rb
CHANGED
@@ -168,6 +168,24 @@ module Zermelo
|
|
168
168
|
}
|
169
169
|
end
|
170
170
|
|
171
|
+
def associations_for(name)
|
172
|
+
data_type, type_klass = @associated_class.send(:with_association_data, name.to_sym) do |data|
|
173
|
+
[data.data_type, data.type_klass]
|
174
|
+
end
|
175
|
+
|
176
|
+
lock {
|
177
|
+
case data_type
|
178
|
+
when :belongs_to, :has_one
|
179
|
+
raise "'associations_for' only supports multiple associations"
|
180
|
+
else
|
181
|
+
_ids.each_with_object({}) do |this_id, memo|
|
182
|
+
memo[this_id] = ::Zermelo::Associations::Multiple.new(data_type,
|
183
|
+
@associated_class, this_id, name)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
171
189
|
protected
|
172
190
|
|
173
191
|
def lock(when_steps_empty = true, *klasses, &block)
|
@@ -193,7 +211,7 @@ module Zermelo
|
|
193
211
|
end
|
194
212
|
|
195
213
|
def _all
|
196
|
-
_ids.map {|id| _load(id) }
|
214
|
+
_ids.map! {|id| _load(id) }
|
197
215
|
end
|
198
216
|
|
199
217
|
end
|
@@ -64,7 +64,15 @@ module Zermelo
|
|
64
64
|
initial_id_data = nil
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
if initial_id_data.nil?
|
68
|
+
ret = case result_type
|
69
|
+
when :ids
|
70
|
+
Set.new
|
71
|
+
when :count
|
72
|
+
0
|
73
|
+
end
|
74
|
+
return ret
|
75
|
+
end
|
68
76
|
|
69
77
|
initial_ids = initial_id_data.first[@initial_key.name]
|
70
78
|
|
@@ -105,9 +113,9 @@ module Zermelo
|
|
105
113
|
|
106
114
|
case result_type
|
107
115
|
when :ids
|
108
|
-
data_keys.empty? ?
|
116
|
+
data_keys.empty? ? Set.new : Set.new(data_keys.collect {|k| k =~ /^#{class_key}\/(.+)$/; $1 })
|
109
117
|
when :count
|
110
|
-
data_keys.empty? ?
|
118
|
+
data_keys.empty? ? 0 : data_keys.size
|
111
119
|
end
|
112
120
|
end
|
113
121
|
end
|
@@ -2,6 +2,8 @@ require 'zermelo/filter'
|
|
2
2
|
|
3
3
|
require 'zermelo/filters/index_range'
|
4
4
|
|
5
|
+
require 'zermelo/ordered_set'
|
6
|
+
|
5
7
|
# TODO check escaping of ids and index_keys -- shouldn't allow bare :
|
6
8
|
|
7
9
|
module Zermelo
|
@@ -14,20 +16,20 @@ module Zermelo
|
|
14
16
|
|
15
17
|
SHORTCUTS = {
|
16
18
|
:list => {
|
17
|
-
:ids => proc {|key| Zermelo.redis.lrange(key, 0, -1) },
|
19
|
+
:ids => proc {|key| Zermelo::OrderedSet.new(Zermelo.redis.lrange(key, 0, -1)) },
|
18
20
|
:count => proc {|key| Zermelo.redis.llen(key) },
|
19
21
|
:exists? => proc {|key, id| Zermelo.redis.lrange(key, 0, -1).include?(id) },
|
20
22
|
:first => proc {|key| Zermelo.redis.lrange(key, 0, 0).first },
|
21
23
|
:last => proc {|key| Zermelo.redis.lrevrange(key, 0, 0).first }
|
22
24
|
},
|
23
25
|
:set => {
|
24
|
-
:ids => proc {|key| Zermelo.redis.smembers(key) },
|
26
|
+
:ids => proc {|key| Set.new(Zermelo.redis.smembers(key)) },
|
25
27
|
:count => proc {|key| Zermelo.redis.scard(key) },
|
26
28
|
:exists? => proc {|key, id| Zermelo.redis.sismember(key, id) }
|
27
29
|
},
|
28
30
|
:sorted_set => {
|
29
31
|
:ids => proc {|key, order|
|
30
|
-
Zermelo.redis.send((:desc.eql?(order) ? :zrevrange : :zrange), key, 0, -1)
|
32
|
+
Zermelo::OrderedSet.new(Zermelo.redis.send((:desc.eql?(order) ? :zrevrange : :zrange), key, 0, -1))
|
31
33
|
},
|
32
34
|
:count => proc {|key, order| Zermelo.redis.zcard(key) },
|
33
35
|
:exists? => proc {|key, order, id| !Zermelo.redis.zscore(key, id).nil? },
|
@@ -73,17 +75,12 @@ module Zermelo
|
|
73
75
|
resolve_steps(:exists?, id)
|
74
76
|
end
|
75
77
|
|
76
|
-
# If called with a
|
77
|
-
#
|
78
|
-
|
79
|
-
# If called with any arguments -- treats them as a hash of shortcuts
|
80
|
-
|
81
|
-
# If not called with any arguments -- returns two values, the first is
|
82
|
-
# the name of a set containing the filtered ids, the second is a boolean
|
83
|
-
# for whether or not to clear up that set once it's been used
|
84
|
-
|
85
|
-
def resolve_steps(shortcut, *args)
|
78
|
+
# If not called with a shortcut, return value is the name of a set
|
79
|
+
# containing the filtered ids
|
80
|
+
def resolve_steps(shortcut = nil, *args)
|
86
81
|
if @steps.empty?
|
82
|
+
raise "Shortcut must be provided if no steps" if shortcut.nil?
|
83
|
+
|
87
84
|
unless @callback_target_class.nil? || @callbacks.nil?
|
88
85
|
br = @callbacks[:before_read]
|
89
86
|
if !br.nil? && @callback_target_class.respond_to?(br)
|
@@ -92,17 +89,13 @@ module Zermelo
|
|
92
89
|
end
|
93
90
|
|
94
91
|
sc = Zermelo::Filters::Redis::SHORTCUTS[@initial_key.type][shortcut]
|
95
|
-
|
96
|
-
|
92
|
+
r_key = backend.key_to_redis_key(@initial_key)
|
93
|
+
shortcut_params = if @initial_key.type == :sorted_set
|
94
|
+
[r_key, @sort_order] + args
|
97
95
|
else
|
98
|
-
r_key
|
99
|
-
shortcut_params = if @initial_key.type == :sorted_set
|
100
|
-
[r_key, @sort_order] + args
|
101
|
-
else
|
102
|
-
[r_key] + args
|
103
|
-
end
|
104
|
-
sc.call(*shortcut_params)
|
96
|
+
[r_key] + args
|
105
97
|
end
|
98
|
+
ret = sc.call(*shortcut_params)
|
106
99
|
|
107
100
|
unless @callback_target_class.nil? || @callbacks.nil?
|
108
101
|
ar = @callbacks[:after_read]
|
@@ -140,7 +133,7 @@ module Zermelo
|
|
140
133
|
raise "'#{step.class.name}' does not accept input type #{step_opts[:source].type}"
|
141
134
|
end
|
142
135
|
|
143
|
-
if step == last_step
|
136
|
+
if step == last_step && !shortcut.nil?
|
144
137
|
step_opts.update(:shortcut => shortcut, :shortcut_args => args)
|
145
138
|
end
|
146
139
|
|
@@ -160,7 +153,11 @@ module Zermelo
|
|
160
153
|
end
|
161
154
|
end
|
162
155
|
|
163
|
-
|
156
|
+
if step == last_step
|
157
|
+
temp_keys.delete(result) if shortcut.nil?
|
158
|
+
else
|
159
|
+
step_opts[:source] = result
|
160
|
+
end
|
164
161
|
end
|
165
162
|
|
166
163
|
result
|
@@ -25,25 +25,42 @@ module Zermelo
|
|
25
25
|
order = opts[:sort_order]
|
26
26
|
|
27
27
|
source_keys = @attributes.each_with_object([]) do |(att, value), memo|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if :id.eql?(att)
|
32
|
-
ts = associated_class.send(:temp_key, :set)
|
33
|
-
temp_keys << ts
|
34
|
-
Zermelo.redis.sadd(backend.key_to_redis_key(ts), val)
|
35
|
-
memo << ts
|
36
|
-
else
|
28
|
+
idx_class = nil
|
29
|
+
unless :id.eql?(att)
|
37
30
|
idx_class = idx_attrs[att.to_s]
|
38
31
|
raise "'#{att}' property is not indexed" if idx_class.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
if [Set, Array].any? {|t| value.is_a?(t) }
|
35
|
+
conditions_set = associated_class.send(:temp_key, source.type)
|
36
|
+
temp_keys << conditions_set
|
37
|
+
r_conditions_set = backend.key_to_redis_key(conditions_set)
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
backend.temp_key_wrap do |conditions_temp_keys|
|
40
|
+
if idx_class.nil?
|
41
|
+
cond_objects, cond_ids = value.partition do |v|
|
42
|
+
[Zermelo::Filter, Zermelo::Associations::Multiple].any? {|c| v.is_a?(c)}
|
43
|
+
end
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
unless cond_objects.empty?
|
46
|
+
cond_keys = cond_objects.collect do |co|
|
47
|
+
k = case co
|
48
|
+
when Zermelo::Filter
|
49
|
+
co.send(:resolve_steps)
|
50
|
+
when Zermelo::Associations::Multiple
|
51
|
+
co.instance_variable_get('@record_ids_key')
|
52
|
+
end
|
53
|
+
backend.key_to_redis_key(k)
|
54
|
+
end
|
55
|
+
|
56
|
+
Zermelo.redis.sunionstore(r_conditions_set, *cond_keys)
|
57
|
+
end
|
58
|
+
unless cond_ids.empty?
|
59
|
+
cond_ids.map! {|ci| ci.is_a?(Zermelo::Associations::Singular) ? ci.id : ci }
|
60
|
+
Zermelo.redis.sadd(r_conditions_set, cond_ids)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
index_keys = value.to_a.collect {|v|
|
47
64
|
il = backend.index_lookup(att, associated_class,
|
48
65
|
idx_class, v, attr_types[att], conditions_temp_keys)
|
49
66
|
backend.key_to_redis_key(il)
|
@@ -56,11 +73,27 @@ module Zermelo
|
|
56
73
|
Zermelo.redis.zunionstore(r_conditions_set, index_keys)
|
57
74
|
end
|
58
75
|
end
|
59
|
-
|
76
|
+
end
|
77
|
+
memo << conditions_set
|
78
|
+
elsif idx_class.nil?
|
79
|
+
case value
|
80
|
+
when Zermelo::Filter
|
81
|
+
ts = value.send(:resolve_steps)
|
82
|
+
temp_keys << ts
|
83
|
+
memo << ts
|
84
|
+
when Zermelo::Associations::Multiple
|
85
|
+
memo << value.instance_variable_get('@record_ids_key')
|
60
86
|
else
|
61
|
-
|
62
|
-
|
87
|
+
ts = associated_class.send(:temp_key, :set)
|
88
|
+
temp_keys << ts
|
89
|
+
r_ts = backend.key_to_redis_key(ts)
|
90
|
+
Zermelo.redis.sadd(r_ts,
|
91
|
+
value.is_a?(Zermelo::Associations::Singular) ? value.id : value)
|
92
|
+
memo << ts
|
63
93
|
end
|
94
|
+
else
|
95
|
+
memo << backend.index_lookup(att, associated_class,
|
96
|
+
idx_class, value, attr_types[att], temp_keys)
|
64
97
|
end
|
65
98
|
end
|
66
99
|
|
@@ -79,10 +112,10 @@ module Zermelo
|
|
79
112
|
r_dest_set = backend.key_to_redis_key(dest_set)
|
80
113
|
|
81
114
|
Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
|
82
|
-
Zermelo.redis.sunion(r_source_key, r_dest_set)
|
115
|
+
Set.new(Zermelo.redis.sunion(r_source_key, r_dest_set))
|
83
116
|
end
|
84
117
|
when :intersect
|
85
|
-
Zermelo.redis.sinter(r_source_key, *r_source_keys)
|
118
|
+
Set.new(Zermelo.redis.sinter(r_source_key, *r_source_keys))
|
86
119
|
when :diff
|
87
120
|
backend.temp_key_wrap do |shortcut_temp_keys|
|
88
121
|
dest_set = associated_class.send(:temp_key, :set)
|
@@ -90,7 +123,7 @@ module Zermelo
|
|
90
123
|
r_dest_set = backend.key_to_redis_key(dest_set)
|
91
124
|
|
92
125
|
Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
|
93
|
-
Zermelo.redis.sdiff(r_source_key, r_dest_set)
|
126
|
+
Set.new(Zermelo.redis.sdiff(r_source_key, r_dest_set))
|
94
127
|
end
|
95
128
|
end
|
96
129
|
else
|
@@ -26,7 +26,7 @@ module Zermelo
|
|
26
26
|
:all, :each, :collect, :map,
|
27
27
|
:select, :find_all, :reject, :destroy_all,
|
28
28
|
:ids, :count, :empty?, :exists?,
|
29
|
-
:associated_ids_for
|
29
|
+
:associated_ids_for, :associations_for
|
30
30
|
|
31
31
|
def generate_id
|
32
32
|
return SecureRandom.uuid if SecureRandom.respond_to?(:uuid)
|
data/lib/zermelo/version.rb
CHANGED
@@ -20,14 +20,14 @@ describe Zermelo::Associations::Multiple do
|
|
20
20
|
|
21
21
|
children = parent.children.all
|
22
22
|
|
23
|
-
expect(children).to
|
23
|
+
expect(children).to be_a(Set)
|
24
24
|
expect(children).to be_empty
|
25
25
|
|
26
26
|
parent.children << child
|
27
27
|
|
28
28
|
children = parent.children.all
|
29
29
|
|
30
|
-
expect(children).to
|
30
|
+
expect(children).to be_a(Set)
|
31
31
|
expect(children.size).to eq(1)
|
32
32
|
end
|
33
33
|
|
@@ -36,7 +36,7 @@ describe Zermelo::Associations::Multiple do
|
|
36
36
|
|
37
37
|
children = parent.children.all
|
38
38
|
|
39
|
-
expect(children).to
|
39
|
+
expect(children).to be_a(Set)
|
40
40
|
expect(children.size).to eq(1)
|
41
41
|
child = children.first
|
42
42
|
expect(child).to be_a(child_class)
|
@@ -61,7 +61,7 @@ describe Zermelo::Associations::Multiple do
|
|
61
61
|
child = child_class.find_by_id('3')
|
62
62
|
parent.children.remove(child)
|
63
63
|
expect(parent.children.count).to eq(1)
|
64
|
-
expect(parent.children.ids).to eq(['4'])
|
64
|
+
expect(parent.children.ids).to eq(Set.new(['4']))
|
65
65
|
end
|
66
66
|
|
67
67
|
it "deletes a record from the set by id" do
|
@@ -71,7 +71,7 @@ describe Zermelo::Associations::Multiple do
|
|
71
71
|
expect(parent.children.count).to eq(2)
|
72
72
|
parent.children.remove_ids('3')
|
73
73
|
expect(parent.children.count).to eq(1)
|
74
|
-
expect(parent.children.ids).to eq(['4'])
|
74
|
+
expect(parent.children.ids).to eq(Set.new(['4']))
|
75
75
|
end
|
76
76
|
|
77
77
|
it "clears all records from the set" do
|
@@ -82,7 +82,7 @@ describe Zermelo::Associations::Multiple do
|
|
82
82
|
child = child_class.find_by_id('3')
|
83
83
|
parent.children.clear
|
84
84
|
expect(parent.children.count).to eq(0)
|
85
|
-
expect(parent.children.ids).to eq(
|
85
|
+
expect(parent.children.ids).to eq(Set.new)
|
86
86
|
end
|
87
87
|
|
88
88
|
it "does not add a child if the before_add callback raises an exception" # do
|
@@ -125,15 +125,15 @@ describe Zermelo::Associations::Multiple do
|
|
125
125
|
it "by indexed attribute values" do
|
126
126
|
important_kids = parent.children.intersect(:important => true).all
|
127
127
|
expect(important_kids).not_to be_nil
|
128
|
-
expect(important_kids).to
|
128
|
+
expect(important_kids).to be_a(Set)
|
129
129
|
expect(important_kids.size).to eq(2)
|
130
|
-
expect(important_kids.map(&:id)).to
|
130
|
+
expect(important_kids.map(&:id)).to eq(['3', '4'])
|
131
131
|
end
|
132
132
|
|
133
133
|
it "by intersecting ids" do
|
134
134
|
important_kids = parent.children.intersect(:important => true, :id => ['4', '5']).all
|
135
135
|
expect(important_kids).not_to be_nil
|
136
|
-
expect(important_kids).to
|
136
|
+
expect(important_kids).to be_a(Set)
|
137
137
|
expect(important_kids.size).to eq(1)
|
138
138
|
expect(important_kids.map(&:id)).to match_array(['4'])
|
139
139
|
end
|
@@ -143,7 +143,7 @@ describe Zermelo::Associations::Multiple do
|
|
143
143
|
create_child(parent, :id => '4', :important => false)
|
144
144
|
|
145
145
|
result = parent.children.intersect(:important => true).union(:id => '4').all
|
146
|
-
expect(result).to
|
146
|
+
expect(result).to be_a(Set)
|
147
147
|
expect(result.size).to eq(2)
|
148
148
|
expect(result.map(&:id)).to eq(['3', '4'])
|
149
149
|
end
|
@@ -172,7 +172,7 @@ describe Zermelo::Associations::Multiple do
|
|
172
172
|
associated_ids_for(:children)
|
173
173
|
expect(assoc_ids).to eq('8' => Set.new(['3', '4', '5']),
|
174
174
|
'9' => Set.new(['6']),
|
175
|
-
'10' => Set.new
|
175
|
+
'10' => Set.new)
|
176
176
|
|
177
177
|
assoc_parent_ids = child_class.intersect(:id => ['3', '4', '5', '6']).
|
178
178
|
associated_ids_for(:parent)
|
@@ -182,6 +182,27 @@ describe Zermelo::Associations::Multiple do
|
|
182
182
|
'6' => '9')
|
183
183
|
end
|
184
184
|
|
185
|
+
it 'returns associations for multiple parent ids' do
|
186
|
+
create_parent(:id => '9')
|
187
|
+
parent_9 = parent_class.find_by_id('9')
|
188
|
+
|
189
|
+
create_child(parent_9, :id => '6', :important => false)
|
190
|
+
|
191
|
+
create_parent(:id => '10')
|
192
|
+
|
193
|
+
assocs = parent_class.intersect(:id => [ '8', '9', '10']).
|
194
|
+
associations_for(:children)
|
195
|
+
expect(assocs).to be_a(Hash)
|
196
|
+
expect(assocs.keys).to match_array(['8', '9', '10'])
|
197
|
+
expect(assocs.values.all? {|r| r.is_a?(Zermelo::Associations::Multiple)}).to be true
|
198
|
+
|
199
|
+
expect(assocs['8'].count).to eq(3)
|
200
|
+
expect(assocs['8'].ids).to eq(Set.new(['3', '4', '5']))
|
201
|
+
expect(assocs['9'].count).to eq(1)
|
202
|
+
expect(assocs['9'].ids).to eq(Set.new(['6']))
|
203
|
+
expect(assocs['10'].count).to eq(0)
|
204
|
+
expect(assocs['10'].ids).to eq(Set.new)
|
205
|
+
end
|
185
206
|
end
|
186
207
|
end
|
187
208
|
|
@@ -310,6 +331,106 @@ describe Zermelo::Associations::Multiple do
|
|
310
331
|
"#{ck}:6:attrs"])
|
311
332
|
end
|
312
333
|
|
334
|
+
it 'queries using association objects' do
|
335
|
+
create_parent(:id => '8')
|
336
|
+
parent_8 = parent_class.find_by_id('8')
|
337
|
+
create_child(parent_8, :id => '5')
|
338
|
+
create_child(parent_8, :id => '6')
|
339
|
+
|
340
|
+
create_parent(:id => '9')
|
341
|
+
parent_9 = parent_class.find_by_id('9')
|
342
|
+
create_child(parent_9, :id => '7')
|
343
|
+
|
344
|
+
create_parent(:id => '10')
|
345
|
+
|
346
|
+
assocs = parent_class.intersect(:id => ['8', '10']).
|
347
|
+
associations_for(:children).values
|
348
|
+
|
349
|
+
children = child_class.intersect(:id => assocs)
|
350
|
+
expect(children.count).to eq(2)
|
351
|
+
expect(children.ids).to eq(Set.new(['5', '6']))
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'queries using multiple association objects' do
|
355
|
+
create_parent(:id => '8')
|
356
|
+
parent_8 = parent_class.find_by_id('8')
|
357
|
+
create_child(parent_8, :id => '5')
|
358
|
+
create_child(parent_8, :id => '6')
|
359
|
+
|
360
|
+
create_parent(:id => '9')
|
361
|
+
parent_9 = parent_class.find_by_id('9')
|
362
|
+
create_child(parent_9, :id => '7')
|
363
|
+
|
364
|
+
create_parent(:id => '10')
|
365
|
+
parent_10 = parent_class.find_by_id('10')
|
366
|
+
create_child(parent_10, :id => '4')
|
367
|
+
|
368
|
+
children = child_class.intersect(:id => [parent_8.children, parent_9.children])
|
369
|
+
expect(children.count).to eq(3)
|
370
|
+
expect(children.ids).to eq(Set.new(['5', '6', '7']))
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'queries using a single filter object' do
|
374
|
+
create_parent(:id => '8')
|
375
|
+
parent_8 = parent_class.find_by_id('8')
|
376
|
+
create_child(parent_8, :id => '5')
|
377
|
+
create_child(parent_8, :id => '6')
|
378
|
+
|
379
|
+
create_parent(:id => '9')
|
380
|
+
parent_9 = parent_class.find_by_id('9')
|
381
|
+
create_child(parent_9, :id => '7')
|
382
|
+
|
383
|
+
create_parent(:id => '10')
|
384
|
+
|
385
|
+
par = parent_class.intersect(:id => ['8', '10'])
|
386
|
+
|
387
|
+
parent_ids = parent_class.intersect(:id => par).ids
|
388
|
+
expect(parent_ids).to eq(Set.new(['8', '10']))
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'queries using multiple filter objects' do
|
392
|
+
create_parent(:id => '8')
|
393
|
+
parent_8 = parent_class.find_by_id('8')
|
394
|
+
create_child(parent_8, :id => '5')
|
395
|
+
create_child(parent_8, :id => '6')
|
396
|
+
|
397
|
+
create_parent(:id => '9')
|
398
|
+
parent_9 = parent_class.find_by_id('9')
|
399
|
+
create_child(parent_9, :id => '7')
|
400
|
+
|
401
|
+
create_parent(:id => '10')
|
402
|
+
|
403
|
+
par_1 = parent_class.intersect(:id => ['8'])
|
404
|
+
par_2 = parent_class.intersect(:id => ['10'])
|
405
|
+
|
406
|
+
parent_ids = parent_class.intersect(:id => [par_1, par_2]).ids
|
407
|
+
expect(parent_ids).to eq(Set.new(['8', '10']))
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'queries using a combination of bare value, association and filter object' do
|
411
|
+
create_parent(:id => '8')
|
412
|
+
parent_8 = parent_class.find_by_id('8')
|
413
|
+
create_child(parent_8, :id => '5')
|
414
|
+
create_child(parent_8, :id => '6')
|
415
|
+
|
416
|
+
create_parent(:id => '9')
|
417
|
+
parent_9 = parent_class.find_by_id('9')
|
418
|
+
create_child(parent_9, :id => '7')
|
419
|
+
|
420
|
+
create_parent(:id => '10')
|
421
|
+
parent_10 = parent_class.find_by_id('10')
|
422
|
+
create_child(parent_10, :id => '4')
|
423
|
+
|
424
|
+
assocs = parent_class.intersect(:id => ['8']).
|
425
|
+
associations_for(:children).values
|
426
|
+
|
427
|
+
children = child_class.intersect(:id => assocs + [
|
428
|
+
parent_9.children.intersect(:id => '7'), '4'
|
429
|
+
])
|
430
|
+
expect(children.count).to eq(4)
|
431
|
+
expect(children.ids).to eq(Set.new(['4', '5', '6', '7']))
|
432
|
+
end
|
433
|
+
|
313
434
|
end
|
314
435
|
|
315
436
|
context 'influxdb', :influxdb => true, :has_many => true do
|
@@ -349,6 +470,9 @@ describe Zermelo::Associations::Multiple do
|
|
349
470
|
par.children.add(child_class.find_by_id!(attrs[:id]))
|
350
471
|
end
|
351
472
|
|
473
|
+
# FIXME not implemented yet for InfluxDB, see SetStep
|
474
|
+
it 'queries associated filters transparently'
|
475
|
+
|
352
476
|
end
|
353
477
|
|
354
478
|
end
|
@@ -372,7 +496,7 @@ describe Zermelo::Associations::Multiple do
|
|
372
496
|
|
373
497
|
secondaries = primary.secondaries.all
|
374
498
|
|
375
|
-
expect(secondaries).to
|
499
|
+
expect(secondaries).to be_a(Set)
|
376
500
|
expect(secondaries.size).to eq(1)
|
377
501
|
other_secondary = secondaries.first
|
378
502
|
expect(other_secondary).to be_a(secondary_class)
|
@@ -412,9 +536,9 @@ describe Zermelo::Associations::Multiple do
|
|
412
536
|
expect(secondary.primaries.count).to eq(1)
|
413
537
|
expect(primary.secondaries.count).to eq(0)
|
414
538
|
expect(primary_2.secondaries.count).to eq(1)
|
415
|
-
expect(secondary.primaries.ids).to eq(['9'])
|
416
|
-
expect(primary.secondaries.ids).to eq(
|
417
|
-
expect(primary_2.secondaries.ids).to eq(['2'])
|
539
|
+
expect(secondary.primaries.ids).to eq(Set.new(['9']))
|
540
|
+
expect(primary.secondaries.ids).to eq(Set.new)
|
541
|
+
expect(primary_2.secondaries.ids).to eq(Set.new(['2']))
|
418
542
|
end
|
419
543
|
|
420
544
|
it "deletes a record from the set by id" do
|
@@ -434,9 +558,9 @@ describe Zermelo::Associations::Multiple do
|
|
434
558
|
expect(secondary.primaries.count).to eq(1)
|
435
559
|
expect(primary.secondaries.count).to eq(0)
|
436
560
|
expect(primary_2.secondaries.count).to eq(1)
|
437
|
-
expect(secondary.primaries.ids).to eq(['9'])
|
438
|
-
expect(primary.secondaries.ids).to eq(
|
439
|
-
expect(primary_2.secondaries.ids).to eq(['2'])
|
561
|
+
expect(secondary.primaries.ids).to eq(Set.new(['9']))
|
562
|
+
expect(primary.secondaries.ids).to eq(Set.new)
|
563
|
+
expect(primary_2.secondaries.ids).to eq(Set.new(['2']))
|
440
564
|
end
|
441
565
|
|
442
566
|
it "clears all records from the set" do
|
@@ -456,9 +580,9 @@ describe Zermelo::Associations::Multiple do
|
|
456
580
|
expect(secondary.primaries.count).to eq(0)
|
457
581
|
expect(primary.secondaries.count).to eq(0)
|
458
582
|
expect(primary_2.secondaries.count).to eq(0)
|
459
|
-
expect(secondary.primaries.ids).to eq(
|
460
|
-
expect(primary.secondaries.ids).to eq(
|
461
|
-
expect(primary_2.secondaries.ids).to eq(
|
583
|
+
expect(secondary.primaries.ids).to eq(Set.new)
|
584
|
+
expect(primary.secondaries.ids).to eq(Set.new)
|
585
|
+
expect(primary_2.secondaries.ids).to eq(Set.new)
|
462
586
|
end
|
463
587
|
|
464
588
|
context 'filters' do
|
@@ -478,7 +602,7 @@ describe Zermelo::Associations::Multiple do
|
|
478
602
|
|
479
603
|
primaries = secondary.primaries.intersect(:active => true).all
|
480
604
|
expect(primaries).not_to be_nil
|
481
|
-
expect(primaries).to
|
605
|
+
expect(primaries).to be_a(Set)
|
482
606
|
expect(primaries.size).to eq(2)
|
483
607
|
expect(primaries.map(&:id)).to match_array(['8', '10'])
|
484
608
|
end
|
@@ -747,7 +871,7 @@ describe Zermelo::Associations::Multiple do
|
|
747
871
|
|
748
872
|
children = parent.children.all
|
749
873
|
|
750
|
-
expect(children).to
|
874
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
751
875
|
expect(children.size).to eq(1)
|
752
876
|
child = children.first
|
753
877
|
expect(child).to be_a(child_class)
|
@@ -866,9 +990,9 @@ describe Zermelo::Associations::Multiple do
|
|
866
990
|
|
867
991
|
assoc_ids = parent_class.intersect(:id => ['8', '9', '10']).
|
868
992
|
associated_ids_for(:children)
|
869
|
-
expect(assoc_ids).to eq('8' => ['3', '4'],
|
870
|
-
'9' =>
|
871
|
-
'10' => ['5'])
|
993
|
+
expect(assoc_ids).to eq('8' => Zermelo::OrderedSet.new(['3', '4']),
|
994
|
+
'9' => Zermelo::OrderedSet.new,
|
995
|
+
'10' => Zermelo::OrderedSet.new(['5']))
|
872
996
|
end
|
873
997
|
|
874
998
|
it "deletes a record from the set" do
|
@@ -881,7 +1005,7 @@ describe Zermelo::Associations::Multiple do
|
|
881
1005
|
child = child_class.find_by_id('3')
|
882
1006
|
parent.children.remove(child)
|
883
1007
|
expect(parent.children.count).to eq(1)
|
884
|
-
expect(parent.children.ids).to eq(['4'])
|
1008
|
+
expect(parent.children.ids).to eq(Set.new(['4']))
|
885
1009
|
end
|
886
1010
|
|
887
1011
|
it "deletes a record from the set by id" do
|
@@ -893,7 +1017,7 @@ describe Zermelo::Associations::Multiple do
|
|
893
1017
|
expect(parent.children.count).to eq(2)
|
894
1018
|
parent.children.remove_ids('3')
|
895
1019
|
expect(parent.children.count).to eq(1)
|
896
|
-
expect(parent.children.ids).to eq(['4'])
|
1020
|
+
expect(parent.children.ids).to eq(Zermelo::OrderedSet.new(['4']))
|
897
1021
|
end
|
898
1022
|
|
899
1023
|
it "clears all records from the set" do
|
@@ -906,7 +1030,7 @@ describe Zermelo::Associations::Multiple do
|
|
906
1030
|
child = child_class.find_by_id('3')
|
907
1031
|
parent.children.clear
|
908
1032
|
expect(parent.children.count).to eq(0)
|
909
|
-
expect(parent.children.ids).to eq(
|
1033
|
+
expect(parent.children.ids).to eq(Zermelo::OrderedSet.new)
|
910
1034
|
end
|
911
1035
|
|
912
1036
|
context 'filters' do
|
@@ -922,7 +1046,7 @@ describe Zermelo::Associations::Multiple do
|
|
922
1046
|
it "by indexed attribute values" do
|
923
1047
|
upset_children = parent.children.intersect(:emotion => 'upset').all
|
924
1048
|
expect(upset_children).not_to be_nil
|
925
|
-
expect(upset_children).to
|
1049
|
+
expect(upset_children).to be_a(Zermelo::OrderedSet)
|
926
1050
|
expect(upset_children.size).to eq(2)
|
927
1051
|
expect(upset_children.map(&:id)).to eq(['4', '6'])
|
928
1052
|
end
|
@@ -930,7 +1054,7 @@ describe Zermelo::Associations::Multiple do
|
|
930
1054
|
it "by indexed attribute values with a regex search" do
|
931
1055
|
upset_children = parent.children.intersect(:emotion => /^ups/).all
|
932
1056
|
expect(upset_children).not_to be_nil
|
933
|
-
expect(upset_children).to
|
1057
|
+
expect(upset_children).to be_a(Zermelo::OrderedSet)
|
934
1058
|
expect(upset_children.size).to eq(2)
|
935
1059
|
expect(upset_children.map(&:id)).to eq(['4', '6'])
|
936
1060
|
end
|
@@ -939,7 +1063,7 @@ describe Zermelo::Associations::Multiple do
|
|
939
1063
|
range = Zermelo::Filters::IndexRange.new(0, 1)
|
940
1064
|
children = parent.children.intersect(:timestamp => range).all
|
941
1065
|
expect(children).not_to be_nil
|
942
|
-
expect(children).to
|
1066
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
943
1067
|
expect(children.size).to eq(2)
|
944
1068
|
expect(children.map(&:id)).to eq(['4', '5'])
|
945
1069
|
end
|
@@ -948,7 +1072,7 @@ describe Zermelo::Associations::Multiple do
|
|
948
1072
|
range = Zermelo::Filters::IndexRange.new(1, 2)
|
949
1073
|
children = parent.children.intersect(:timestamp => range).sort(:id, :desc => true).all
|
950
1074
|
expect(children).not_to be_nil
|
951
|
-
expect(children).to
|
1075
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
952
1076
|
expect(children.size).to eq(2)
|
953
1077
|
expect(children.map(&:id)).to eq(['6', '5'])
|
954
1078
|
end
|
@@ -957,7 +1081,7 @@ describe Zermelo::Associations::Multiple do
|
|
957
1081
|
range = Zermelo::Filters::IndexRange.new(time - 25, time - 5, :by_score => true)
|
958
1082
|
children = parent.children.intersect(:timestamp => range).all
|
959
1083
|
expect(children).not_to be_nil
|
960
|
-
expect(children).to
|
1084
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
961
1085
|
expect(children.size).to eq(2)
|
962
1086
|
expect(children.map(&:id)).to eq(['4', '5'])
|
963
1087
|
end
|
@@ -967,7 +1091,7 @@ describe Zermelo::Associations::Multiple do
|
|
967
1091
|
children = parent.children.intersect(:timestamp => range).
|
968
1092
|
sort(:timestamp, :desc => true).all
|
969
1093
|
expect(children).not_to be_nil
|
970
|
-
expect(children).to
|
1094
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
971
1095
|
expect(children.size).to eq(2)
|
972
1096
|
expect(children.map(&:id)).to eq(['5', '4'])
|
973
1097
|
end
|
@@ -994,7 +1118,7 @@ describe Zermelo::Associations::Multiple do
|
|
994
1118
|
children = parent.children.intersect(:id => ['4']).
|
995
1119
|
union(:emotion => 'upset', :id => ['4', '6']).all
|
996
1120
|
expect(children).not_to be_nil
|
997
|
-
expect(children).to
|
1121
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
998
1122
|
expect(children.size).to eq(2)
|
999
1123
|
expect(children.map(&:id)).to eq(['4', '6'])
|
1000
1124
|
end
|
@@ -1002,7 +1126,7 @@ describe Zermelo::Associations::Multiple do
|
|
1002
1126
|
it "ANDs multiple diff arguments, not ORs them" do
|
1003
1127
|
children = parent.children.diff(:emotion => 'upset', :id => ['4', '5']).all
|
1004
1128
|
expect(children).not_to be_nil
|
1005
|
-
expect(children).to
|
1129
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
1006
1130
|
expect(children.size).to eq(2)
|
1007
1131
|
expect(children.map(&:id)).to eq(['5', '6'])
|
1008
1132
|
end
|
@@ -1011,7 +1135,7 @@ describe Zermelo::Associations::Multiple do
|
|
1011
1135
|
range = Zermelo::Filters::IndexRange.new(0, 1)
|
1012
1136
|
children = parent.children.diff(:timestamp => range).all
|
1013
1137
|
expect(children).not_to be_nil
|
1014
|
-
expect(children).to
|
1138
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
1015
1139
|
expect(children.size).to eq(1)
|
1016
1140
|
expect(children.map(&:id)).to eq(['6'])
|
1017
1141
|
end
|
@@ -1020,7 +1144,7 @@ describe Zermelo::Associations::Multiple do
|
|
1020
1144
|
range = Zermelo::Filters::IndexRange.new(2, 2)
|
1021
1145
|
children = parent.children.diff(:timestamp => range).sort(:id, :desc => true).all
|
1022
1146
|
expect(children).not_to be_nil
|
1023
|
-
expect(children).to
|
1147
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
1024
1148
|
expect(children.size).to eq(2)
|
1025
1149
|
expect(children.map(&:id)).to eq(['5', '4'])
|
1026
1150
|
end
|
@@ -1029,7 +1153,7 @@ describe Zermelo::Associations::Multiple do
|
|
1029
1153
|
range = Zermelo::Filters::IndexRange.new(time - 25, time - 5, :by_score => true)
|
1030
1154
|
children = parent.children.diff(:timestamp => range).all
|
1031
1155
|
expect(children).not_to be_nil
|
1032
|
-
expect(children).to
|
1156
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
1033
1157
|
expect(children.size).to eq(1)
|
1034
1158
|
expect(children.map(&:id)).to eq(['6'])
|
1035
1159
|
end
|
@@ -1038,7 +1162,7 @@ describe Zermelo::Associations::Multiple do
|
|
1038
1162
|
range = Zermelo::Filters::IndexRange.new(time - 5, time, :by_score => true)
|
1039
1163
|
children = parent.children.diff(:timestamp => range).sort(:timestamp, :desc => true).all
|
1040
1164
|
expect(children).not_to be_nil
|
1041
|
-
expect(children).to
|
1165
|
+
expect(children).to be_a(Zermelo::OrderedSet)
|
1042
1166
|
expect(children.size).to eq(2)
|
1043
1167
|
expect(children.map(&:id)).to eq(['5', '4'])
|
1044
1168
|
end
|
@@ -27,7 +27,7 @@ describe Zermelo::Filter do
|
|
27
27
|
it "returns all record ids" do
|
28
28
|
examples = example_class.ids
|
29
29
|
expect(examples).not_to be_nil
|
30
|
-
expect(examples).to
|
30
|
+
expect(examples).to be_a(Set)
|
31
31
|
expect(examples.size).to eq(2)
|
32
32
|
expect(examples).to contain_exactly('8', '9')
|
33
33
|
end
|
@@ -42,7 +42,7 @@ describe Zermelo::Filter do
|
|
42
42
|
it "returns all records" do
|
43
43
|
examples = example_class.all
|
44
44
|
expect(examples).not_to be_nil
|
45
|
-
expect(examples).to
|
45
|
+
expect(examples).to be_a(Set)
|
46
46
|
expect(examples.size).to eq(2)
|
47
47
|
expect(examples.map(&:id)).to contain_exactly('9', '8')
|
48
48
|
end
|
@@ -57,7 +57,7 @@ describe Zermelo::Filter do
|
|
57
57
|
it "finds records by a uniquely indexed value" do
|
58
58
|
examples = example_class.intersect(:name => 'John Jones').all
|
59
59
|
expect(examples).not_to be_nil
|
60
|
-
expect(examples).to be_an(
|
60
|
+
expect(examples).to be_an(Set)
|
61
61
|
expect(examples.size).to eq(1)
|
62
62
|
example = examples.first
|
63
63
|
expect(example.id).to eq('8')
|
@@ -67,7 +67,7 @@ describe Zermelo::Filter do
|
|
67
67
|
it "filters all class records by indexed attribute values" do
|
68
68
|
example = example_class.intersect(:active => true).all
|
69
69
|
expect(example).not_to be_nil
|
70
|
-
expect(example).to
|
70
|
+
expect(example).to be_a(Set)
|
71
71
|
expect(example.size).to eq(1)
|
72
72
|
expect(example.map(&:id)).to eq(['8'])
|
73
73
|
end
|
@@ -75,7 +75,7 @@ describe Zermelo::Filter do
|
|
75
75
|
it 'filters by id attribute values' do
|
76
76
|
example = example_class.intersect(:id => '9').all
|
77
77
|
expect(example).not_to be_nil
|
78
|
-
expect(example).to
|
78
|
+
expect(example).to be_a(Set)
|
79
79
|
expect(example.size).to eq(1)
|
80
80
|
expect(example.map(&:id)).to eq(['9'])
|
81
81
|
end
|
@@ -86,7 +86,7 @@ describe Zermelo::Filter do
|
|
86
86
|
|
87
87
|
examples = example_class.intersect(:id => ['8', '10']).all
|
88
88
|
expect(examples).not_to be_nil
|
89
|
-
expect(examples).to
|
89
|
+
expect(examples).to be_a(Set)
|
90
90
|
expect(examples.size).to eq(2)
|
91
91
|
expect(examples.map(&:id)).to match_array(['8', '10'])
|
92
92
|
end
|
@@ -95,7 +95,7 @@ describe Zermelo::Filter do
|
|
95
95
|
examples = example_class.intersect(:active => true).
|
96
96
|
union(:active => false).all
|
97
97
|
expect(examples).not_to be_nil
|
98
|
-
expect(examples).to
|
98
|
+
expect(examples).to be_a(Set)
|
99
99
|
expect(examples.size).to eq(2)
|
100
100
|
expect(examples.map(&:id)).to match_array(['8', '9'])
|
101
101
|
end
|
@@ -104,7 +104,7 @@ describe Zermelo::Filter do
|
|
104
104
|
example = example_class.intersect(:active => true).
|
105
105
|
intersect(:name => 'John Jones').all
|
106
106
|
expect(example).not_to be_nil
|
107
|
-
expect(example).to
|
107
|
+
expect(example).to be_a(Set)
|
108
108
|
expect(example.size).to eq(1)
|
109
109
|
expect(example.map(&:id)).to eq(['8'])
|
110
110
|
end
|
@@ -113,7 +113,7 @@ describe Zermelo::Filter do
|
|
113
113
|
example = example_class.intersect(:active => true,
|
114
114
|
:name => 'John Jones').all
|
115
115
|
expect(example).not_to be_nil
|
116
|
-
expect(example).to
|
116
|
+
expect(example).to be_a(Set)
|
117
117
|
expect(example.size).to eq(1)
|
118
118
|
expect(example.map(&:id)).to eq(['8'])
|
119
119
|
end
|
@@ -124,7 +124,7 @@ describe Zermelo::Filter do
|
|
124
124
|
|
125
125
|
example = example_class.intersect(:active => true).diff(:name => 'Fred Bloggs').all
|
126
126
|
expect(example).not_to be_nil
|
127
|
-
expect(example).to
|
127
|
+
expect(example).to be_a(Set)
|
128
128
|
expect(example.size).to eq(1)
|
129
129
|
expect(example.map(&:id)).to eq(['8'])
|
130
130
|
end
|
@@ -138,7 +138,7 @@ describe Zermelo::Filter do
|
|
138
138
|
it 'finds records by regex match against a uniquely indexed value' do
|
139
139
|
examples = example_class.intersect(:name => /hn Jones/).all
|
140
140
|
expect(examples).not_to be_nil
|
141
|
-
expect(examples).to
|
141
|
+
expect(examples).to be_a(Set)
|
142
142
|
expect(examples.size).to eq(1)
|
143
143
|
example = examples.first
|
144
144
|
expect(example.id).to eq('8')
|
@@ -153,13 +153,13 @@ describe Zermelo::Filter do
|
|
153
153
|
|
154
154
|
it 'can append to a filter chain fragment more than once' do
|
155
155
|
inter = example_class.intersect(:active => true)
|
156
|
-
expect(inter.ids).to eq(['8'])
|
156
|
+
expect(inter.ids).to eq(Set.new(['8']))
|
157
157
|
|
158
158
|
union = inter.union(:name => 'James Brown')
|
159
|
-
expect(union.ids).to
|
159
|
+
expect(union.ids).to eq(Set.new(['8', '9']))
|
160
160
|
|
161
161
|
diff = inter.diff(:id => ['8'])
|
162
|
-
expect(diff.ids).to eq(
|
162
|
+
expect(diff.ids).to eq(Set.new)
|
163
163
|
end
|
164
164
|
|
165
165
|
it "ANDs multiple union arguments, not ORs them" do
|
@@ -167,7 +167,7 @@ describe Zermelo::Filter do
|
|
167
167
|
examples = example_class.intersect(:id => ['8']).
|
168
168
|
union(:id => ['9', '10'], :active => true).all
|
169
169
|
expect(examples).not_to be_nil
|
170
|
-
expect(examples).to
|
170
|
+
expect(examples).to be_a(Set)
|
171
171
|
expect(examples.size).to eq(2)
|
172
172
|
expect(examples.map(&:id)).to match_array(['8', '10'])
|
173
173
|
end
|
@@ -177,7 +177,7 @@ describe Zermelo::Filter do
|
|
177
177
|
examples = example_class.intersect(:id => ['8', '9', '10']).
|
178
178
|
diff(:id => ['9', '10'], :active => false).all
|
179
179
|
expect(examples).not_to be_nil
|
180
|
-
expect(examples).to
|
180
|
+
expect(examples).to be_a(Set)
|
181
181
|
expect(examples.size).to eq(2)
|
182
182
|
expect(examples.map(&:id)).to match_array(['8', '10'])
|
183
183
|
end
|
@@ -187,7 +187,7 @@ describe Zermelo::Filter do
|
|
187
187
|
examples = example_class.intersect(:id => ['8']).
|
188
188
|
union(:id => ['9', '10'], :name => [nil, /^Jam/]).all
|
189
189
|
expect(examples).not_to be_nil
|
190
|
-
expect(examples).to
|
190
|
+
expect(examples).to be_a(Set)
|
191
191
|
expect(examples.size).to eq(2)
|
192
192
|
expect(examples.map(&:id)).to match_array(['8', '9'])
|
193
193
|
end
|
@@ -197,7 +197,7 @@ describe Zermelo::Filter do
|
|
197
197
|
|
198
198
|
examples = example_class.intersect(:name => ['Jay Johns', 'James Brown']).all
|
199
199
|
expect(examples).not_to be_nil
|
200
|
-
expect(examples).to
|
200
|
+
expect(examples).to be_a(Set)
|
201
201
|
expect(examples.size).to eq(2)
|
202
202
|
expect(examples.map(&:id)).to match_array(['9', '10'])
|
203
203
|
end
|
@@ -208,7 +208,7 @@ describe Zermelo::Filter do
|
|
208
208
|
examples = example_class.intersect(:active => false).
|
209
209
|
union(:name => ['Jay Johns', 'James Brown']).all
|
210
210
|
expect(examples).not_to be_nil
|
211
|
-
expect(examples).to
|
211
|
+
expect(examples).to be_a(Set)
|
212
212
|
expect(examples.size).to eq(2)
|
213
213
|
expect(examples.map(&:id)).to match_array(['9', '10'])
|
214
214
|
end
|
@@ -216,7 +216,7 @@ describe Zermelo::Filter do
|
|
216
216
|
it 'excludes particular records' do
|
217
217
|
example = example_class.diff(:active => true).all
|
218
218
|
expect(example).not_to be_nil
|
219
|
-
expect(example).to
|
219
|
+
expect(example).to be_a(Set)
|
220
220
|
expect(example.size).to eq(1)
|
221
221
|
expect(example.map(&:id)).to eq(['9'])
|
222
222
|
end
|
@@ -260,7 +260,7 @@ describe Zermelo::Filter do
|
|
260
260
|
it 'sorts records by an attribute' do
|
261
261
|
example = example_class.sort(:name, :order => 'alpha').all
|
262
262
|
expect(example).not_to be_nil
|
263
|
-
expect(example).to
|
263
|
+
expect(example).to be_a(Set)
|
264
264
|
expect(example.size).to eq(2)
|
265
265
|
expect(example.map(&:id)).to eq(['9', '8'])
|
266
266
|
end
|
data/spec/support/mock_logger.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# Store logger messages in specs for possible later output
|
1
2
|
class MockLogger
|
2
3
|
attr_accessor :messages, :errors
|
3
4
|
|
4
|
-
def self.configure_log(name,
|
5
|
+
def self.configure_log(name, _config = {})
|
5
6
|
@name = name
|
6
7
|
end
|
7
8
|
|
@@ -20,7 +21,8 @@ class MockLogger
|
|
20
21
|
def #{level}(msg = nil, &block)
|
21
22
|
msg = yield if msg.nil? && block_given?
|
22
23
|
@messages << '[#{level.upcase}] :: ' +
|
23
|
-
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
24
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
25
|
+
msg
|
24
26
|
end
|
25
27
|
RUBY
|
26
28
|
end
|
@@ -30,9 +32,11 @@ class MockLogger
|
|
30
32
|
def #{level}(msg = nil, &block)
|
31
33
|
msg = yield if msg.nil? && block_given?
|
32
34
|
@messages << '[#{level.upcase}] :: ' +
|
33
|
-
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
35
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
36
|
+
msg
|
34
37
|
@errors << '[#{level.upcase}] :: ' +
|
35
|
-
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
38
|
+
(self.class.instance_variable_get('@name') || 'flapjack') + ' :: ' +
|
39
|
+
msg
|
36
40
|
end
|
37
41
|
ERRORS
|
38
42
|
end
|
@@ -44,5 +48,4 @@ class MockLogger
|
|
44
48
|
end
|
45
49
|
LEVELS
|
46
50
|
end
|
47
|
-
|
48
|
-
end
|
51
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'rspec/core/formatters/base_formatter'
|
2
2
|
|
3
|
+
# Outputs how log each spec takes to run
|
3
4
|
class ProfileAllFormatter < RSpec::Core::Formatters::BaseFormatter
|
4
|
-
|
5
5
|
RSpec::Core::Formatters.register self,
|
6
|
-
|
6
|
+
:example_started, :example_passed,
|
7
|
+
:start_dump
|
7
8
|
|
8
9
|
def initialize(output)
|
9
10
|
super(output)
|
@@ -12,33 +13,33 @@ class ProfileAllFormatter < RSpec::Core::Formatters::BaseFormatter
|
|
12
13
|
|
13
14
|
def start(notification)
|
14
15
|
super(notification)
|
15
|
-
@output.puts
|
16
|
+
@output.puts 'Profiling enabled.'
|
16
17
|
end
|
17
18
|
|
18
|
-
def example_started(
|
19
|
-
@time = (
|
19
|
+
def example_started(_notification)
|
20
|
+
@time = (Time.respond_to?(:zone) && Time.zone) ? Time.zone.now : Time.now
|
20
21
|
end
|
21
22
|
|
22
23
|
def example_passed(notification)
|
24
|
+
now = (Time.respond_to?(:zone) && Time.zone) ? Time.zone.now : Time.now
|
23
25
|
@example_times << [
|
24
26
|
notification.example.example_group.description,
|
25
27
|
notification.example.description,
|
26
|
-
|
28
|
+
now - @time
|
27
29
|
]
|
28
30
|
end
|
29
31
|
|
30
|
-
def start_dump(
|
32
|
+
def start_dump(_notification)
|
31
33
|
@output.puts "\n\nExample times:\n"
|
32
34
|
|
33
|
-
@example_times = @example_times.sort_by do |
|
35
|
+
@example_times = @example_times.sort_by do |_description, _example, time|
|
34
36
|
time
|
35
37
|
end.reverse
|
36
38
|
|
37
39
|
@example_times.each do |description, example, time|
|
38
|
-
@output.print
|
40
|
+
@output.print format('%.7f', time)
|
39
41
|
@output.puts " #{description} #{example}"
|
40
42
|
end
|
41
43
|
@output.flush
|
42
44
|
end
|
43
|
-
|
44
|
-
end
|
45
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'rspec/core/formatters/base_text_formatter'
|
2
2
|
|
3
|
+
# Output text witout colourising it
|
3
4
|
module NoColorizer
|
4
|
-
def self.wrap(text,
|
5
|
+
def self.wrap(text, _)
|
5
6
|
text
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
10
|
+
# Re-implements RSpec's doc formatter without ANSI text colours
|
9
11
|
class UncoloredDocFormatter < RSpec::Core::Formatters::BaseTextFormatter
|
10
12
|
RSpec::Core::Formatters.register(self, :example_group_started,
|
11
13
|
:example_group_finished,
|
@@ -25,7 +27,7 @@ class UncoloredDocFormatter < RSpec::Core::Formatters::BaseTextFormatter
|
|
25
27
|
@group_level += 1
|
26
28
|
end
|
27
29
|
|
28
|
-
def example_group_finished(
|
30
|
+
def example_group_finished(_)
|
29
31
|
@group_level -= 1
|
30
32
|
end
|
31
33
|
|
@@ -34,12 +36,12 @@ class UncoloredDocFormatter < RSpec::Core::Formatters::BaseTextFormatter
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def example_pending(pending)
|
37
|
-
output.puts "#{current_indentation}#{pending.example.description.strip} "
|
39
|
+
output.puts "#{current_indentation}#{pending.example.description.strip} " \
|
38
40
|
"(PENDING: #{pending.example.execution_result.pending_message})"
|
39
41
|
end
|
40
42
|
|
41
43
|
def example_failed(failure)
|
42
|
-
output.puts "#{current_indentation}#{failure.example.description.strip} "
|
44
|
+
output.puts "#{current_indentation}#{failure.example.description.strip} " \
|
43
45
|
"(FAILED - #{next_failure_index})"
|
44
46
|
end
|
45
47
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zermelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ali Graham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/zermelo/filters/steps/sort_step.rb
|
92
92
|
- lib/zermelo/locks/no_lock.rb
|
93
93
|
- lib/zermelo/locks/redis_lock.rb
|
94
|
+
- lib/zermelo/ordered_set.rb
|
94
95
|
- lib/zermelo/record.rb
|
95
96
|
- lib/zermelo/records/attributes.rb
|
96
97
|
- lib/zermelo/records/class_methods.rb
|