memstore 1.0.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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sebastian Klepper
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,391 @@
1
+ # MemStore
2
+
3
+ *A simple in-memory data store.*
4
+
5
+ MemStore is a simple in-memory data store that supports adding, retrieving and deleting items as well as complex search queries and easy serialization.
6
+
7
+ It’s not in any way supposed to be a “real” database. However, it can replace a database in small applications or prototypes.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem "memstore"
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install memstore
22
+
23
+ ## Usage
24
+
25
+ - [Basics](#basics)
26
+ - [Objects vs. Hashes](#objects-vs-hashes)
27
+ - [Adding Items](#adding-items)
28
+ - [Retrieving Items](#retrieving-items)
29
+ - [Deleting Items](#deleting-items)
30
+ - [Search Queries](#search-queries)
31
+ - [Serialization](#serialization)
32
+ - [Binary](#binary)
33
+ - [Hash](#hash)
34
+ - [YAML](#yaml)
35
+ - [JSON](#json)
36
+ - [MessagePack](#messagepack)
37
+
38
+ ### Basics
39
+
40
+ Creating a data store is utterly simple:
41
+
42
+ ```ruby
43
+ mb = MemStore.new
44
+ ```
45
+
46
+ By default, objects are indexed using `Object#hash`.
47
+
48
+ If a different property should be used, it can be specified like this:
49
+
50
+ ```ruby
51
+ mb = MemStore.new(:id)
52
+ ```
53
+
54
+ The property needs to be truly unique for all objects since it’s used as a hash key internally.
55
+
56
+ An items collection can also be provided on creation:
57
+
58
+ ```ruby
59
+ mb = MemStore.new(nil, { ... }) # to use Object.hash as key
60
+ mb = MemStore.new(:id, { ... }) # to use custom key
61
+ ```
62
+
63
+ The collection must be a hash that correctly maps the used key to each item.
64
+
65
+ ### Objects vs. Hashes
66
+
67
+ MemStore comes in two flavors: `ObjectStore` and `HashStore`.
68
+
69
+ They’re basically the same, but `ObjectStore` accesses items through `item.attribute` while `HashStore` accesses items through `item[attribute]`.
70
+
71
+ `ObjectStore` is the default variant:
72
+
73
+ ```ruby
74
+ mb = MemStore.new
75
+ # is equal to
76
+ mb = MemStore::ObjectStore.new
77
+ ```
78
+
79
+ `HashStore` needs to be created explicitly:
80
+
81
+ ```ruby
82
+ mb = MemStore::HashStore.new
83
+ ```
84
+
85
+ If no key attribute is specified, `HashStore` will also use `Object#hash`.
86
+
87
+ ### Adding Items
88
+
89
+ `items` provides direct access to the internal items hash.
90
+
91
+ ```ruby
92
+ mb.items
93
+ # => {}
94
+ mb.items = { 1 => a, 2 => b, 3 => c }
95
+ # => { 1 => a, 2 => b, 3 => c }
96
+ ```
97
+
98
+ `insert` adds one or multiple items and returns the data store itself:
99
+
100
+ ```ruby
101
+ mb.insert(a, b, c)
102
+ # => mb
103
+ ```
104
+
105
+ Since it returns the data store, items can be added right after instantiation:
106
+
107
+ ```ruby
108
+ mb = MemStore.new.insert(a, b, c)
109
+ # => mb
110
+ ```
111
+
112
+ MemStore also supports the shovel operator `<<` for adding items.
113
+ Only one item can be added at a time but it’s chainable:
114
+
115
+ ```ruby
116
+ mb << a << b << c
117
+ # => mb
118
+ ```
119
+
120
+ ### Retrieving Items
121
+
122
+ `size` returns the current number of items:
123
+
124
+ ```ruby
125
+ mb.size
126
+ # => 3
127
+ ```
128
+
129
+ The bracket operator `[]` is used to look up items by their key.
130
+ If a single key is given, a single item will be returned.
131
+ If multiple keys are given, an array of items will be returned with `nil` when there is no item for a key.
132
+
133
+ ```ruby
134
+ mb[1]
135
+ # => a
136
+ mb[1, 2, 3]
137
+ # => [a, b, c]
138
+ ```
139
+
140
+ Ranges are also supported and can even be combined with single keys:
141
+
142
+ ```ruby
143
+ mb[1..3]
144
+ # => [a, b, c]
145
+ mb[1..3, 6]
146
+ # => [a, b, c, f]
147
+ ```
148
+
149
+ ### Deleting Items
150
+
151
+ `delete_items` (or `delete_item`) deletes items by reference and returns them.
152
+ This is considered the default use case and therefore also available as `delete`.
153
+
154
+ If one item is given, it is deleted and returned.
155
+ If multiple items are given, they are deleted and returned as an array.
156
+
157
+ ```ruby
158
+ mb.delete_item(a)
159
+ # => a
160
+ mb.delete_items(b, c, d)
161
+ # => [b, c, d]
162
+ mb.delete(e, f, g)
163
+ # => [e, f, g]
164
+ ```
165
+
166
+ This is considered the default use case and therefore also available as `delete`.
167
+
168
+ `delete_keys` (or `delete_key`) deletes items by key and returns them.
169
+ Again, one or multiple items can be deleted at a time and even ranges are handled.
170
+
171
+ ```ruby
172
+ mb.delete_key(1)
173
+ # => a
174
+ mb.delete_keys(2, 3, 4)
175
+ # => [b, c, d]
176
+ mb.delete_keys(5..7, 9)
177
+ # => [e, f, g, i]
178
+ ```
179
+
180
+ ### Search Queries
181
+
182
+ The following methods are available to query the data store:
183
+
184
+ - `find_all` (`find`)
185
+ - `find_any`
186
+ - `find_one`
187
+ - `find_not_all`
188
+ - `find_none`
189
+ - `first_all` (`first`)
190
+ - `first_any`
191
+ - `first_one`
192
+ - `first_not_all`
193
+ - `first_none`
194
+
195
+ The first part indicates what is returned:
196
+
197
+ - `find_*` returns all matches.
198
+ - `first_*` returns the first match.
199
+
200
+ The second part indicates how conditions are evaluated:
201
+
202
+ - `*_all` matches items *fulfilling all* conditions.
203
+ - `*_any` matches items *fulfilling at least one* condition.
204
+ - `*_one` matches items *fulfilling exactly one* condition.
205
+ - `*_not_all` matches items *violating at least one* condition.
206
+ - `*_none` matches items *violating all* conditions.
207
+
208
+ In other words:
209
+
210
+ - `all` means `condition && condition && ...`
211
+ - `any` means `condition || condition || ...`
212
+ - `one` means `condition ^ condition ^ ...` (XOR)
213
+ - `not all` means `!(condition && condition && ...)` or `!condition || !condition || ...`
214
+ - `none` means `!(condition || condition || ...)` or `!condition && !condition && ...`
215
+
216
+ For convenience, `find` is aliased to `find_all` and `first` to `first_all`.
217
+
218
+ All variants take a `conditions` hash and an optional block.
219
+
220
+ The hash maps attributes names to conditions that should be tested.
221
+ Conditions are evaluated using the `===` operator and can be virtually anything:
222
+
223
+ ```ruby
224
+ mb.find(name: "Fred", age: 25)
225
+ mb.find(name: /red/i, age: 10..30)
226
+ mb.find(child: MyClass)
227
+ mb.find(child: -> child { child.valid? })
228
+ ```
229
+
230
+ Additional types can be used in conditions by supporting the `===` operator. For example:
231
+
232
+ ```ruby
233
+ class Array
234
+ def ===(obj)
235
+ self.include?(obj)
236
+ end
237
+ end
238
+
239
+ mb.find age: [23, 25, 27]
240
+ ```
241
+
242
+ The block is invoked with every item and can do more complex tests.
243
+ Its return value is interpreted as a boolean value:
244
+
245
+ ```ruby
246
+ mb.find { |item| item.age - item.child.age > 20 }
247
+ ```
248
+
249
+ In addition to the evaluation logic, the arrays returned by all variants of `find` can be merged:
250
+
251
+ ```ruby
252
+ mb.find(...) | mb.find(...) | mb.find(...)
253
+ ```
254
+
255
+ Note that the pipe operator `|` already eliminates duplicates:
256
+
257
+ ```ruby
258
+ [a, b, c] | [c, d, e]
259
+ # => [a, b, c, d, e]
260
+ # which is equal to
261
+ ([a, b, c] + [c, d, e]).uniq
262
+ ```
263
+
264
+ ### Serialization
265
+
266
+ #### Binary
267
+
268
+ The data store can easily be serialized and restored in binary format:
269
+
270
+ ```ruby
271
+ mb.to_file("datastore.bin")
272
+ # => number of bytes written
273
+ MemStore.from_file("datastore.bin")
274
+ # => instance of ObjectStore or HashStore
275
+ ```
276
+
277
+ MemStore will automatically restore the correct class (`ObjectStore`/`HashStore`), key and items.
278
+
279
+ #### Hash
280
+
281
+ `HashStore` can be converted to and from a hash:
282
+
283
+ ```ruby
284
+ h = mb.to_hash
285
+ # => { key: ..., items: { ... } }
286
+ MemStore::HashStore.from_hash(h)
287
+ # => instance of HashStore
288
+ ```
289
+
290
+ #### YAML
291
+
292
+ `memstore/yaml` enables serialization of `HashStore` to and from [YAML](http://yaml.org/):
293
+
294
+ ```ruby
295
+ require "memstore/yaml" # requires "yaml"
296
+
297
+ mb.to_yaml
298
+ # => YAML string
299
+ mb.to_yaml_file(file)
300
+ # => number of bytes written
301
+ MemStore::HashStore.from_yaml(yaml)
302
+ # => instance of HashStore
303
+ MemStore::HashStore.from_yaml_file(file)
304
+ # => instance of HashStore
305
+ ```
306
+
307
+ De/serialization is seamless since YAML can handle symbols and non-string keys (i.e. Psych converts them correctly).
308
+
309
+ #### JSON
310
+
311
+ `memstore/json` enables serialization of `HashStore` to and from [JSON](http://www.json.org/):
312
+
313
+ ```ruby
314
+ require "memstore/json" # requires "json"
315
+
316
+ mb.to_json
317
+ # => JSON string
318
+ mb.to_json_file(file)
319
+ # => number of bytes written
320
+ MemStore::HashStore.from_json(json)
321
+ # => instance of HashStore
322
+ MemStore::HashStore.from_json_file(file)
323
+ # => instance of HashStore
324
+ ```
325
+
326
+ **Important:** Symbols will be converted to strings and JSON only allows string keys.
327
+
328
+ ```ruby
329
+ mb = MemStore::HashStore.new(:id)
330
+ mb << { id: 1 }
331
+ mb.to_hash
332
+ # => { :key => :id, :items => { 1 => { :id => 1 } } }
333
+ mb = MemStore::HashStore.from_json(mb.to_json)
334
+ mb.to_hash
335
+ # => { :key => "id", :items => { "1" => { "id" => 1 } } }
336
+ ```
337
+
338
+ The following style ensures consistent access before and after serialization:
339
+
340
+ ```ruby
341
+ mb = MemStore::HashStore.new("id")
342
+ mb << { "id" => "1" }
343
+ mb["1"]
344
+ # => { "id" => "1" }
345
+ ```
346
+
347
+ #### MessagePack
348
+
349
+ `memstore/msgpack` enables serialization of `HashStore` to and from [MessagePack](http://msgpack.org/):
350
+
351
+ ```ruby
352
+ require "memstore/msgpack" # requires "msgpack"
353
+
354
+ mb.to_msgpack
355
+ # => MessagePack binary format
356
+ mb.to_msgpack_file(file)
357
+ # => number of bytes written
358
+ MemStore::HashStore.from_msgpack(msgpack)
359
+ # => instance of HashStore
360
+ MemStore::HashStore.from_msgpack_file(file)
361
+ # => instance of HashStore
362
+ ```
363
+
364
+ **Important:** Symbols will be converted to strings but non-string keys are allowed.
365
+
366
+ ```ruby
367
+ mb = MemStore::HashStore.new(:id)
368
+ mb << { id: 1 }
369
+ mb.to_hash
370
+ # => { :key => :id, :items => { 1 => { :id => 1 } } }
371
+ mb = MemStore::HashStore.from_msgpack(mb.to_msgpack)
372
+ mb.to_hash
373
+ # => { :key => "id", :items => { 1 => { "id" => 1 } } }
374
+ ```
375
+
376
+ The following style ensures consistent access before and after serialization:
377
+
378
+ ```ruby
379
+ mb = MemStore::HashStore.new("id")
380
+ mb << { "id" => 1 }
381
+ mb[1]
382
+ # => { "id" => 1 }
383
+ ```
384
+
385
+ ## Contributing
386
+
387
+ 1. Fork it
388
+ 2. Create your feature branch: `git checkout -b my-new-feature`
389
+ 3. Commit your changes: `git commit -am 'Add some feature'`
390
+ 4. Push to the branch: `git push origin my-new-feature`
391
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new :default do |t|
5
+ t.pattern = "spec/**/*_spec.rb"
6
+ end
data/lib/memstore.rb ADDED
@@ -0,0 +1,164 @@
1
+ require "memstore/version"
2
+
3
+ module MemStore
4
+
5
+ def self.new(key=nil)
6
+ ObjectStore.new(key)
7
+ end
8
+
9
+ def self.from_file(file)
10
+ Marshal.load IO.read(file)
11
+ end
12
+
13
+ class ObjectStore
14
+
15
+ def initialize(key=nil, items={})
16
+ @key = key || :hash
17
+ @items = items
18
+ end
19
+
20
+ attr_accessor :items
21
+
22
+ def insert(*items)
23
+ items.each { |item| @items[key(item)] = item }
24
+ self
25
+ end
26
+ alias_method :<<, :insert
27
+
28
+ def size
29
+ @items.length
30
+ end
31
+
32
+ def [](*keys)
33
+ return @items[keys.first] if keys.length == 1 && !keys.first.is_a?(Range)
34
+ keys.inject [] do |items, key|
35
+ if key.is_a? Range then key.inject(items) { |i, k| i << @items[k] }
36
+ else items << @items[key] end
37
+ end
38
+ end
39
+
40
+ def all
41
+ @items.values
42
+ end
43
+
44
+ def delete_items(*items)
45
+ return @items.delete(key(items.first)) if items.length == 1
46
+ items.collect { |item| @items.delete(key(item)) }
47
+ end
48
+ alias_method :delete_item, :delete_items
49
+ alias_method :delete, :delete_items
50
+
51
+ def delete_keys(*keys)
52
+ return @items.delete(keys.first) if keys.length == 1 && !keys.first.is_a?(Range)
53
+ keys.inject [] do |items, key|
54
+ if key.is_a? Range then key.inject(items) { |i, k| i << @items.delete(k) }
55
+ else items << @items.delete(key) end
56
+ end
57
+ end
58
+ alias_method :delete_key, :delete_keys
59
+
60
+ def find_all(conditions={}, &block)
61
+ all.select { |item| instance_exec(item, conditions, block, &FIND_ALL) }
62
+ end
63
+ alias_method :find, :find_all
64
+
65
+ def find_any(conditions={}, &block)
66
+ all.select { |item| instance_exec(item, conditions, block, &FIND_ANY) }
67
+ end
68
+
69
+ def find_one(conditions={}, &block)
70
+ all.select { |item| instance_exec(item, conditions, block, &FIND_ONE) }
71
+ end
72
+
73
+ def find_not_all(conditions={}, &block)
74
+ all.reject { |item| instance_exec(item, conditions, block, &FIND_ALL) }
75
+ end
76
+
77
+ def find_none(conditions={}, &block)
78
+ all.select { |item| instance_exec(item, conditions, block, &FIND_NONE) }
79
+ end
80
+
81
+ def first_all(conditions={}, &block)
82
+ all.detect { |item| instance_exec(item, conditions, block, &FIND_ALL) }
83
+ end
84
+ alias_method :first, :first_all
85
+
86
+ def first_any(conditions={}, &block)
87
+ all.detect { |item| instance_exec(item, conditions, block, &FIND_ANY) }
88
+ end
89
+
90
+ def first_one(conditions={}, &block)
91
+ all.detect { |item| instance_exec(item, conditions, block, &FIND_ONE) }
92
+ end
93
+
94
+ def first_not_all(conditions={}, &block)
95
+ all.detect { |item| !instance_exec(item, conditions, block, &FIND_ALL) }
96
+ end
97
+
98
+ def first_none(conditions={}, &block)
99
+ all.detect { |item| instance_exec(item, conditions, block, &FIND_NONE) }
100
+ end
101
+
102
+ def to_file(file)
103
+ IO.write file, Marshal.dump(self)
104
+ end
105
+
106
+ private
107
+
108
+ FIND_ALL = Proc.new do |item, conditions, block|
109
+ conditions.all? { |attribute, condition| condition === attr(item, attribute) } &&
110
+ if block then !!block.call(item) else true end
111
+ end
112
+
113
+ FIND_ANY = Proc.new do |item, conditions, block|
114
+ conditions.any? { |attribute, condition| condition === attr(item, attribute) } ||
115
+ if block then !!block.call(item) else false end
116
+ end
117
+
118
+ FIND_NONE = Proc.new do |item, conditions, block|
119
+ conditions.none? { |attribute, condition| condition === attr(item, attribute) } &&
120
+ if block then !!block.call(item) else true end
121
+ end
122
+
123
+ FIND_ONE = Proc.new do |item, conditions, block|
124
+ conditions.one? { |attribute, condition| condition === attr(item, attribute) } ||
125
+ if block then !!block.call(item) else false end
126
+ end
127
+
128
+ def key(item)
129
+ item.send(@key)
130
+ end
131
+
132
+ def attr(item, attribute)
133
+ item.send(attribute)
134
+ end
135
+
136
+ end
137
+
138
+ class HashStore < ObjectStore
139
+
140
+ def self.from_hash(hash)
141
+ self.new(hash[:key] || hash["key"], hash[:items] || hash["items"])
142
+ end
143
+
144
+ def initialize(key=nil, items={})
145
+ @key, @items = key, items
146
+ end
147
+
148
+ def to_hash
149
+ { key: @key, items: @items }
150
+ end
151
+
152
+ private
153
+
154
+ def key(item)
155
+ if @key.nil? then item.hash else item[@key] end
156
+ end
157
+
158
+ def attr(item, attribute)
159
+ item[attribute]
160
+ end
161
+
162
+ end
163
+
164
+ end
@@ -0,0 +1,25 @@
1
+ require "json"
2
+
3
+ module MemStore
4
+
5
+ class HashStore
6
+
7
+ def to_json
8
+ self.to_hash.to_json
9
+ end
10
+
11
+ def to_json_file(file)
12
+ IO.write file, self.to_json
13
+ end
14
+
15
+ def self.from_json(json)
16
+ self.from_hash JSON.parse(json)
17
+ end
18
+
19
+ def self.from_json_file(file)
20
+ self.from_json IO.read(file)
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,25 @@
1
+ require "msgpack"
2
+
3
+ module MemStore
4
+
5
+ class HashStore
6
+
7
+ def to_msgpack
8
+ self.to_hash.to_msgpack
9
+ end
10
+
11
+ def to_msgpack_file(file)
12
+ IO.write file, self.to_msgpack
13
+ end
14
+
15
+ def self.from_msgpack(msgpack)
16
+ self.from_hash MessagePack.unpack(msgpack)
17
+ end
18
+
19
+ def self.from_msgpack_file(file)
20
+ self.from_msgpack IO.read(file)
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,3 @@
1
+ module MemStore
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require "yaml"
2
+
3
+ module MemStore
4
+
5
+ class HashStore
6
+
7
+ def to_yaml
8
+ self.to_hash.to_yaml
9
+ end
10
+
11
+ def to_yaml_file(file)
12
+ IO.write file, self.to_yaml
13
+ end
14
+
15
+ def self.from_yaml(yaml)
16
+ self.from_hash YAML.load(yaml)
17
+ end
18
+
19
+ def self.from_yaml_file(file)
20
+ self.from_yaml IO.read(file)
21
+ end
22
+
23
+ end
24
+
25
+ end
data/memstore.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "memstore/version"
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "memstore"
8
+ gem.version = MemStore::VERSION
9
+ gem.summary = %q{A simple, in-memory data store.}
10
+ gem.description = %q{MemStore is a simple in-memory data store that supports adding, retrieving and deleting items as well as complex search queries and easy serialization.}
11
+ gem.authors = ["Sebastian Klepper"]
12
+ gem.email = ["sk@sebastianklepper.com"]
13
+ gem.homepage = "https://github.com/sklppr/memstore"
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require "minitest/autorun"
4
+ require "tempfile"
5
+ require "memstore"
6
+ require "memstore/json"
7
+
8
+ describe MemStore::HashStore do
9
+
10
+ before do
11
+ @key = "id"
12
+ @mb = MemStore::HashStore.new(@key)
13
+ 10.times { |i| @mb << { "id" => i.to_s } }
14
+ end
15
+
16
+ it "can be converted to and from JSON" do
17
+ restored = MemStore::HashStore.from_json(@mb.to_json)
18
+ restored.items.must_equal @mb.items
19
+ restored.instance_variable_get(:@key).must_equal @key
20
+ end
21
+
22
+ it "can be serialized to and deserialized from a JSON file" do
23
+ tmp = Tempfile.new("memstore_json")
24
+ @mb.to_json_file(tmp)
25
+ restored = MemStore::HashStore.from_json_file(tmp)
26
+ restored.items.must_equal @mb.items
27
+ restored.instance_variable_get(:@key).must_equal @key
28
+ tmp.unlink
29
+ end
30
+
31
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require "minitest/autorun"
4
+ require "tempfile"
5
+ require "memstore"
6
+ require "memstore/msgpack"
7
+
8
+ describe MemStore::HashStore do
9
+
10
+ before do
11
+ @key = "id"
12
+ @mb = MemStore::HashStore.new(@key)
13
+ 10.times { |i| @mb << { "id" => i } }
14
+ end
15
+
16
+ it "can be converted to and from MessagePack" do
17
+ restored = MemStore::HashStore.from_msgpack(@mb.to_msgpack)
18
+ restored.items.must_equal @mb.items
19
+ restored.instance_variable_get(:@key).must_equal @key
20
+ end
21
+
22
+ it "can be serialized to and deserialized from a MessagePack file" do
23
+ tmp = Tempfile.new("memstore_json")
24
+ @mb.to_msgpack_file(tmp)
25
+ restored = MemStore::HashStore.from_msgpack_file(tmp)
26
+ restored.items.must_equal @mb.items
27
+ restored.instance_variable_get(:@key).must_equal @key
28
+ tmp.unlink
29
+ end
30
+
31
+ end
@@ -0,0 +1,192 @@
1
+ # encoding: utf-8
2
+
3
+ require "minitest/autorun"
4
+ require "tempfile"
5
+ require "memstore"
6
+
7
+ describe MemStore::ObjectStore do
8
+
9
+ it "can be instantiated with items" do
10
+ h = { a: 1, b: 2, c: 3 }
11
+ mb = MemStore::ObjectStore.new(nil, h)
12
+ mb.items.must_equal h
13
+ end
14
+
15
+ it "is the default when instantiating MemStore" do
16
+ MemStore.new.must_be_instance_of MemStore::ObjectStore
17
+ end
18
+
19
+ it "indexes items by Object#hash by default" do
20
+ o = Object.new
21
+ mb = MemStore::ObjectStore.new.insert(o)
22
+ mb.items[o.hash].must_equal o
23
+ end
24
+
25
+ it "indexes items using a custom key" do
26
+ o = Struct.new(:id).new(id: "custom key")
27
+ mb = MemStore::ObjectStore.new(:id).insert(o)
28
+ mb.items[o.id].must_equal o
29
+ end
30
+
31
+ it "can be serialized and deserialized" do
32
+ tmp = Tempfile.new("memstore")
33
+ MemStore::ObjectStore.new.to_file tmp
34
+ MemStore.from_file(tmp).must_be_instance_of MemStore::ObjectStore
35
+ tmp.unlink
36
+ end
37
+
38
+ end
39
+
40
+ describe MemStore::HashStore do
41
+
42
+ it "can be instantiated with items" do
43
+ h = { a: 1, b: 2, c: 3 }
44
+ mb = MemStore::HashStore.new(nil, h)
45
+ mb.items.must_equal h
46
+ end
47
+
48
+ it "indexes items by Object#hash by default" do
49
+ h = {}
50
+ mb = MemStore::HashStore.new.insert(h)
51
+ mb.items[h.hash].must_equal h
52
+ end
53
+
54
+ it "indexes items using a custom key" do
55
+ h = { id: "custom key" }
56
+ mb = MemStore::HashStore.new(:id).insert(h)
57
+ mb.items[h[:id]].must_equal h
58
+ end
59
+
60
+ it "can be serialized and deserialized" do
61
+ tmp = Tempfile.new("memstore")
62
+ MemStore::HashStore.new.to_file tmp
63
+ MemStore.from_file(tmp).must_be_instance_of MemStore::HashStore
64
+ tmp.unlink
65
+ end
66
+
67
+ it "can be converted to and from a hash" do
68
+ mb = MemStore::HashStore.new(:id)
69
+ 10.times { |i| mb << { id: i } }
70
+ restored = MemStore::HashStore.from_hash(mb.to_hash)
71
+ restored.items.must_equal mb.items
72
+ restored.instance_variable_get(:@key).must_equal :id
73
+ end
74
+
75
+ end
76
+
77
+ describe MemStore do
78
+
79
+ before do
80
+ @mb = MemStore.new(:to_i)
81
+ # Use float as objects and integer as key
82
+ 10.times { |i| @mb << i.to_f }
83
+ end
84
+
85
+ it "returns a single item by itself" do
86
+ @mb[3].must_equal 3.0
87
+ end
88
+
89
+ it "returns multiple items as an array" do
90
+ @mb[3, 4, 5, 6].must_equal [3.0, 4.0, 5.0, 6.0]
91
+ end
92
+
93
+ it "returns multiple items using a Range as an array" do
94
+ @mb[0..9].must_equal @mb.all
95
+ end
96
+
97
+ it "deletes a single item by reference and returns it by itself" do
98
+ @mb.delete_item(3.0).must_equal 3.0
99
+ end
100
+
101
+ it "deletes multiple items by reference and returns them" do
102
+ @mb.delete_items(3.0, 4.0, 5.0, 6.0).must_equal [3.0, 4.0, 5.0, 6.0]
103
+ @mb.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
104
+ end
105
+
106
+ it "deletes a single item by key and returns it by itself" do
107
+ @mb.delete_key(3).must_equal 3.0
108
+ end
109
+
110
+ it "deletes multiple items by key and returns them as an array" do
111
+ @mb.delete_keys(3, 4, 5, 6).must_equal [3.0, 4.0, 5.0, 6.0]
112
+ @mb.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
113
+ end
114
+
115
+ it "deletes multiple items by key using a Range and returns them as an array" do
116
+ @mb.delete_keys(3..6).must_equal [3.0, 4.0, 5.0, 6.0]
117
+ @mb.all.must_equal [0.0, 1.0, 2.0, 7.0, 8.0, 9.0]
118
+ end
119
+
120
+ it "can be serialized and deserialized" do
121
+ tmp = Tempfile.new("memstore")
122
+ @mb.to_file tmp
123
+ MemStore.from_file(tmp).items.must_equal @mb.items
124
+ tmp.unlink
125
+ end
126
+
127
+ end
128
+
129
+ Dummy = Struct.new(:id, :name, :child)
130
+
131
+ describe MemStore do
132
+
133
+ before do
134
+ @mb = MemStore.new
135
+ strings = %w(foo moo boo faa maa baa foa moa boa lao)
136
+ classes = [String, Array]
137
+ 10.times do |i|
138
+ @mb << Dummy.new(i, strings[i], classes[i%2].new)
139
+ end
140
+ end
141
+
142
+ it "finds all items fulfilling all conditions" do
143
+ matches = @mb.find_all(id: 3..7, child: String)
144
+ matches.collect{ |m| m.id }.must_equal [4, 6]
145
+ end
146
+
147
+ it "finds all items fulfilling at least one condition" do
148
+ matches = @mb.find_any(id: 3..7, child: String)
149
+ matches.collect{ |m| m.id }.must_equal [0, 2, 3, 4, 5, 6, 7, 8]
150
+ end
151
+
152
+ it "finds all items fulfilling exactly one condition" do
153
+ matches = @mb.find_one(name: /o/, child: String)
154
+ matches.collect{ |m| m.id }.must_equal [1, 4, 7, 9]
155
+ end
156
+
157
+ it "finds all items violating at least one condition" do
158
+ matches = @mb.find_not_all(name: /o/, child: String)
159
+ matches.collect{ |m| m.id }.must_equal [1, 3, 4, 5, 7, 9]
160
+ end
161
+
162
+ it "finds all items violating all conditions" do
163
+ matches = @mb.find_none(name: /o/, child: String)
164
+ matches.collect{ |m| m.id }.must_equal [3, 5]
165
+ end
166
+
167
+ it "finds the first item fulfilling all conditions" do
168
+ match = @mb.first_all(id: 3..7, child: String)
169
+ match.id.must_equal 4
170
+ end
171
+
172
+ it "finds the first item fulfilling at least one condition" do
173
+ match = @mb.first_any(id: 3..7, child: String)
174
+ match.id.must_equal 0
175
+ end
176
+
177
+ it "finds the first item fulfilling exactly one condition" do
178
+ match = @mb.first_one(name: /o/, child: String)
179
+ match.id.must_equal 1
180
+ end
181
+
182
+ it "finds the first item violating at least one condition" do
183
+ match = @mb.first_not_all(name: /o/, child: String)
184
+ match.id.must_equal 1
185
+ end
186
+
187
+ it "finds the first item violating all conditions" do
188
+ match = @mb.first_none(name: /o/, child: String)
189
+ match.id.must_equal 3
190
+ end
191
+
192
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require "minitest/autorun"
4
+ require "tempfile"
5
+ require "memstore"
6
+ require "memstore/yaml"
7
+
8
+ describe MemStore::HashStore do
9
+
10
+ before do
11
+ @key = :id
12
+ @mb = MemStore::HashStore.new(@key)
13
+ 10.times { |i| @mb << { id: i } }
14
+ end
15
+
16
+ it "can be converted to and from YAML" do
17
+ restored = MemStore::HashStore.from_yaml(@mb.to_yaml)
18
+ restored.items.must_equal @mb.items
19
+ restored.instance_variable_get(:@key).must_equal @key
20
+ end
21
+
22
+ it "can be serialized to and deserialized from a YAML file" do
23
+ tmp = Tempfile.new("memstore_yaml")
24
+ @mb.to_yaml_file(tmp)
25
+ restored = MemStore::HashStore.from_yaml_file(tmp)
26
+ restored.items.must_equal @mb.items
27
+ restored.instance_variable_get(:@key).must_equal @key
28
+ tmp.unlink
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memstore
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sebastian Klepper
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-19 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: MemStore is a simple in-memory data store that supports adding, retrieving
15
+ and deleting items as well as complex search queries and easy serialization.
16
+ email:
17
+ - sk@sebastianklepper.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/memstore.rb
28
+ - lib/memstore/json.rb
29
+ - lib/memstore/msgpack.rb
30
+ - lib/memstore/version.rb
31
+ - lib/memstore/yaml.rb
32
+ - memstore.gemspec
33
+ - spec/memstore_json_spec.rb
34
+ - spec/memstore_msgpack_spec.rb
35
+ - spec/memstore_spec.rb
36
+ - spec/memstore_yaml_spec.rb
37
+ homepage: https://github.com/sklppr/memstore
38
+ licenses: []
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 1.8.25
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: A simple, in-memory data store.
61
+ test_files:
62
+ - spec/memstore_json_spec.rb
63
+ - spec/memstore_msgpack_spec.rb
64
+ - spec/memstore_spec.rb
65
+ - spec/memstore_yaml_spec.rb