campchair 0.0.4
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 +5 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +273 -0
- data/Rakefile +9 -0
- data/campchair.gemspec +24 -0
- data/lib/campchair.rb +14 -0
- data/lib/campchair/leveldb.rb +70 -0
- data/lib/campchair/version.rb +3 -0
- data/lib/campchair/views.rb +4 -0
- data/spec/.gitkeep +0 -0
- data/spec/campchair/leveldb_spec.rb +139 -0
- data/spec/campchair/views_spec.rb +4 -0
- data/spec/campchair_spec.rb +21 -0
- data/spec/integration/readme_examples_spec.rb +60 -0
- data/spec/spec_helper.rb +2 -0
- data/tmp/.gitkeep +0 -0
- metadata +102 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
# ⑁ Campchair
|
2
|
+
### Lightweight, ghetto-tech CouchDB-like views in Ruby
|
3
|
+
|
4
|
+
[](http://travis-ci.org/captainpete/campchair)
|
5
|
+
|
6
|
+
___This README is a wishlist. The thing doesn't entirely work yet!___
|
7
|
+
|
8
|
+
Campchair provides some helpers for lazily materialized views and a
|
9
|
+
persistence mapping for different Ruby types.
|
10
|
+
|
11
|
+
Views are described a bit like those found in [CouchDB](http://couchdb.apache.org/).
|
12
|
+
Campchair is not a service and has no client-server stuff.
|
13
|
+
Parallel writes are not supported, but that's okay – you can use something
|
14
|
+
else for concurrency and use this in your persistence fiber/thread/process.
|
15
|
+
Persistence happens through [LevelDB](http://code.google.com/p/leveldb/).
|
16
|
+
This is not append-only and replication is not a feature.
|
17
|
+
For these reasons it's called campchair, not couch :)
|
18
|
+
|
19
|
+
This project was born at [Railscamp 11](http://railscamps.com/) for the heavy-metrics project.
|
20
|
+
|
21
|
+
### How does it look?
|
22
|
+
|
23
|
+
For these examples we'll work with two documents.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
fred = {
|
27
|
+
:name => 'Fred', :height => 1.748,
|
28
|
+
:location => 'Flinders St Station, Melbourne, Australia',
|
29
|
+
:first_seen => Time.new(2012, 3, 6, 12, 10),
|
30
|
+
:last_seen => Time.new(2012, 3, 6, 12, 40)
|
31
|
+
}
|
32
|
+
jane = {
|
33
|
+
:name => 'Jane', :height => 1.634,
|
34
|
+
:location => 'Flinders St Station, Melbourne, Australia',
|
35
|
+
:first_seen => Time.new(2012, 3, 6, 12, 20),
|
36
|
+
:last_seen => Time.new(2012, 3, 6, 12, 50)
|
37
|
+
}
|
38
|
+
```
|
39
|
+
|
40
|
+
Mixing in Campchair DB behavior to a class is as simple as...
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class People
|
44
|
+
include Campchair::LevelDB
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Now, use it like a `Hash`.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Add a document to the db
|
52
|
+
id = People << fred
|
53
|
+
|
54
|
+
# Add another person
|
55
|
+
People << jane
|
56
|
+
|
57
|
+
# Rerieve a document from the db
|
58
|
+
fred = People[id]
|
59
|
+
|
60
|
+
# Delete the document from the db
|
61
|
+
People.delete(id)
|
62
|
+
|
63
|
+
# Update/create the document in the db using an id
|
64
|
+
fred[:last_seen] = Time.new(2012, 3, 6, 12, 40, 25)
|
65
|
+
doc[id] = fred
|
66
|
+
```
|
67
|
+
|
68
|
+
### Basic views _not yet implemented_
|
69
|
+
|
70
|
+
For views to be available on classes we need to include `Campchair::Views`.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class People # add some views to the people class
|
74
|
+
include Campchair::Views
|
75
|
+
|
76
|
+
# Map to document by id
|
77
|
+
view :all do
|
78
|
+
map { |id, person| emit id, person }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Map to name by doc id
|
82
|
+
view :names do
|
83
|
+
map { |id, person| emit id, person.name }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
People.names
|
88
|
+
# ['Fred', 'Jane']
|
89
|
+
```
|
90
|
+
|
91
|
+
### Using reduce _not yet implemented_
|
92
|
+
|
93
|
+
Reduce gets called on groups of map results.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class People
|
97
|
+
# Map to count by doc id
|
98
|
+
# Reduce to sum of counts
|
99
|
+
view :count do
|
100
|
+
map { |id, person| emit id, 1 }
|
101
|
+
reduce do |keys, values|
|
102
|
+
values.inject(0) { |memo, values| memo + values }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
People.count
|
108
|
+
# 2
|
109
|
+
```
|
110
|
+
|
111
|
+
Reduce happens in stages. Supply a rereduce step to handle reducing reduce
|
112
|
+
results. The advantage of rereduce is results can be cached in a b-tree index.
|
113
|
+
|
114
|
+
If `rereduce` is supplied then `reduce` is called with a portion of the `map`
|
115
|
+
results and those reductions are passed to `rereduce`. If `rereduce` is
|
116
|
+
omitted then `reduce` results will not be cached.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class People
|
120
|
+
# Map to location by doc id
|
121
|
+
# Reduce by unique location
|
122
|
+
view :unique_locations do
|
123
|
+
map { |id, person| emit id, person.location }
|
124
|
+
reduce do |keys, values|
|
125
|
+
results = values.uniq
|
126
|
+
end
|
127
|
+
rereduce do |results|
|
128
|
+
results.inject(Set.new) { |memo, values| memo.merge values }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
People.unique_locations
|
134
|
+
# <Set: {"Flinders St Station, Melbourne, Australia"}>
|
135
|
+
```
|
136
|
+
|
137
|
+
Keep in mind that if `rereduce` is not supplied, `reduce` will always get
|
138
|
+
called with the entire keyspace. If the dataset is large this might consume a
|
139
|
+
lot of memory.
|
140
|
+
|
141
|
+
### Using keys with reduce _not yet implemented_
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class People
|
145
|
+
# Map to count by name
|
146
|
+
# Reduce by sum of counts
|
147
|
+
view :count_by_name do
|
148
|
+
map { |id, person| emit name, 1 }
|
149
|
+
reduce do |keys, values|
|
150
|
+
values.inject(0) { |memo, value| memo + value }
|
151
|
+
end
|
152
|
+
rereduce do |results|
|
153
|
+
results.inject(0) { |memo, value| memo + value }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
People.count_by_name['Jane']
|
159
|
+
# 1
|
160
|
+
People.count_by_name
|
161
|
+
# 2
|
162
|
+
```
|
163
|
+
|
164
|
+
### Controlling the index _not yet relevant_
|
165
|
+
|
166
|
+
It's possible to write reduce methods that return results larger than the
|
167
|
+
input. If you're doing this, you're gonna have a bad time. Unless you're sure
|
168
|
+
the dataset will remain small enough that the index for each `rereduce` doesn't
|
169
|
+
blow out your disk store.
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
class People
|
173
|
+
# DANGER: this view will blow out the index
|
174
|
+
|
175
|
+
# Map to count by name
|
176
|
+
# Reduce by sum of counts by name
|
177
|
+
view :count_all_by_name do
|
178
|
+
map { |id, person| emit person.name, 1 }
|
179
|
+
reduce do |keys, values|
|
180
|
+
result = Hash.new
|
181
|
+
keys.each_with_index do |key, index|
|
182
|
+
result[key] ||= 0
|
183
|
+
result[key] += values[index]
|
184
|
+
end
|
185
|
+
result # scary, nearly as large as the input
|
186
|
+
end
|
187
|
+
rereduce(:cache => false) do |results|
|
188
|
+
result = {}
|
189
|
+
results.each do |name, count|
|
190
|
+
result[name] ||= 0
|
191
|
+
result[name] += count
|
192
|
+
end
|
193
|
+
result # likewise, this will explode the index
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
People.count_all_by_name['Jane']
|
199
|
+
# { 'Jane' => 1 }
|
200
|
+
People.count_all_by_name
|
201
|
+
# { 'Fred' => 1, 'Jane' => 1 }
|
202
|
+
```
|
203
|
+
|
204
|
+
### Re-re-reducing _not yet implemented_
|
205
|
+
|
206
|
+
Sometimes it's useful to post-process reduce results. Add another rereduce to
|
207
|
+
process rereduced values. Only the first rereduce gets called on values it
|
208
|
+
produces itself. Subsequent rereduces are chained to the initial result.
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
clas People
|
212
|
+
# Map to height by location
|
213
|
+
# Reduce by count and sum of heights
|
214
|
+
# Rereduce by the calculating average
|
215
|
+
view :average_height_by_location do
|
216
|
+
map { |id, person| emit location, height }
|
217
|
+
reduce do |keys, values|
|
218
|
+
result = Hash.new
|
219
|
+
keys.each_with_index do |key, index|
|
220
|
+
result[key] ||= { :count => 0, :sum => 0 }
|
221
|
+
result[key][:count] += 1
|
222
|
+
result[key][:sum] += height
|
223
|
+
end
|
224
|
+
result
|
225
|
+
end
|
226
|
+
rereduce do |results|
|
227
|
+
result = { :count => 0, :sum => 0 }
|
228
|
+
results.each do |result|
|
229
|
+
result[:count] += part[:count]
|
230
|
+
result[:sum] += part[:sum]
|
231
|
+
end
|
232
|
+
result
|
233
|
+
end
|
234
|
+
rereduce do |results|
|
235
|
+
count = results.inject(0) { |memo, result| result[:count] }
|
236
|
+
sum = results.inject(0) { |memo, result| result[:sum] }
|
237
|
+
count == 0 ? nil : sum / count
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
People.average_height_by_location
|
243
|
+
# 1.690999999
|
244
|
+
People.average_height_by_location['Flinders St Station, Melbourne, Australia']
|
245
|
+
# 1.690999999
|
246
|
+
People.average_height_by_location['Docklands, Melbourne, Australia']
|
247
|
+
# nil
|
248
|
+
```
|
249
|
+
|
250
|
+
### Custom database path
|
251
|
+
|
252
|
+
By default, the folder for db files is 'cddb'.
|
253
|
+
You can change this with:
|
254
|
+
```ruby
|
255
|
+
Campchair.db_path = 'db/heavy_metrics'
|
256
|
+
```
|
257
|
+
|
258
|
+
You can also change the db on a per-class basis.
|
259
|
+
```ruby
|
260
|
+
class Person
|
261
|
+
include Campchair::LevelDB
|
262
|
+
self.db_path = 'db/heavy_metrics/Person'
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
### TODO
|
267
|
+
|
268
|
+
- Make the README examples work
|
269
|
+
- Views spanning views
|
270
|
+
- Caching view results
|
271
|
+
- Caching reduce results
|
272
|
+
- method_missing for `count_by_*, sum_of_*s, min_*, max_*, unique_*s`
|
273
|
+
- cache priming
|
data/Rakefile
ADDED
data/campchair.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "campchair/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "campchair"
|
7
|
+
s.version = Campchair::VERSION
|
8
|
+
s.authors = ["Peter Hollows"]
|
9
|
+
s.email = ["pete@dojo7.com"]
|
10
|
+
s.homepage = "https://github.com/captainpete/campchair"
|
11
|
+
s.summary = %q{Lightweight, ghetto-tech CouchDB-like views in Ruby}
|
12
|
+
s.description = %q{Campchair is a map-reduce framework using levelDB for persistence, and Ruby for everything else.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "campchair"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency(%q<leveldb-ruby>, ["~> 0.14"])
|
22
|
+
s.add_development_dependency(%q<rspec>)
|
23
|
+
s.add_development_dependency(%q<rake>)
|
24
|
+
end
|
data/lib/campchair.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'leveldb'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Campchair
|
5
|
+
module LevelDB
|
6
|
+
class << self
|
7
|
+
def generate_key
|
8
|
+
@alpha ||= [('a'..'f'), ('0'..'9')].map(&:to_a).flatten
|
9
|
+
(0...40).map { @alpha[rand(@alpha.size)] }.join
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(value)
|
13
|
+
Marshal.dump(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decode(value)
|
17
|
+
Marshal.load(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def included(base)
|
21
|
+
base.class_eval do
|
22
|
+
class << self
|
23
|
+
attr_writer :db_path, :db_name, :db
|
24
|
+
|
25
|
+
def db_path
|
26
|
+
@db_path || File.join(Campchair.db_path, db_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def db_name
|
30
|
+
@db_name || name
|
31
|
+
end
|
32
|
+
|
33
|
+
def db
|
34
|
+
@db ||= create_db
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](key)
|
38
|
+
val = db[key]
|
39
|
+
val && Campchair::LevelDB.decode(db[key])
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(key, value)
|
43
|
+
db[key] = Campchair::LevelDB.encode(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def <<(value)
|
47
|
+
id = Campchair::LevelDB.generate_key
|
48
|
+
self[id] = value
|
49
|
+
return id
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(key)
|
53
|
+
db.delete(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def create_db
|
59
|
+
FileUtils.mkdir_p(db_path)
|
60
|
+
::LevelDB::DB.new(db_path)
|
61
|
+
end
|
62
|
+
|
63
|
+
end # class << self
|
64
|
+
|
65
|
+
end # base.class_eval
|
66
|
+
end # included
|
67
|
+
end # class << self
|
68
|
+
|
69
|
+
end # module LevelDB
|
70
|
+
end # module Campchair
|
data/spec/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Campchair::LevelDB do
|
4
|
+
let(:test_module) do
|
5
|
+
Campchair::LevelDB.dup
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".generate_key" do
|
9
|
+
it "generates a hex key 40 characters long" do
|
10
|
+
test_module.generate_key.should =~ /\A[a-f0-9]{40}\Z/
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "included" do
|
15
|
+
let(:test_class) do
|
16
|
+
Object.send(:remove_const, :TestEntity) if defined? TestEntity
|
17
|
+
TestEntity = Class.new do
|
18
|
+
include Campchair::LevelDB
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
`rm -fr tmp/ccdb`
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "setting the db_name" do
|
27
|
+
describe ".db_name" do
|
28
|
+
it "is the class name by default" do
|
29
|
+
test_class.stub! :name => 'TestEntity'
|
30
|
+
test_class.db_name.should == 'TestEntity'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".db_name=" do
|
35
|
+
it "is the custom db_name specified" do
|
36
|
+
test_class.db_name = 'TestEntityCustomName'
|
37
|
+
test_class.db_name.should == 'TestEntityCustomName'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "setting the db_path" do
|
43
|
+
describe ".db_path" do
|
44
|
+
it "is the Campchair.db_path, and the db_name by default" do
|
45
|
+
Campchair.should_receive(:db_path).and_return('some/path')
|
46
|
+
test_class.db_path.should == 'some/path/TestEntity'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe ".db_path=" do
|
51
|
+
it "is the custom db_path specified" do
|
52
|
+
test_class.db_path = 'some/other/path'
|
53
|
+
test_class.db_path.should == 'some/other/path'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with test db_path set" do
|
59
|
+
before do
|
60
|
+
test_class.db_path = 'tmp/ccdb/test'
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ".db" do
|
64
|
+
it "is a LevelDB, at the db_path" do
|
65
|
+
test_class.db.should be_a(LevelDB::DB)
|
66
|
+
test_class.db.pathname.should == 'tmp/ccdb/test'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "no data serialization" do
|
71
|
+
before do
|
72
|
+
# Passthrough {en,de}coding for these tests
|
73
|
+
Campchair::LevelDB.stub(:encode).and_return {|v| v}
|
74
|
+
Campchair::LevelDB.stub(:decode).and_return {|v| v}
|
75
|
+
|
76
|
+
FileUtils.mkdir_p('tmp/ccdb/TestEntity')
|
77
|
+
@db = ::LevelDB::DB.new('tmp/ccdb/TestEntity')
|
78
|
+
|
79
|
+
test_class.stub! :db => @db
|
80
|
+
end
|
81
|
+
|
82
|
+
describe ".[]" do
|
83
|
+
it "is nil when there's no matching key in the database" do
|
84
|
+
test_class['nonexistent'].should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "is the value of the matching key in the database" do
|
88
|
+
@db['something'] = '22'
|
89
|
+
test_class['something'].should == '22'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe ".[]=" do
|
94
|
+
it "sets the value of the key in the database" do
|
95
|
+
test_class['something'] = '22'
|
96
|
+
@db['something'].should == '22'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe ".<<" do
|
101
|
+
it "adds the value to the database, generating a key" do
|
102
|
+
key = 'b78aa2f6b718651743fac2682003f2c63340ae34b'
|
103
|
+
Campchair::LevelDB.stub! :generate_key => key
|
104
|
+
|
105
|
+
id = TestEntity << '22'
|
106
|
+
id.should == key
|
107
|
+
test_class[id].should == '22'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe ".delete" do
|
112
|
+
it "deletes the key its values from the db" do
|
113
|
+
test_class['something'] = '22'
|
114
|
+
test_class.delete('something')
|
115
|
+
|
116
|
+
test_class['something'].should be_nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "Campchair::LevelDB serialization" do
|
122
|
+
describe ".[]{,=}" do
|
123
|
+
it "persists non-string data structures" do
|
124
|
+
doc = { :sym => :sym, 'string' => 'string', 'types' => [1234.12341234, (55..44), /regexen/, Time.now, { 'a' => 22 }] }
|
125
|
+
test_class['something'] = doc
|
126
|
+
test_class['something'].should == doc
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe ".[]" do
|
131
|
+
it "is nil when the document does not exist" do
|
132
|
+
test_class['something'].should be_nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end # context "included"
|
139
|
+
end # describe Campchair::LevelDB
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Campchair do
|
4
|
+
let(:test_module) do
|
5
|
+
Campchair::dup
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".db_path" do
|
9
|
+
it "is 'ccdb' by default" do
|
10
|
+
test_module.db_path.should == 'ccdb'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".db_path=" do
|
15
|
+
it "sets the default LevelDB for Campchair classes" do
|
16
|
+
test_module.db_path = 'tmp/ccdb'
|
17
|
+
test_module.db_path.should == 'tmp/ccdb'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "DB behavior" do
|
4
|
+
before do
|
5
|
+
@fred = {
|
6
|
+
:name => 'Fred', :height => 1.748,
|
7
|
+
:location => 'Flinders St Station, Melbourne, Australia',
|
8
|
+
:first_seen => Time.local(2012, 3, 6, 12, 10),
|
9
|
+
:last_seen => Time.local(2012, 3, 6, 12, 40)
|
10
|
+
}
|
11
|
+
@jane = {
|
12
|
+
:name => 'Jane', :height => 1.634,
|
13
|
+
:location => 'Flinders St Station, Melbourne, Australia',
|
14
|
+
:first_seen => Time.local(2012, 3, 6, 12, 20),
|
15
|
+
:last_seen => Time.local(2012, 3, 6, 12, 50)
|
16
|
+
}
|
17
|
+
|
18
|
+
class TestPeople
|
19
|
+
include Campchair::LevelDB
|
20
|
+
self.db_path = 'tmp/ccdb'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after do
|
25
|
+
`rm -fr tmp/ccdb`
|
26
|
+
end
|
27
|
+
|
28
|
+
it "persists documents without an id" do
|
29
|
+
id = TestPeople << @fred
|
30
|
+
TestPeople << @jane
|
31
|
+
end
|
32
|
+
|
33
|
+
it "retrieves documents" do
|
34
|
+
id = TestPeople << @fred
|
35
|
+
TestPeople[id].should == @fred
|
36
|
+
end
|
37
|
+
|
38
|
+
it "deletes documents" do
|
39
|
+
id = TestPeople << @fred
|
40
|
+
TestPeople.delete(id)
|
41
|
+
TestPeople[id].should be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "updates docuemnts with an id" do
|
45
|
+
id = TestPeople << @fred
|
46
|
+
new_last_seen = Time.local(2012, 3, 6, 12, 50)
|
47
|
+
@fred[:last_seen] = new_last_seen
|
48
|
+
TestPeople[id] = @fred
|
49
|
+
|
50
|
+
TestPeople[id][:last_seen].should == new_last_seen
|
51
|
+
end
|
52
|
+
|
53
|
+
it "creates documents with an id" do
|
54
|
+
id = 'something'
|
55
|
+
TestPeople[id] = @fred
|
56
|
+
|
57
|
+
TestPeople[id].should == @fred
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: campchair
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Hollows
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: leveldb-ruby
|
16
|
+
requirement: &70242209955760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.14'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70242209955760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70242209955340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70242209955340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70242209954880 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70242209954880
|
47
|
+
description: Campchair is a map-reduce framework using levelDB for persistence, and
|
48
|
+
Ruby for everything else.
|
49
|
+
email:
|
50
|
+
- pete@dojo7.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- .rspec
|
57
|
+
- .travis.yml
|
58
|
+
- Gemfile
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- campchair.gemspec
|
62
|
+
- lib/campchair.rb
|
63
|
+
- lib/campchair/leveldb.rb
|
64
|
+
- lib/campchair/version.rb
|
65
|
+
- lib/campchair/views.rb
|
66
|
+
- spec/.gitkeep
|
67
|
+
- spec/campchair/leveldb_spec.rb
|
68
|
+
- spec/campchair/views_spec.rb
|
69
|
+
- spec/campchair_spec.rb
|
70
|
+
- spec/integration/readme_examples_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- tmp/.gitkeep
|
73
|
+
homepage: https://github.com/captainpete/campchair
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: campchair
|
93
|
+
rubygems_version: 1.8.17
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Lightweight, ghetto-tech CouchDB-like views in Ruby
|
97
|
+
test_files:
|
98
|
+
- spec/campchair/leveldb_spec.rb
|
99
|
+
- spec/campchair/views_spec.rb
|
100
|
+
- spec/campchair_spec.rb
|
101
|
+
- spec/integration/readme_examples_spec.rb
|
102
|
+
- spec/spec_helper.rb
|