memstore 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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