memstore 1.1.0 → 1.2.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.
- 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
|