paulcarey-relaxdb 0.2.1 → 0.2.2
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.textile +4 -2
- data/Rakefile +1 -1
- data/lib/relaxdb/document.rb +12 -13
- data/lib/relaxdb/has_one_proxy.rb +1 -1
- data/lib/relaxdb/paginate_params.rb +1 -1
- data/lib/relaxdb/paginator.rb +1 -1
- data/lib/relaxdb/query.rb +10 -1
- data/lib/relaxdb/relaxdb.rb +20 -5
- data/lib/relaxdb/server.rb +2 -1
- data/lib/relaxdb/sorted_by_view.rb +8 -7
- data/lib/relaxdb/view_result.rb +18 -0
- data/spec/document_spec.rb +17 -3
- data/spec/query_spec.rb +10 -0
- data/spec/relaxdb_spec.rb +28 -0
- metadata +2 -1
data/README.textile
CHANGED
@@ -2,6 +2,8 @@ h3. What's New?
|
|
2
2
|
|
3
3
|
* Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
|
4
4
|
** Note that if you invoke paginate_by on an already created view, the necessary reduce function won't be automatically created. Take a look at SortedByView and create the reduce func by hand.
|
5
|
+
* Support for multi key post
|
6
|
+
** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
|
5
7
|
* Works with CouchDB 0.9 trunk as of 2008/10/08. Note that pagination won't work correctly on trunk until issue "COUCHDB-135":http://issues.apache.org/jira/browse/COUCHDB-135 is fixed.
|
6
8
|
|
7
9
|
*Note*: 0.2.1 requires CouchDB 0.9 trunk. 0.2.0 works with CouchDB 0.8 onwards.
|
@@ -78,10 +80,10 @@ h3. Paginating models
|
|
78
80
|
<code>
|
79
81
|
# Controller (merb-action-args used for extracting view_params)
|
80
82
|
|
81
|
-
def action(
|
83
|
+
def action(page_params={})
|
82
84
|
u_id = @user._id
|
83
85
|
|
84
|
-
@posts = Post.paginate_by(
|
86
|
+
@posts = Post.paginate_by(page_params, :writer_id, :created_at) do |p|
|
85
87
|
p.startkey([u_id, {}]).endkey([u_id]).descending(true).count(5)
|
86
88
|
end
|
87
89
|
render
|
data/Rakefile
CHANGED
data/lib/relaxdb/document.rb
CHANGED
@@ -136,9 +136,11 @@ module RelaxDB
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
# Order changed as of 30/10/2008 to be consistent with ActiveRecord
|
140
|
+
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
139
141
|
def save
|
140
|
-
return false unless before_save
|
141
142
|
return false unless validates?
|
143
|
+
return false unless before_save
|
142
144
|
|
143
145
|
set_created_at if unsaved?
|
144
146
|
|
@@ -151,7 +153,7 @@ module RelaxDB
|
|
151
153
|
end
|
152
154
|
|
153
155
|
def validates?
|
154
|
-
|
156
|
+
total_success = true
|
155
157
|
properties.each do |prop|
|
156
158
|
if methods.include? "validate_#{prop}"
|
157
159
|
prop_val = instance_variable_get("@#{prop}")
|
@@ -161,9 +163,15 @@ module RelaxDB
|
|
161
163
|
@errors["#{prop}".to_sym] = send("#{prop}_validation_msg")
|
162
164
|
end
|
163
165
|
end
|
166
|
+
total_success &= success
|
164
167
|
end
|
165
168
|
end
|
166
|
-
|
169
|
+
total_success &= validate
|
170
|
+
total_success
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate
|
174
|
+
true
|
167
175
|
end
|
168
176
|
|
169
177
|
# Hmm. Rename... never_saved? newnew?
|
@@ -171,6 +179,7 @@ module RelaxDB
|
|
171
179
|
@_rev.nil?
|
172
180
|
end
|
173
181
|
alias_method :new_record?, :unsaved?
|
182
|
+
alias_method :new_document?, :unsaved?
|
174
183
|
|
175
184
|
def to_param
|
176
185
|
self._id
|
@@ -352,16 +361,6 @@ module RelaxDB
|
|
352
361
|
end
|
353
362
|
end
|
354
363
|
|
355
|
-
#
|
356
|
-
# Document views are generated on demand if they don't exist when queried.
|
357
|
-
# The paginator is used with both document generated views, and user defined views.
|
358
|
-
# Auto generating document views on demand in the paginator makes it significantly
|
359
|
-
# more complex and less amendable to use by user defined views. For these reasons,
|
360
|
-
# the first call to paginate_by for a particular list of attributes for a particular
|
361
|
-
# Document class will update the corresponding design doc, even if it already contains
|
362
|
-
# the required view. This should not incur a penalty with CouchDB as it uses the
|
363
|
-
# same index for byte identical views.
|
364
|
-
#
|
365
364
|
def self.paginate_by(page_params, *view_keys)
|
366
365
|
paginate_params = PaginateParams.new
|
367
366
|
yield paginate_params
|
@@ -37,7 +37,7 @@ module RelaxDB
|
|
37
37
|
view_name = @relationship
|
38
38
|
view_path = "_view/#{design_doc}/#{view_name}?key=\"#{@client._id}\""
|
39
39
|
map_function = ViewCreator.has_n(@target_class, @relationship_as_viewed_by_target)
|
40
|
-
RelaxDB.retrieve(view_path, design_doc, view_name, map_function)
|
40
|
+
RelaxDB.retrieve(view_path, design_doc, view_name, map_function).first
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -2,7 +2,7 @@ module RelaxDB
|
|
2
2
|
|
3
3
|
class PaginateParams
|
4
4
|
|
5
|
-
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending group reduce)
|
5
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending group reduce include_docs)
|
6
6
|
|
7
7
|
@@params.each do |param|
|
8
8
|
define_method(param.to_sym) do |*val|
|
data/lib/relaxdb/paginator.rb
CHANGED
data/lib/relaxdb/query.rb
CHANGED
@@ -16,7 +16,8 @@ module RelaxDB
|
|
16
16
|
#
|
17
17
|
class Query
|
18
18
|
|
19
|
-
|
19
|
+
# keys is not included in the standard param as it is significantly different from the others
|
20
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending skip group group_level reduce include_docs)
|
20
21
|
|
21
22
|
@@params.each do |param|
|
22
23
|
define_method(param.to_sym) do |*val|
|
@@ -36,6 +37,14 @@ module RelaxDB
|
|
36
37
|
@view_name = view_name
|
37
38
|
end
|
38
39
|
|
40
|
+
def keys(keys=nil)
|
41
|
+
if keys.nil?
|
42
|
+
@keys
|
43
|
+
else
|
44
|
+
@keys = { :keys => keys }.to_json
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
39
48
|
def view_path
|
40
49
|
uri = "_view/#{@design_doc}/#{@view_name}"
|
41
50
|
|
data/lib/relaxdb/relaxdb.rb
CHANGED
@@ -47,10 +47,16 @@ module RelaxDB
|
|
47
47
|
data["ok"]
|
48
48
|
end
|
49
49
|
|
50
|
-
def load(
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
def load(*ids)
|
51
|
+
if ids.size == 1
|
52
|
+
resp = db.get(ids[0])
|
53
|
+
data = JSON.parse(resp.body)
|
54
|
+
create_object(data)
|
55
|
+
else
|
56
|
+
resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
|
57
|
+
data = JSON.parse(resp.body)
|
58
|
+
data["rows"].map { |row| create_object(row["doc"]) }
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
62
|
# Used internally by RelaxDB
|
@@ -72,7 +78,7 @@ module RelaxDB
|
|
72
78
|
q = Query.new(design_doc, view_name)
|
73
79
|
yield q if block_given?
|
74
80
|
|
75
|
-
resp = db.get(q.view_path)
|
81
|
+
resp = q.keys ? db.post(q.view_path, q.keys) : db.get(q.view_path)
|
76
82
|
JSON.parse(resp.body)
|
77
83
|
end
|
78
84
|
|
@@ -136,10 +142,19 @@ module RelaxDB
|
|
136
142
|
|
137
143
|
# Convenience methods - should be in a diffent module?
|
138
144
|
|
145
|
+
def get(uri=nil)
|
146
|
+
JSON.parse(db.get(uri).body)
|
147
|
+
end
|
148
|
+
|
139
149
|
def pp_get(uri=nil)
|
140
150
|
resp = db.get(uri)
|
141
151
|
pp(JSON.parse(resp.body))
|
142
152
|
end
|
153
|
+
|
154
|
+
def pp_post(uri=nil, json=nil)
|
155
|
+
resp = db.post(uri, json)
|
156
|
+
pp(JSON.parse(resp.body))
|
157
|
+
end
|
143
158
|
|
144
159
|
end
|
145
160
|
|
data/lib/relaxdb/server.rb
CHANGED
@@ -84,7 +84,8 @@ module RelaxDB
|
|
84
84
|
@server.delete("/#{@db}/#{path}")
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
# *ignored allows methods to invoke get or post indifferently
|
88
|
+
def get(path=nil, *ignored)
|
88
89
|
@logger.info("GET /#{@db}/#{unesc(path)}")
|
89
90
|
@server.get("/#{@db}/#{path}")
|
90
91
|
end
|
@@ -31,25 +31,26 @@ module RelaxDB
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def view_name
|
34
|
-
|
35
|
-
s << "_#{att}_and"
|
36
|
-
end
|
37
|
-
s[0, s.size-4]
|
34
|
+
"all_sorted_by_" << @atts.join("_and_")
|
38
35
|
end
|
39
36
|
|
40
37
|
def query(query)
|
41
38
|
# If a view contains both a map and reduce function, CouchDB will by default return
|
42
39
|
# the result of the reduce function when queried.
|
43
40
|
# This class automatically creates both map and reduce functions so it can be used by the paginator.
|
44
|
-
# In normal usage, this class will be used with map functions, hence reduce is explicitly set
|
41
|
+
# In normal usage, this class will be used with map functions, hence reduce is explicitly set
|
42
|
+
# to false if it hasn't already been set.
|
45
43
|
query.reduce(false) if query.reduce.nil?
|
46
44
|
|
45
|
+
# I hope the query.group(true) should be temporary only (given that reduce has been set to false)
|
46
|
+
method = query.keys ? (query.group(true) && :post) : :get
|
47
|
+
|
47
48
|
begin
|
48
|
-
resp = RelaxDB.db.
|
49
|
+
resp = RelaxDB.db.send(method, query.view_path, query.keys)
|
49
50
|
rescue => e
|
50
51
|
design_doc = DesignDocument.get(@class_name)
|
51
52
|
design_doc.add_map_view(view_name, map_function).add_reduce_view(view_name, reduce_function).save
|
52
|
-
resp = RelaxDB.db.
|
53
|
+
resp = RelaxDB.db.send(method, query.view_path, query.keys)
|
53
54
|
end
|
54
55
|
|
55
56
|
data = JSON.parse(resp.body)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class ViewResult < DelegateClass(Array)
|
4
|
+
|
5
|
+
attr_reader :offset, :total_rows
|
6
|
+
|
7
|
+
def initialize(result_hash)
|
8
|
+
objs = RelaxDB.create_from_hash(result_hash)
|
9
|
+
|
10
|
+
@offset = result_hash["offset"]
|
11
|
+
@total_rows = result_hash["total_rows"]
|
12
|
+
|
13
|
+
super(objs)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/document_spec.rb
CHANGED
@@ -15,8 +15,7 @@ describe RelaxDB::Document do
|
|
15
15
|
describe ".new" do
|
16
16
|
|
17
17
|
it "should create an object with an id" do
|
18
|
-
|
19
|
-
p._id.should_not be_nil
|
18
|
+
Atom.new._id.should_not be_nil
|
20
19
|
end
|
21
20
|
|
22
21
|
it "should create an object with a nil revision" do
|
@@ -244,6 +243,12 @@ describe RelaxDB::Document do
|
|
244
243
|
User.new(:name => "atlas").save
|
245
244
|
User.all.sorted_by(:name, :age) { |q| q.startkey(["paul",0 ]).endkey(["paul", 50]) }.size.should == 1
|
246
245
|
end
|
246
|
+
|
247
|
+
it "should be retrievable by a multi key post" do
|
248
|
+
5.times { |i| Primitives.new(:num => i).save }
|
249
|
+
ps = Primitives.all.sorted_by(:num) { |q| q.keys([0, 4]) }
|
250
|
+
ps.map { |p| p.num }.should == [0, 4]
|
251
|
+
end
|
247
252
|
|
248
253
|
end
|
249
254
|
|
@@ -304,7 +309,7 @@ describe RelaxDB::Document do
|
|
304
309
|
r = Class.new(RelaxDB::Document) do
|
305
310
|
property :thumbs_up, :validator => lambda { |tu| tu >=0 && tu < 3 }
|
306
311
|
end
|
307
|
-
r.new(:thumbs_up => 2).save.
|
312
|
+
r.new(:thumbs_up => 2).save.should be
|
308
313
|
r.new(:thumbs_up => 3).save.should be_false
|
309
314
|
end
|
310
315
|
|
@@ -328,6 +333,15 @@ describe RelaxDB::Document do
|
|
328
333
|
x.errors[:bar].should == "rab"
|
329
334
|
end
|
330
335
|
|
336
|
+
it "should prevent saving unless all validations pass" do
|
337
|
+
r = Class.new(RelaxDB::Document) do
|
338
|
+
property :foo, :validator => lambda { false }
|
339
|
+
property :bar, :validator => lambda { true }
|
340
|
+
end
|
341
|
+
x = r.new
|
342
|
+
x.save.should == false
|
343
|
+
end
|
344
|
+
|
331
345
|
it "may be a proc" do
|
332
346
|
r = Class.new(RelaxDB::Document) do
|
333
347
|
property :thumbs_up, :validator => lambda { false }
|
data/spec/query_spec.rb
CHANGED
data/spec/relaxdb_spec.rb
CHANGED
@@ -58,6 +58,23 @@ describe RelaxDB do
|
|
58
58
|
|
59
59
|
end
|
60
60
|
|
61
|
+
describe ".load" do
|
62
|
+
|
63
|
+
it "should load a single document" do
|
64
|
+
a = Atom.new.save
|
65
|
+
ar = RelaxDB.load a._id
|
66
|
+
ar.should == a
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should load an arbitrary number of documents" do
|
70
|
+
a1, a2 = Atom.new.save, Atom.new.save
|
71
|
+
ar1, ar2 = RelaxDB.load a1._id, a2._id
|
72
|
+
ar1.should == a1
|
73
|
+
ar2.should == a2
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
61
78
|
describe ".view" do
|
62
79
|
|
63
80
|
map_func = %Q<
|
@@ -80,6 +97,17 @@ describe RelaxDB do
|
|
80
97
|
data["rows"].size.should == 1
|
81
98
|
end
|
82
99
|
|
100
|
+
it "should be queryable with a multi key post" do
|
101
|
+
5.times { |i| Primitives.new(:num => i).save }
|
102
|
+
# Create the view
|
103
|
+
Primitives.all.sorted_by(:num)
|
104
|
+
resp = RelaxDB.view("Primitives", "all_sorted_by_num") do |q|
|
105
|
+
q.keys([0, 4])
|
106
|
+
q.reduce(false).group(true) # group invocation should hopefully be temporary
|
107
|
+
end
|
108
|
+
RelaxDB.instantiate(resp).map{ |p| p.num }.should == [0, 4]
|
109
|
+
end
|
110
|
+
|
83
111
|
end
|
84
112
|
|
85
113
|
describe ".merge" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paulcarey-relaxdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Carey
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/relaxdb/sorted_by_view.rb
|
70
70
|
- lib/relaxdb/uuid_generator.rb
|
71
71
|
- lib/relaxdb/view_object.rb
|
72
|
+
- lib/relaxdb/view_result.rb
|
72
73
|
- lib/relaxdb/view_uploader.rb
|
73
74
|
- lib/relaxdb/views.rb
|
74
75
|
- lib/more/grapher.rb
|