memstore 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +34 -12
- data/lib/memstore.rb +7 -191
- data/lib/memstore/hashstore.rb +45 -0
- data/lib/memstore/json.rb +8 -2
- data/lib/memstore/msgpack.rb +8 -2
- data/lib/memstore/objectstore.rb +98 -0
- data/lib/memstore/queries.rb +94 -0
- data/lib/memstore/version.rb +1 -1
- data/lib/memstore/yaml.rb +12 -2
- data/spec/hashstore_spec.rb +58 -0
- data/spec/{memstore_json_spec.rb → json_spec.rb} +19 -0
- data/spec/{memstore_msgpack_spec.rb → msgpack_spec.rb} +19 -0
- data/spec/objectstore_spec.rb +113 -0
- data/spec/queries_spec.rb +107 -0
- data/spec/{memstore_yaml_spec.rb → yaml_spec.rb} +19 -0
- metadata +16 -9
- data/spec/memstore_spec.rb +0 -268
data/README.md
CHANGED
@@ -29,10 +29,10 @@ $ gem install memstore
|
|
29
29
|
## Usage
|
30
30
|
|
31
31
|
- [Basics](#basics)
|
32
|
-
- [Objects vs. Hashes](#objects-vs-hashes)
|
33
|
-
- [Adding Items](#adding-items)
|
34
|
-
- [Retrieving Items](#retrieving-items)
|
35
|
-
- [Deleting Items](#deleting-items)
|
32
|
+
- [Objects vs. Hashes](#objects-vs-hashes)
|
33
|
+
- [Adding Items](#adding-items)
|
34
|
+
- [Retrieving Items](#retrieving-items)
|
35
|
+
- [Deleting Items](#deleting-items)
|
36
36
|
- [Search Queries](#search-queries)
|
37
37
|
- [Serialization](#serialization)
|
38
38
|
- [Binary](#binary)
|
@@ -40,6 +40,7 @@ $ gem install memstore
|
|
40
40
|
- [YAML](#yaml)
|
41
41
|
- [JSON](#json)
|
42
42
|
- [MessagePack](#messagepack)
|
43
|
+
- [Concurrent Access](#concurrent-access)
|
43
44
|
|
44
45
|
### Basics
|
45
46
|
|
@@ -68,7 +69,7 @@ store = MemStore.new(:id, { ... }) # to use custom key
|
|
68
69
|
|
69
70
|
The collection must be a hash that correctly maps the used key to each item.
|
70
71
|
|
71
|
-
|
72
|
+
#### Objects vs. Hashes
|
72
73
|
|
73
74
|
MemStore comes in two flavors: `ObjectStore` and `HashStore`.
|
74
75
|
|
@@ -90,7 +91,7 @@ store = MemStore::HashStore.new
|
|
90
91
|
|
91
92
|
If no key attribute is specified, `HashStore` will also use `Object#hash`.
|
92
93
|
|
93
|
-
|
94
|
+
#### Adding Items
|
94
95
|
|
95
96
|
`items` provides direct access to the internal items hash.
|
96
97
|
|
@@ -123,7 +124,7 @@ store << a << b << c
|
|
123
124
|
# => store
|
124
125
|
```
|
125
126
|
|
126
|
-
|
127
|
+
#### Retrieving Items
|
127
128
|
|
128
129
|
`length` (or `size`) returns the current number of items:
|
129
130
|
|
@@ -152,7 +153,7 @@ store[1..3, 6]
|
|
152
153
|
# => [a, b, c, f]
|
153
154
|
```
|
154
155
|
|
155
|
-
|
156
|
+
#### Deleting Items
|
156
157
|
|
157
158
|
`delete_items` (or `delete_item`) deletes items by reference and returns them.
|
158
159
|
This is considered the default use case and therefore also available as `delete`.
|
@@ -187,17 +188,17 @@ store.delete_keys(5..7, 9)
|
|
187
188
|
|
188
189
|
The following methods are available to query the data store:
|
189
190
|
|
190
|
-
- `find_all` (
|
191
|
+
- `find_all` (alias `find`)
|
191
192
|
- `find_any`
|
192
193
|
- `find_one`
|
193
194
|
- `find_not_all`
|
194
195
|
- `find_none`
|
195
|
-
- `first_all` (
|
196
|
+
- `first_all` (alias `first`)
|
196
197
|
- `first_any`
|
197
198
|
- `first_one`
|
198
199
|
- `first_not_all`
|
199
200
|
- `first_none`
|
200
|
-
- `count_all` (
|
201
|
+
- `count_all` (alias `count`)
|
201
202
|
- `count_any`
|
202
203
|
- `count_one`
|
203
204
|
- `count_not_all`
|
@@ -221,7 +222,7 @@ In other words:
|
|
221
222
|
|
222
223
|
- `all` means `condition && condition && ...`.
|
223
224
|
- `any` means `condition || condition || ...`.
|
224
|
-
- `one` means `condition ^ condition ^
|
225
|
+
- `one` means `condition ^ condition ^ ...`.
|
225
226
|
- `not all` means `!(condition && condition && ...)` or `!condition || !condition || ...`.
|
226
227
|
- `none` means `!(condition || condition || ...)` or `!condition && !condition && ...`.
|
227
228
|
|
@@ -405,6 +406,27 @@ store[1]
|
|
405
406
|
# => { "id" => 1 }
|
406
407
|
```
|
407
408
|
|
409
|
+
#### Concurrent Access
|
410
|
+
|
411
|
+
To support concurrent access, e.g. when multiple threads or processes read and write the same data store file, there’s a `with_file` equivalent of `from_file` and `to_file`.
|
412
|
+
|
413
|
+
`with_file` takes a file (name) and a block. It then obtains an exclusive lock on that file, restores a data store from it or creates a new one, invokes the block with the data store and finally saves the data store back to the file. Optionally, key and/or items can be specified for when the data store is created instead of restored.
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
MemStore.with_file(file, key, items) do |store|
|
417
|
+
# create, read, update, delete
|
418
|
+
end
|
419
|
+
```
|
420
|
+
|
421
|
+
`MemStore.with_file` is a shortcut to `MemStore::ObjectStore.with_file`.
|
422
|
+
|
423
|
+
`HashStore` offers a locking version of all its serialization methods:
|
424
|
+
|
425
|
+
- `with_file` by default,
|
426
|
+
- `with_yaml_file` when `memstore/yaml` is included,
|
427
|
+
- `with_json_file` when `memstore/json` is included,
|
428
|
+
- `with_msgpack_file` when `memstore/msgpack` is included.
|
429
|
+
|
408
430
|
## Contributing
|
409
431
|
|
410
432
|
1. Fork it on [GitHub](https://github.com/sklppr/memstore).
|
data/lib/memstore.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "memstore/version"
|
4
|
+
require "memstore/objectstore"
|
5
|
+
require "memstore/hashstore"
|
6
|
+
require "memstore/queries"
|
2
7
|
|
3
8
|
module MemStore
|
4
9
|
|
@@ -14,197 +19,8 @@ module MemStore
|
|
14
19
|
ObjectStore.from_file(file)
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
def initialize(key=nil, items={})
|
20
|
-
@key = key || :hash
|
21
|
-
@items = items
|
22
|
-
end
|
23
|
-
|
24
|
-
attr_accessor :items
|
25
|
-
|
26
|
-
def insert(*items)
|
27
|
-
items.each { |item| @items[key(item)] = item }
|
28
|
-
self
|
29
|
-
end
|
30
|
-
alias_method :<<, :insert
|
31
|
-
|
32
|
-
def length
|
33
|
-
@items.length
|
34
|
-
end
|
35
|
-
alias_method :size, :length
|
36
|
-
alias_method :count, :length
|
37
|
-
|
38
|
-
def [](*keys)
|
39
|
-
return @items[keys.first] if keys.length == 1 && !keys.first.is_a?(Range)
|
40
|
-
keys.inject [] do |items, key|
|
41
|
-
if key.is_a?(Range) then key.inject(items) { |i, k| i << @items[k] }
|
42
|
-
else items << @items[key] end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def all
|
47
|
-
@items.values
|
48
|
-
end
|
49
|
-
|
50
|
-
def delete_items(*items)
|
51
|
-
return @items.delete(key(items.first)) if items.length == 1
|
52
|
-
items.collect { |item| @items.delete(key(item)) }
|
53
|
-
end
|
54
|
-
alias_method :delete_item, :delete_items
|
55
|
-
alias_method :delete, :delete_items
|
56
|
-
|
57
|
-
def delete_keys(*keys)
|
58
|
-
return @items.delete(keys.first) if keys.length == 1 && !keys.first.is_a?(Range)
|
59
|
-
keys.inject [] do |items, key|
|
60
|
-
if key.is_a?(Range) then key.inject(items) { |i, k| i << @items.delete(k) }
|
61
|
-
else items << @items.delete(key) end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
alias_method :delete_key, :delete_keys
|
65
|
-
|
66
|
-
def find_all(conditions={}, &block)
|
67
|
-
all.select { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
68
|
-
end
|
69
|
-
alias_method :find, :find_all
|
70
|
-
|
71
|
-
def find_any(conditions={}, &block)
|
72
|
-
all.select { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
73
|
-
end
|
74
|
-
|
75
|
-
def find_one(conditions={}, &block)
|
76
|
-
all.select { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
77
|
-
end
|
78
|
-
|
79
|
-
def find_not_all(conditions={}, &block)
|
80
|
-
all.reject { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
81
|
-
end
|
82
|
-
|
83
|
-
def find_none(conditions={}, &block)
|
84
|
-
all.select { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
85
|
-
end
|
86
|
-
|
87
|
-
def first_all(conditions={}, &block)
|
88
|
-
all.detect { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
89
|
-
end
|
90
|
-
alias_method :first, :first_all
|
91
|
-
|
92
|
-
def first_any(conditions={}, &block)
|
93
|
-
all.detect { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
94
|
-
end
|
95
|
-
|
96
|
-
def first_one(conditions={}, &block)
|
97
|
-
all.detect { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
98
|
-
end
|
99
|
-
|
100
|
-
def first_not_all(conditions={}, &block)
|
101
|
-
all.detect { |item| !instance_exec(item, conditions, block, &FIND_ALL) }
|
102
|
-
end
|
103
|
-
|
104
|
-
def first_none(conditions={}, &block)
|
105
|
-
all.detect { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
106
|
-
end
|
107
|
-
|
108
|
-
def count_all(conditions={}, &block)
|
109
|
-
all.count { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
110
|
-
end
|
111
|
-
alias_method :count, :count_all
|
112
|
-
|
113
|
-
def count_any(conditions={}, &block)
|
114
|
-
all.count { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
115
|
-
end
|
116
|
-
|
117
|
-
def count_one(conditions={}, &block)
|
118
|
-
all.count { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
119
|
-
end
|
120
|
-
|
121
|
-
def count_not_all(conditions={}, &block)
|
122
|
-
all.count { |item| !instance_exec(item, conditions, block, &FIND_ALL) }
|
123
|
-
end
|
124
|
-
|
125
|
-
def count_none(conditions={}, &block)
|
126
|
-
all.count { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
127
|
-
end
|
128
|
-
|
129
|
-
def self.from_binary(binary)
|
130
|
-
begin Marshal.load(binary) rescue nil end
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.from_file(file)
|
134
|
-
begin self.from_binary(IO.read(file)) rescue nil end
|
135
|
-
end
|
136
|
-
|
137
|
-
def to_binary
|
138
|
-
Marshal.dump(self)
|
139
|
-
end
|
140
|
-
|
141
|
-
def to_file(file)
|
142
|
-
IO.write(file, self.to_binary)
|
143
|
-
end
|
144
|
-
|
145
|
-
private
|
146
|
-
|
147
|
-
FIND_ALL = Proc.new do |item, conditions, block|
|
148
|
-
conditions.all? { |attribute, condition| condition === attr(item, attribute) } &&
|
149
|
-
if block then !!block.call(item) else true end
|
150
|
-
end
|
151
|
-
|
152
|
-
FIND_ANY = Proc.new do |item, conditions, block|
|
153
|
-
conditions.any? { |attribute, condition| condition === attr(item, attribute) } ||
|
154
|
-
if block then !!block.call(item) else false end
|
155
|
-
end
|
156
|
-
|
157
|
-
FIND_NONE = Proc.new do |item, conditions, block|
|
158
|
-
conditions.none? { |attribute, condition| condition === attr(item, attribute) } &&
|
159
|
-
if block then !!block.call(item) else true end
|
160
|
-
end
|
161
|
-
|
162
|
-
FIND_ONE = Proc.new do |item, conditions, block|
|
163
|
-
conditions.one? { |attribute, condition| condition === attr(item, attribute) } ||
|
164
|
-
if block then !!block.call(item) else false end
|
165
|
-
end
|
166
|
-
|
167
|
-
def key(item)
|
168
|
-
item.send(@key)
|
169
|
-
end
|
170
|
-
|
171
|
-
def attr(item, attribute)
|
172
|
-
item.send(attribute)
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
class HashStore < ObjectStore
|
178
|
-
|
179
|
-
def self.from_hash(hash)
|
180
|
-
begin
|
181
|
-
key = hash[:key] || hash["key"]
|
182
|
-
items = hash[:items] || hash["items"]
|
183
|
-
return nil if key.nil? || items.nil?
|
184
|
-
self.new(key, items)
|
185
|
-
rescue
|
186
|
-
nil
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def initialize(key=nil, items={})
|
191
|
-
@key, @items = key, items
|
192
|
-
end
|
193
|
-
|
194
|
-
def to_hash
|
195
|
-
{ key: @key, items: @items }
|
196
|
-
end
|
197
|
-
|
198
|
-
private
|
199
|
-
|
200
|
-
def key(item)
|
201
|
-
if @key.nil? then item.hash else item[@key] end
|
202
|
-
end
|
203
|
-
|
204
|
-
def attr(item, attribute)
|
205
|
-
item[attribute]
|
206
|
-
end
|
207
|
-
|
22
|
+
def self.with_file(file, key=nil, items={}, &block)
|
23
|
+
ObjectStore.with_file(file, key, items, &block)
|
208
24
|
end
|
209
25
|
|
210
26
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MemStore
|
4
|
+
|
5
|
+
class HashStore < ObjectStore
|
6
|
+
|
7
|
+
def self.from_hash(hash)
|
8
|
+
begin
|
9
|
+
|
10
|
+
if hash.has_key?(:key) then key = hash[:key]
|
11
|
+
elsif hash.has_key? "key" then key = hash["key"]
|
12
|
+
else return nil end
|
13
|
+
|
14
|
+
if hash.has_key?(:items) then items = hash[:items]
|
15
|
+
elsif hash.has_key? "items" then items = hash["items"]
|
16
|
+
else return nil end
|
17
|
+
|
18
|
+
if items.is_a?(Hash) then self.new(key, items) else self.new(key) end
|
19
|
+
|
20
|
+
rescue
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(key=nil, items={})
|
26
|
+
@key, @items = key, items
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
{ key: @key, items: @items }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def key(item)
|
36
|
+
if @key.nil? then item.hash else item[@key] end
|
37
|
+
end
|
38
|
+
|
39
|
+
def attr(item, attribute)
|
40
|
+
item[attribute]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/memstore/json.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "json"
|
2
4
|
|
3
5
|
module MemStore
|
@@ -13,11 +15,15 @@ module MemStore
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.from_json(json)
|
16
|
-
|
18
|
+
self.from_hash(JSON.parse(json)) rescue nil
|
17
19
|
end
|
18
20
|
|
19
21
|
def self.from_json_file(file)
|
20
|
-
|
22
|
+
self.from_json(IO.read(file)) rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.with_json_file(file, key=nil, items={}, &block)
|
26
|
+
self.run_with_file(:from_json_file, :to_json_file, file, key, items, &block)
|
21
27
|
end
|
22
28
|
|
23
29
|
end
|
data/lib/memstore/msgpack.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "msgpack"
|
2
4
|
|
3
5
|
module MemStore
|
@@ -13,11 +15,15 @@ module MemStore
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.from_msgpack(msgpack)
|
16
|
-
|
18
|
+
self.from_hash(MessagePack.unpack(msgpack)) rescue nil
|
17
19
|
end
|
18
20
|
|
19
21
|
def self.from_msgpack_file(file)
|
20
|
-
|
22
|
+
self.from_msgpack(IO.read(file)) rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.with_msgpack_file(file, key=nil, items={}, &block)
|
26
|
+
self.run_with_file(:from_msgpack_file, :to_msgpack_file, file, key, items, &block)
|
21
27
|
end
|
22
28
|
|
23
29
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MemStore
|
4
|
+
|
5
|
+
class ObjectStore
|
6
|
+
|
7
|
+
def initialize(key=nil, items={})
|
8
|
+
@key = key || :hash
|
9
|
+
@items = items
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :items
|
13
|
+
|
14
|
+
def insert(*items)
|
15
|
+
items.each { |item| @items[key(item)] = item }
|
16
|
+
self
|
17
|
+
end
|
18
|
+
alias_method :<<, :insert
|
19
|
+
|
20
|
+
def length
|
21
|
+
@items.length
|
22
|
+
end
|
23
|
+
alias_method :size, :length
|
24
|
+
alias_method :count, :length
|
25
|
+
|
26
|
+
def [](*keys)
|
27
|
+
return @items[keys.first] if keys.length == 1 && !keys.first.is_a?(Range)
|
28
|
+
keys.inject [] do |items, key|
|
29
|
+
if key.is_a?(Range) then key.inject(items) { |i, k| i << @items[k] }
|
30
|
+
else items << @items[key] end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def all
|
35
|
+
@items.values
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_items(*items)
|
39
|
+
return @items.delete(key(items.first)) if items.length == 1
|
40
|
+
items.collect { |item| @items.delete(key(item)) }
|
41
|
+
end
|
42
|
+
alias_method :delete_item, :delete_items
|
43
|
+
alias_method :delete, :delete_items
|
44
|
+
|
45
|
+
def delete_keys(*keys)
|
46
|
+
return @items.delete(keys.first) if keys.length == 1 && !keys.first.is_a?(Range)
|
47
|
+
keys.inject [] do |items, key|
|
48
|
+
if key.is_a?(Range) then key.inject(items) { |i, k| i << @items.delete(k) }
|
49
|
+
else items << @items.delete(key) end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :delete_key, :delete_keys
|
53
|
+
|
54
|
+
def self.from_binary(binary)
|
55
|
+
restored = Marshal.load(binary) rescue nil
|
56
|
+
if restored.kind_of?(ObjectStore) || restored.kind_of?(HashStore) then restored else nil end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.from_file(file)
|
60
|
+
self.from_binary(IO.read(file)) rescue nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_binary
|
64
|
+
Marshal.dump(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_file(file)
|
68
|
+
IO.write(file, self.to_binary)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.with_file(file, key=nil, items={}, &block)
|
72
|
+
self.run_with_file(:from_file, :to_file, file, key, items, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def key(item)
|
78
|
+
item.send(@key)
|
79
|
+
end
|
80
|
+
|
81
|
+
def attr(item, attribute)
|
82
|
+
item.send(attribute)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.run_with_file(from_file_method, to_file_method, file, key=nil, items={}, &block)
|
86
|
+
File.open(file) do |file|
|
87
|
+
file.flock(File::LOCK_EX)
|
88
|
+
store = self.send(from_file_method, file) || self.new(key, items)
|
89
|
+
result = block.call(store)
|
90
|
+
store.send(to_file_method, file)
|
91
|
+
file.flock(File::LOCK_UN)
|
92
|
+
result
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MemStore
|
4
|
+
|
5
|
+
class ObjectStore
|
6
|
+
|
7
|
+
def find_all(conditions={}, &block)
|
8
|
+
all.select { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
9
|
+
end
|
10
|
+
alias_method :find, :find_all
|
11
|
+
|
12
|
+
def find_any(conditions={}, &block)
|
13
|
+
all.select { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_one(conditions={}, &block)
|
17
|
+
all.select { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_not_all(conditions={}, &block)
|
21
|
+
all.reject { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_none(conditions={}, &block)
|
25
|
+
all.select { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def first_all(conditions={}, &block)
|
29
|
+
all.detect { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
30
|
+
end
|
31
|
+
alias_method :first, :first_all
|
32
|
+
|
33
|
+
def first_any(conditions={}, &block)
|
34
|
+
all.detect { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def first_one(conditions={}, &block)
|
38
|
+
all.detect { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def first_not_all(conditions={}, &block)
|
42
|
+
all.detect { |item| !instance_exec(item, conditions, block, &FIND_ALL) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def first_none(conditions={}, &block)
|
46
|
+
all.detect { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def count_all(conditions={}, &block)
|
50
|
+
all.count { |item| instance_exec(item, conditions, block, &FIND_ALL) }
|
51
|
+
end
|
52
|
+
alias_method :count, :count_all
|
53
|
+
|
54
|
+
def count_any(conditions={}, &block)
|
55
|
+
all.count { |item| instance_exec(item, conditions, block, &FIND_ANY) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def count_one(conditions={}, &block)
|
59
|
+
all.count { |item| instance_exec(item, conditions, block, &FIND_ONE) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def count_not_all(conditions={}, &block)
|
63
|
+
all.count { |item| !instance_exec(item, conditions, block, &FIND_ALL) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def count_none(conditions={}, &block)
|
67
|
+
all.count { |item| instance_exec(item, conditions, block, &FIND_NONE) }
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
FIND_ALL = Proc.new do |item, conditions, block|
|
73
|
+
conditions.all? { |attribute, condition| condition === attr(item, attribute) } &&
|
74
|
+
if block then !!block.call(item) else true end
|
75
|
+
end
|
76
|
+
|
77
|
+
FIND_ANY = Proc.new do |item, conditions, block|
|
78
|
+
conditions.any? { |attribute, condition| condition === attr(item, attribute) } ||
|
79
|
+
if block then !!block.call(item) else false end
|
80
|
+
end
|
81
|
+
|
82
|
+
FIND_NONE = Proc.new do |item, conditions, block|
|
83
|
+
conditions.none? { |attribute, condition| condition === attr(item, attribute) } &&
|
84
|
+
if block then !!block.call(item) else true end
|
85
|
+
end
|
86
|
+
|
87
|
+
FIND_ONE = Proc.new do |item, conditions, block|
|
88
|
+
conditions.one? { |attribute, condition| condition === attr(item, attribute) } ||
|
89
|
+
if block then !!block.call(item) else false end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/lib/memstore/version.rb
CHANGED
data/lib/memstore/yaml.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "yaml"
|
2
4
|
|
3
5
|
module MemStore
|
@@ -13,11 +15,19 @@ module MemStore
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.from_yaml(yaml)
|
16
|
-
begin
|
18
|
+
begin
|
19
|
+
self.from_hash(YAML.load(yaml))
|
20
|
+
rescue StandardError, Psych::SyntaxError
|
21
|
+
nil
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def self.from_yaml_file(file)
|
20
|
-
|
26
|
+
self.from_yaml(IO.read(file)) rescue nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.with_yaml_file(file, key=nil, items={}, &block)
|
30
|
+
self.run_with_file(:from_yaml_file, :to_yaml_file, file, key, items, &block)
|
21
31
|
end
|
22
32
|
|
23
33
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "tempfile"
|
5
|
+
require "memstore"
|
6
|
+
|
7
|
+
describe MemStore::HashStore do
|
8
|
+
|
9
|
+
before do
|
10
|
+
@store = MemStore::HashStore.new(:id)
|
11
|
+
10.times { |i| @store << { id: i } }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can be instantiated with items" do
|
15
|
+
h = { a: 1, b: 2, c: 3 }
|
16
|
+
store = MemStore::HashStore.new(nil, h)
|
17
|
+
store.items.must_equal h
|
18
|
+
end
|
19
|
+
|
20
|
+
it "indexes items by Object#hash by default" do
|
21
|
+
h = {}
|
22
|
+
store = MemStore::HashStore.new.insert(h)
|
23
|
+
store.items[h.hash].must_equal h
|
24
|
+
end
|
25
|
+
|
26
|
+
it "indexes items using a custom key" do
|
27
|
+
@store.items[0].must_equal @store.all.first
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can be converted to and from binary" do
|
31
|
+
restored = MemStore::HashStore.from_binary(@store.to_binary)
|
32
|
+
restored.must_be_instance_of MemStore::HashStore
|
33
|
+
restored.items.must_equal @store.items
|
34
|
+
restored.instance_variable_get(:@key).must_equal @store.instance_variable_get(:@key)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can be serialized to and deserialized from a binary file" do
|
38
|
+
tmp = Tempfile.new("memstore")
|
39
|
+
@store.to_file(tmp)
|
40
|
+
restored = MemStore::HashStore.from_file(tmp)
|
41
|
+
restored.must_be_instance_of MemStore::HashStore
|
42
|
+
restored.items.must_equal @store.items
|
43
|
+
restored.instance_variable_get(:@key).must_equal @store.instance_variable_get(:@key)
|
44
|
+
tmp.unlink
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can be converted to and from a hash" do
|
48
|
+
restored = MemStore::HashStore.from_hash(@store.to_hash)
|
49
|
+
restored.items.must_equal @store.items
|
50
|
+
restored.instance_variable_get(:@key).must_equal @store.instance_variable_get(:@key)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns nil when conversion from hash fails" do
|
54
|
+
MemStore::HashStore.from_hash(nil).must_equal nil
|
55
|
+
MemStore::HashStore.from_hash({}).must_equal nil
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -28,4 +28,23 @@ describe MemStore::HashStore do
|
|
28
28
|
tmp.unlink
|
29
29
|
end
|
30
30
|
|
31
|
+
it "returns nil when conversion from JSON fails" do
|
32
|
+
MemStore::HashStore.from_json(nil).must_equal nil
|
33
|
+
MemStore::HashStore.from_json(Marshal.dump(Object.new)).must_equal nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "supports concurrent access to a single JSON file" do
|
37
|
+
tmp = Tempfile.new("memstore")
|
38
|
+
fork do
|
39
|
+
MemStore::HashStore.with_json_file(tmp, "id") { |store| store.insert({"id"=>"1"}, {"id"=>"3"}) }
|
40
|
+
end
|
41
|
+
fork do
|
42
|
+
MemStore::HashStore.with_json_file(tmp, "id") { |store| store.insert({"id"=>"2"}, {"id"=>"4"}) }
|
43
|
+
end
|
44
|
+
sleep 0.1
|
45
|
+
restored = MemStore::HashStore.from_json_file(tmp)
|
46
|
+
restored.items.must_equal({"1"=>{"id"=>"1"}, "2"=>{"id"=>"2"}, "3"=>{"id"=>"3"}, "4"=>{"id"=>"4"}})
|
47
|
+
tmp.unlink
|
48
|
+
end
|
49
|
+
|
31
50
|
end
|
@@ -28,4 +28,23 @@ describe MemStore::HashStore do
|
|
28
28
|
tmp.unlink
|
29
29
|
end
|
30
30
|
|
31
|
+
it "returns nil when conversion from MessagePack fails" do
|
32
|
+
MemStore::HashStore.from_msgpack(nil).must_equal nil
|
33
|
+
MemStore::HashStore.from_msgpack(Marshal.dump(Object.new)).must_equal nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "supports concurrent access to a single MessagePack file" do
|
37
|
+
tmp = Tempfile.new("memstore")
|
38
|
+
fork do
|
39
|
+
MemStore::HashStore.with_msgpack_file(tmp, "id") { |store| store.insert({"id"=>1}, {"id"=>3}) }
|
40
|
+
end
|
41
|
+
fork do
|
42
|
+
MemStore::HashStore.with_msgpack_file(tmp, "id") { |store| store.insert({"id"=>2}, {"id"=>4}) }
|
43
|
+
end
|
44
|
+
sleep 0.1
|
45
|
+
restored = MemStore::HashStore.from_msgpack_file(tmp)
|
46
|
+
restored.items.must_equal({1=>{"id"=>1}, 2=>{"id"=>2}, 3=>{"id"=>3}, 4=>{"id"=>4}})
|
47
|
+
tmp.unlink
|
48
|
+
end
|
49
|
+
|
31
50
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "tempfile"
|
5
|
+
require "memstore"
|
6
|
+
|
7
|
+
describe MemStore::ObjectStore do
|
8
|
+
|
9
|
+
before do
|
10
|
+
@store = MemStore::ObjectStore.new(:to_i)
|
11
|
+
10.times { |i| @store << i.to_f }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can be instantiated with items" do
|
15
|
+
h = { a: 1, b: 2, c: 3 }
|
16
|
+
store = MemStore::ObjectStore.new(nil, h)
|
17
|
+
store.items.must_equal h
|
18
|
+
end
|
19
|
+
|
20
|
+
it "is the default when instantiating MemStore" do
|
21
|
+
MemStore.new.must_be_instance_of MemStore::ObjectStore
|
22
|
+
end
|
23
|
+
|
24
|
+
it "indexes items by Object#hash by default" do
|
25
|
+
o = Object.new
|
26
|
+
store = MemStore::ObjectStore.new.insert(o)
|
27
|
+
store.items[o.hash].must_equal o
|
28
|
+
end
|
29
|
+
|
30
|
+
it "indexes items using a custom key" do
|
31
|
+
@store.items[0].must_equal 0.0
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns the overall number of items" do
|
35
|
+
@store.length.must_equal 10
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns a single item by itself" do
|
39
|
+
@store[3].must_equal 3.0
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns multiple items as an array" do
|
43
|
+
@store[3, 4, 5, 6].must_equal [3.0, 4.0, 5.0, 6.0]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns multiple items using a Range as an array" do
|
47
|
+
@store[0..9].must_equal @store.all
|
48
|
+
end
|
49
|
+
|
50
|
+
it "deletes a single item by reference and returns it by itself" do
|
51
|
+
@store.delete_item(3.0).must_equal 3.0
|
52
|
+
end
|
53
|
+
|
54
|
+
it "deletes multiple items by reference and returns them" do
|
55
|
+
@store.delete_items(3.0, 4.0, 5.0, 6.0).must_equal [3.0, 4.0, 5.0, 6.0]
|
56
|
+
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "deletes a single item by key and returns it by itself" do
|
60
|
+
@store.delete_key(3).must_equal 3.0
|
61
|
+
end
|
62
|
+
|
63
|
+
it "deletes multiple items by key and returns them as an array" do
|
64
|
+
@store.delete_keys(3, 4, 5, 6).must_equal [3.0, 4.0, 5.0, 6.0]
|
65
|
+
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "deletes multiple items by key using a Range and returns them as an array" do
|
69
|
+
@store.delete_keys(3..6).must_equal [3.0, 4.0, 5.0, 6.0]
|
70
|
+
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "can be converted to and from binary" do
|
74
|
+
restored = MemStore::ObjectStore.from_binary(@store.to_binary)
|
75
|
+
restored.must_be_instance_of MemStore::ObjectStore
|
76
|
+
restored.items.must_equal @store.items
|
77
|
+
restored.instance_variable_get(:@key).must_equal @store.instance_variable_get(:@key)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns nil when conversion from binary fails" do
|
81
|
+
MemStore::ObjectStore.from_binary(nil).must_equal nil
|
82
|
+
MemStore::ObjectStore.from_binary(Marshal.dump(Object.new)).must_equal nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "can be serialized to and deserialized from a binary file" do
|
86
|
+
tmp = Tempfile.new("memstore")
|
87
|
+
@store.to_file(tmp)
|
88
|
+
restored = MemStore::ObjectStore.from_file(tmp)
|
89
|
+
restored.must_be_instance_of MemStore::ObjectStore
|
90
|
+
restored.items.must_equal @store.items
|
91
|
+
restored.instance_variable_get(:@key).must_equal @store.instance_variable_get(:@key)
|
92
|
+
tmp.unlink
|
93
|
+
end
|
94
|
+
|
95
|
+
it "returns nil when deserialization from binary file fails" do
|
96
|
+
MemStore::ObjectStore.from_file("does_not_exist").must_equal nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "supports concurrent access to a single binary file" do
|
100
|
+
tmp = Tempfile.new("memstore")
|
101
|
+
fork do
|
102
|
+
MemStore.with_file(tmp, :to_i) { |store| store.insert(1.0, 3.0) }
|
103
|
+
end
|
104
|
+
fork do
|
105
|
+
MemStore.with_file(tmp, :to_i) { |store| store.insert(2.0, 4.0) }
|
106
|
+
end
|
107
|
+
sleep 0.1
|
108
|
+
restored = MemStore.from_file(tmp)
|
109
|
+
restored.items.must_equal({1=>1.0, 2=>2.0, 3=>3.0, 4=>4.0})
|
110
|
+
tmp.unlink
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
class Dummy
|
5
|
+
|
6
|
+
attr_accessor :id, :name, :child
|
7
|
+
|
8
|
+
def initialize(id, name=nil, child=nil)
|
9
|
+
@id, @name, @child = id, name, child
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(obj)
|
13
|
+
obj.is_a?(Dummy) &&
|
14
|
+
obj.id == @id &&
|
15
|
+
obj.name == @name &&
|
16
|
+
obj.child == @child
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe MemStore::ObjectStore do
|
22
|
+
|
23
|
+
before do
|
24
|
+
@store = MemStore::ObjectStore.new
|
25
|
+
strings = %w(foo moo boo faa maa baa foa moa boa lao)
|
26
|
+
classes = [String, Array]
|
27
|
+
10.times do |i|
|
28
|
+
@store << Dummy.new(i, strings[i], classes[i%2].new)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "finds all items fulfilling all conditions" do
|
33
|
+
matches = @store.find_all(id: 3..7, child: String)
|
34
|
+
matches.collect{ |m| m.id }.must_equal [4, 6]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "finds all items fulfilling at least one condition" do
|
38
|
+
matches = @store.find_any(id: 3..7, child: String)
|
39
|
+
matches.collect{ |m| m.id }.must_equal [0, 2, 3, 4, 5, 6, 7, 8]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "finds all items fulfilling exactly one condition" do
|
43
|
+
matches = @store.find_one(name: /o/, child: String)
|
44
|
+
matches.collect{ |m| m.id }.must_equal [1, 4, 7, 9]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "finds all items violating at least one condition" do
|
48
|
+
matches = @store.find_not_all(name: /o/, child: String)
|
49
|
+
matches.collect{ |m| m.id }.must_equal [1, 3, 4, 5, 7, 9]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "finds all items violating all conditions" do
|
53
|
+
matches = @store.find_none(name: /o/, child: String)
|
54
|
+
matches.collect{ |m| m.id }.must_equal [3, 5]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "finds the first item fulfilling all conditions" do
|
58
|
+
match = @store.first_all(id: 3..7, child: String)
|
59
|
+
match.id.must_equal 4
|
60
|
+
end
|
61
|
+
|
62
|
+
it "finds the first item fulfilling at least one condition" do
|
63
|
+
match = @store.first_any(id: 3..7, child: String)
|
64
|
+
match.id.must_equal 0
|
65
|
+
end
|
66
|
+
|
67
|
+
it "finds the first item fulfilling exactly one condition" do
|
68
|
+
match = @store.first_one(name: /o/, child: String)
|
69
|
+
match.id.must_equal 1
|
70
|
+
end
|
71
|
+
|
72
|
+
it "finds the first item violating at least one condition" do
|
73
|
+
match = @store.first_not_all(name: /o/, child: String)
|
74
|
+
match.id.must_equal 1
|
75
|
+
end
|
76
|
+
|
77
|
+
it "finds the first item violating all conditions" do
|
78
|
+
match = @store.first_none(name: /o/, child: String)
|
79
|
+
match.id.must_equal 3
|
80
|
+
end
|
81
|
+
|
82
|
+
it "counts all items fulfilling all conditions" do
|
83
|
+
count = @store.count_all(id: 3..7, child: String)
|
84
|
+
count.must_equal [4, 6].length
|
85
|
+
end
|
86
|
+
|
87
|
+
it "counts all items fulfilling at least one condition" do
|
88
|
+
count = @store.count_any(id: 3..7, child: String)
|
89
|
+
count.must_equal [0, 2, 3, 4, 5, 6, 7, 8].length
|
90
|
+
end
|
91
|
+
|
92
|
+
it "counts all items fulfilling exactly one condition" do
|
93
|
+
count = @store.count_one(name: /o/, child: String)
|
94
|
+
count.must_equal [1, 4, 7, 9].length
|
95
|
+
end
|
96
|
+
|
97
|
+
it "counts all items violating at least one condition" do
|
98
|
+
count = @store.count_not_all(name: /o/, child: String)
|
99
|
+
count.must_equal [1, 3, 4, 5, 7, 9].length
|
100
|
+
end
|
101
|
+
|
102
|
+
it "counts all items violating all conditions" do
|
103
|
+
count = @store.count_none(name: /o/, child: String)
|
104
|
+
count.must_equal [3, 5].length
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -28,4 +28,23 @@ describe MemStore::HashStore do
|
|
28
28
|
tmp.unlink
|
29
29
|
end
|
30
30
|
|
31
|
+
it "returns nil when conversion from YAML fails" do
|
32
|
+
MemStore::HashStore.from_yaml(nil).must_equal nil
|
33
|
+
MemStore::HashStore.from_yaml(Marshal.dump(Object.new)).must_equal nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "supports concurrent access to a single YAML file" do
|
37
|
+
tmp = Tempfile.new("memstore")
|
38
|
+
fork do
|
39
|
+
MemStore::HashStore.with_yaml_file(tmp, :id) { |store| store.insert({id: 1}, {id: 3}) }
|
40
|
+
end
|
41
|
+
fork do
|
42
|
+
MemStore::HashStore.with_yaml_file(tmp, :id) { |store| store.insert({id: 2}, {id: 4}) }
|
43
|
+
end
|
44
|
+
sleep 0.1
|
45
|
+
restored = MemStore::HashStore.from_yaml_file(tmp)
|
46
|
+
restored.items.must_equal({1=>{id: 1}, 2=>{id: 2}, 3=>{id: 3}, 4=>{id: 4}})
|
47
|
+
tmp.unlink
|
48
|
+
end
|
49
|
+
|
31
50
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -25,15 +25,20 @@ files:
|
|
25
25
|
- README.md
|
26
26
|
- Rakefile
|
27
27
|
- lib/memstore.rb
|
28
|
+
- lib/memstore/hashstore.rb
|
28
29
|
- lib/memstore/json.rb
|
29
30
|
- lib/memstore/msgpack.rb
|
31
|
+
- lib/memstore/objectstore.rb
|
32
|
+
- lib/memstore/queries.rb
|
30
33
|
- lib/memstore/version.rb
|
31
34
|
- lib/memstore/yaml.rb
|
32
35
|
- memstore.gemspec
|
33
|
-
- spec/
|
34
|
-
- spec/
|
35
|
-
- spec/
|
36
|
-
- spec/
|
36
|
+
- spec/hashstore_spec.rb
|
37
|
+
- spec/json_spec.rb
|
38
|
+
- spec/msgpack_spec.rb
|
39
|
+
- spec/objectstore_spec.rb
|
40
|
+
- spec/queries_spec.rb
|
41
|
+
- spec/yaml_spec.rb
|
37
42
|
homepage: https://github.com/sklppr/memstore
|
38
43
|
licenses: []
|
39
44
|
post_install_message:
|
@@ -59,7 +64,9 @@ signing_key:
|
|
59
64
|
specification_version: 3
|
60
65
|
summary: A simple, in-memory data store.
|
61
66
|
test_files:
|
62
|
-
- spec/
|
63
|
-
- spec/
|
64
|
-
- spec/
|
65
|
-
- spec/
|
67
|
+
- spec/hashstore_spec.rb
|
68
|
+
- spec/json_spec.rb
|
69
|
+
- spec/msgpack_spec.rb
|
70
|
+
- spec/objectstore_spec.rb
|
71
|
+
- spec/queries_spec.rb
|
72
|
+
- spec/yaml_spec.rb
|
data/spec/memstore_spec.rb
DELETED
@@ -1,268 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require "minitest/autorun"
|
4
|
-
require "tempfile"
|
5
|
-
require "memstore"
|
6
|
-
|
7
|
-
class Dummy
|
8
|
-
|
9
|
-
attr_accessor :id, :name, :child
|
10
|
-
|
11
|
-
def initialize(id, name=nil, child=nil)
|
12
|
-
@id, @name, @child = id, name, child
|
13
|
-
end
|
14
|
-
|
15
|
-
def ==(obj)
|
16
|
-
obj.is_a?(Dummy) &&
|
17
|
-
obj.id == @id &&
|
18
|
-
obj.name == @name &&
|
19
|
-
obj.child == @child
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
describe MemStore::ObjectStore do
|
25
|
-
|
26
|
-
it "can be instantiated with items" do
|
27
|
-
h = { a: 1, b: 2, c: 3 }
|
28
|
-
store = MemStore::ObjectStore.new(nil, h)
|
29
|
-
store.items.must_equal h
|
30
|
-
end
|
31
|
-
|
32
|
-
it "is the default when instantiating MemStore" do
|
33
|
-
MemStore.new.must_be_instance_of MemStore::ObjectStore
|
34
|
-
end
|
35
|
-
|
36
|
-
it "indexes items by Object#hash by default" do
|
37
|
-
o = Object.new
|
38
|
-
store = MemStore::ObjectStore.new.insert(o)
|
39
|
-
store.items[o.hash].must_equal o
|
40
|
-
end
|
41
|
-
|
42
|
-
it "indexes items using a custom key" do
|
43
|
-
o = Dummy.new("custom key")
|
44
|
-
store = MemStore::ObjectStore.new(:id).insert(o)
|
45
|
-
store.items[o.id].must_equal o
|
46
|
-
end
|
47
|
-
|
48
|
-
it "can be converted to and from binary" do
|
49
|
-
o = Dummy.new("custom key")
|
50
|
-
store = MemStore::ObjectStore.new(:id).insert(o)
|
51
|
-
restored = MemStore::ObjectStore.from_binary(store.to_binary)
|
52
|
-
restored.must_be_instance_of MemStore::ObjectStore
|
53
|
-
restored.items.must_equal store.items
|
54
|
-
restored.instance_variable_get(:@key).must_equal :id
|
55
|
-
end
|
56
|
-
|
57
|
-
it "returns nil when conversion from binary fails" do
|
58
|
-
MemStore::ObjectStore.from_binary(nil).must_equal nil
|
59
|
-
end
|
60
|
-
|
61
|
-
it "can be serialized to and deserialized from a binary file" do
|
62
|
-
tmp = Tempfile.new("memstore")
|
63
|
-
o = Dummy.new("custom key")
|
64
|
-
store = MemStore::ObjectStore.new(:id).insert(o)
|
65
|
-
store.to_file(tmp)
|
66
|
-
restored = MemStore::ObjectStore.from_file(tmp)
|
67
|
-
restored.must_be_instance_of MemStore::ObjectStore
|
68
|
-
restored.items.must_equal store.items
|
69
|
-
restored.instance_variable_get(:@key).must_equal :id
|
70
|
-
tmp.unlink
|
71
|
-
end
|
72
|
-
|
73
|
-
it "returns nil when deserialization from binary file fails" do
|
74
|
-
MemStore::ObjectStore.from_file("does_not_exist").must_equal nil
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
describe MemStore::HashStore do
|
80
|
-
|
81
|
-
it "can be instantiated with items" do
|
82
|
-
h = { a: 1, b: 2, c: 3 }
|
83
|
-
store = MemStore::HashStore.new(nil, h)
|
84
|
-
store.items.must_equal h
|
85
|
-
end
|
86
|
-
|
87
|
-
it "indexes items by Object#hash by default" do
|
88
|
-
h = {}
|
89
|
-
store = MemStore::HashStore.new.insert(h)
|
90
|
-
store.items[h.hash].must_equal h
|
91
|
-
end
|
92
|
-
|
93
|
-
it "indexes items using a custom key" do
|
94
|
-
h = { id: "custom key" }
|
95
|
-
store = MemStore::HashStore.new(:id).insert(h)
|
96
|
-
store.items[h[:id]].must_equal h
|
97
|
-
end
|
98
|
-
|
99
|
-
it "can be converted to and from binary" do
|
100
|
-
store = MemStore::HashStore.new(:id).insert({ id: "custom key" })
|
101
|
-
restored = MemStore::HashStore.from_binary(store.to_binary)
|
102
|
-
restored.must_be_instance_of MemStore::HashStore
|
103
|
-
restored.items.must_equal store.items
|
104
|
-
restored.instance_variable_get(:@key).must_equal :id
|
105
|
-
end
|
106
|
-
|
107
|
-
it "can be serialized to and deserialized from a binary file" do
|
108
|
-
tmp = Tempfile.new("memstore")
|
109
|
-
store = MemStore::HashStore.new(:id).insert({ id: "custom key" })
|
110
|
-
store.to_file(tmp)
|
111
|
-
restored = MemStore::HashStore.from_file(tmp)
|
112
|
-
restored.must_be_instance_of MemStore::HashStore
|
113
|
-
restored.items.must_equal store.items
|
114
|
-
restored.instance_variable_get(:@key).must_equal :id
|
115
|
-
tmp.unlink
|
116
|
-
end
|
117
|
-
|
118
|
-
it "can be converted to and from a hash" do
|
119
|
-
store = MemStore::HashStore.new(:id)
|
120
|
-
10.times { |i| store << { id: i } }
|
121
|
-
restored = MemStore::HashStore.from_hash(store.to_hash)
|
122
|
-
restored.items.must_equal store.items
|
123
|
-
restored.instance_variable_get(:@key).must_equal :id
|
124
|
-
end
|
125
|
-
|
126
|
-
it "returns nil when conversion from hash fails" do
|
127
|
-
MemStore::HashStore.from_hash(nil).must_equal nil
|
128
|
-
MemStore::HashStore.from_hash({}).must_equal nil
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
describe MemStore do
|
134
|
-
|
135
|
-
before do
|
136
|
-
@store = MemStore.new(:to_i)
|
137
|
-
# Use float as objects and integer as key
|
138
|
-
10.times { |i| @store << i.to_f }
|
139
|
-
end
|
140
|
-
|
141
|
-
it "returns the overall number of items" do
|
142
|
-
@store.length.must_equal 10
|
143
|
-
end
|
144
|
-
|
145
|
-
it "returns a single item by itself" do
|
146
|
-
@store[3].must_equal 3.0
|
147
|
-
end
|
148
|
-
|
149
|
-
it "returns multiple items as an array" do
|
150
|
-
@store[3, 4, 5, 6].must_equal [3.0, 4.0, 5.0, 6.0]
|
151
|
-
end
|
152
|
-
|
153
|
-
it "returns multiple items using a Range as an array" do
|
154
|
-
@store[0..9].must_equal @store.all
|
155
|
-
end
|
156
|
-
|
157
|
-
it "deletes a single item by reference and returns it by itself" do
|
158
|
-
@store.delete_item(3.0).must_equal 3.0
|
159
|
-
end
|
160
|
-
|
161
|
-
it "deletes multiple items by reference and returns them" do
|
162
|
-
@store.delete_items(3.0, 4.0, 5.0, 6.0).must_equal [3.0, 4.0, 5.0, 6.0]
|
163
|
-
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
164
|
-
end
|
165
|
-
|
166
|
-
it "deletes a single item by key and returns it by itself" do
|
167
|
-
@store.delete_key(3).must_equal 3.0
|
168
|
-
end
|
169
|
-
|
170
|
-
it "deletes multiple items by key and returns them as an array" do
|
171
|
-
@store.delete_keys(3, 4, 5, 6).must_equal [3.0, 4.0, 5.0, 6.0]
|
172
|
-
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
173
|
-
end
|
174
|
-
|
175
|
-
it "deletes multiple items by key using a Range and returns them as an array" do
|
176
|
-
@store.delete_keys(3..6).must_equal [3.0, 4.0, 5.0, 6.0]
|
177
|
-
@store.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
|
178
|
-
end
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
describe MemStore do
|
183
|
-
|
184
|
-
before do
|
185
|
-
@store = MemStore.new
|
186
|
-
strings = %w(foo moo boo faa maa baa foa moa boa lao)
|
187
|
-
classes = [String, Array]
|
188
|
-
10.times do |i|
|
189
|
-
@store << Dummy.new(i, strings[i], classes[i%2].new)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
it "finds all items fulfilling all conditions" do
|
194
|
-
matches = @store.find_all(id: 3..7, child: String)
|
195
|
-
matches.collect{ |m| m.id }.must_equal [4, 6]
|
196
|
-
end
|
197
|
-
|
198
|
-
it "finds all items fulfilling at least one condition" do
|
199
|
-
matches = @store.find_any(id: 3..7, child: String)
|
200
|
-
matches.collect{ |m| m.id }.must_equal [0, 2, 3, 4, 5, 6, 7, 8]
|
201
|
-
end
|
202
|
-
|
203
|
-
it "finds all items fulfilling exactly one condition" do
|
204
|
-
matches = @store.find_one(name: /o/, child: String)
|
205
|
-
matches.collect{ |m| m.id }.must_equal [1, 4, 7, 9]
|
206
|
-
end
|
207
|
-
|
208
|
-
it "finds all items violating at least one condition" do
|
209
|
-
matches = @store.find_not_all(name: /o/, child: String)
|
210
|
-
matches.collect{ |m| m.id }.must_equal [1, 3, 4, 5, 7, 9]
|
211
|
-
end
|
212
|
-
|
213
|
-
it "finds all items violating all conditions" do
|
214
|
-
matches = @store.find_none(name: /o/, child: String)
|
215
|
-
matches.collect{ |m| m.id }.must_equal [3, 5]
|
216
|
-
end
|
217
|
-
|
218
|
-
it "finds the first item fulfilling all conditions" do
|
219
|
-
match = @store.first_all(id: 3..7, child: String)
|
220
|
-
match.id.must_equal 4
|
221
|
-
end
|
222
|
-
|
223
|
-
it "finds the first item fulfilling at least one condition" do
|
224
|
-
match = @store.first_any(id: 3..7, child: String)
|
225
|
-
match.id.must_equal 0
|
226
|
-
end
|
227
|
-
|
228
|
-
it "finds the first item fulfilling exactly one condition" do
|
229
|
-
match = @store.first_one(name: /o/, child: String)
|
230
|
-
match.id.must_equal 1
|
231
|
-
end
|
232
|
-
|
233
|
-
it "finds the first item violating at least one condition" do
|
234
|
-
match = @store.first_not_all(name: /o/, child: String)
|
235
|
-
match.id.must_equal 1
|
236
|
-
end
|
237
|
-
|
238
|
-
it "finds the first item violating all conditions" do
|
239
|
-
match = @store.first_none(name: /o/, child: String)
|
240
|
-
match.id.must_equal 3
|
241
|
-
end
|
242
|
-
|
243
|
-
it "counts all items fulfilling all conditions" do
|
244
|
-
count = @store.count_all(id: 3..7, child: String)
|
245
|
-
count.must_equal [4, 6].length
|
246
|
-
end
|
247
|
-
|
248
|
-
it "counts all items fulfilling at least one condition" do
|
249
|
-
count = @store.count_any(id: 3..7, child: String)
|
250
|
-
count.must_equal [0, 2, 3, 4, 5, 6, 7, 8].length
|
251
|
-
end
|
252
|
-
|
253
|
-
it "counts all items fulfilling exactly one condition" do
|
254
|
-
count = @store.count_one(name: /o/, child: String)
|
255
|
-
count.must_equal [1, 4, 7, 9].length
|
256
|
-
end
|
257
|
-
|
258
|
-
it "counts all items violating at least one condition" do
|
259
|
-
count = @store.count_not_all(name: /o/, child: String)
|
260
|
-
count.must_equal [1, 3, 4, 5, 7, 9].length
|
261
|
-
end
|
262
|
-
|
263
|
-
it "counts all items violating all conditions" do
|
264
|
-
count = @store.count_none(name: /o/, child: String)
|
265
|
-
count.must_equal [3, 5].length
|
266
|
-
end
|
267
|
-
|
268
|
-
end
|