paulcarey-relaxdb 0.2.8 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +100 -108
- data/Rakefile +13 -2
- data/docs/spec_results.html +609 -154
- data/lib/more/atomic_bulk_save_support.rb +18 -0
- data/lib/relaxdb/all_delegator.rb +15 -39
- data/lib/relaxdb/design_doc.rb +8 -6
- data/lib/relaxdb/document.rb +140 -74
- data/lib/relaxdb/has_many_proxy.rb +6 -5
- data/lib/relaxdb/has_one_proxy.rb +2 -5
- data/lib/relaxdb/paginate_params.rb +3 -4
- data/lib/relaxdb/paginator.rb +12 -16
- data/lib/relaxdb/query.rb +17 -13
- data/lib/relaxdb/references_many_proxy.rb +3 -5
- data/lib/relaxdb/relaxdb.rb +51 -45
- data/lib/relaxdb/server.rb +4 -4
- data/lib/relaxdb/view_uploader.rb +10 -8
- data/lib/relaxdb/views.rb +88 -28
- data/lib/relaxdb.rb +0 -1
- data/readme.rb +79 -0
- data/spec/belongs_to_spec.rb +1 -6
- data/spec/callbacks_spec.rb +19 -3
- data/spec/derived_properties_spec.rb +2 -7
- data/spec/design_doc_spec.rb +3 -3
- data/spec/document_spec.rb +19 -57
- data/spec/has_many_spec.rb +11 -14
- data/spec/has_one_spec.rb +1 -6
- data/spec/query_spec.rb +26 -22
- data/spec/references_many_spec.rb +1 -6
- data/spec/relaxdb_spec.rb +88 -29
- data/spec/spec_helper.rb +34 -0
- data/spec/spec_models.rb +163 -118
- metadata +7 -3
- data/lib/relaxdb/sorted_by_view.rb +0 -65
data/README.textile
CHANGED
@@ -1,44 +1,12 @@
|
|
1
1
|
h3. What's New?
|
2
2
|
|
3
|
-
|
3
|
+
RelaxDB 0.3 released - compatible with CouchDB 0.9.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
* Potentially breaking change. @load@ now returns an array if passed an array of size one. Previously it would have returned a single object.
|
8
|
-
|
9
|
-
* Update conflict hook and property
|
10
|
-
|
11
|
-
* Semantic consistency for bulk_save and bulk_save! wrt to save and save!
|
12
|
-
|
13
|
-
* Multiple exception handling improvements
|
14
|
-
|
15
|
-
* @save_all@ that issues a bulk_save for an object and its has_one and has_many children
|
16
|
-
|
17
|
-
* assignment of @has_many@ relationships
|
18
|
-
|
19
|
-
* Validations may be skipped by passing the attribute symbol(s) to @save@ or @save!@.
|
20
|
-
|
21
|
-
* Denormalisation via derived properties. Examples in spec/derived_properties_spec.rb.
|
22
|
-
|
23
|
-
* Semantic changes for @ has_many#<< @. The parent object is now assigned to the child object *prior* to validation. This potentially breaking change was made to allow child objects to derive properties from a parent object.
|
24
|
-
|
25
|
-
* Semantic consistency for load, load!, save and save!. The bang versions raise an exception when their more relaxed siblings would simply return nil.
|
26
|
-
|
27
|
-
* Minimal support for CouchDB validation
|
28
|
-
|
29
|
-
* Time storage changes. All Time objects are now converted to UTC and formatted as @ %Y/%m/%d %H:%M:%S +0000 @. Storing all Times as UTC should have been happening anyway. Formatting Times as above (as opposed to ISO 8601 as was done prior to 0.2.3) allows the Time strings to be passed directly to Date.new in a JavaScript interpreter.
|
30
|
-
|
31
|
-
* Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
|
32
|
-
** 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.
|
33
|
-
* Support for multi key post
|
34
|
-
** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
|
35
|
-
* Works with CouchDB 0.9 trunk as of 2009/01/02. Note that pagination won't work correctly on trunk until issue "COUCHDB-135":http://issues.apache.org/jira/browse/COUCHDB-135 is fixed.
|
36
|
-
|
37
|
-
*Note*: Current versions require CouchDB 0.9 trunk. If you're working with CouchDB 0.8 or 0.8.1, please build from commit @ a8a2d496462 @.
|
5
|
+
Version 0.3 includes many breaking changes. Most notable are simplified view syntax changes and the requirement that a design doc be specified up front.
|
38
6
|
|
39
7
|
h2. Overview
|
40
8
|
|
41
|
-
RelaxDB provides a Ruby interface to CouchDB. It offers a simple idiom for specifying object relationships. The underlying objects are persisted to
|
9
|
+
RelaxDB provides a Ruby interface to CouchDB. It offers a simple idiom for specifying object relationships. The underlying objects are persisted to CouchDB and are retreived using CouchDB idioms.
|
42
10
|
|
43
11
|
A few facilities are provided including pretty printing of GET requests and uploading of JavaScript views.
|
44
12
|
|
@@ -46,14 +14,21 @@ A basic merb plugin, "merb_relaxdb":http://github.com/paulcarey/merb_relaxdb/tre
|
|
46
14
|
|
47
15
|
For more complete documentation take a look at docs/spec_results.html and the corresponding specs.
|
48
16
|
|
49
|
-
|
17
|
+
*Note*: While RelaxDB 0.3 is explicitly compatible with CouchDB 0.9, HEAD typically tracks CouchDB HEAD.
|
18
|
+
|
19
|
+
h2. Details
|
50
20
|
|
51
21
|
h3. Getting started
|
52
22
|
|
53
23
|
<pre>
|
54
24
|
<code>
|
55
|
-
|
56
|
-
|
25
|
+
require 'rubygems'
|
26
|
+
require 'relaxdb'
|
27
|
+
|
28
|
+
RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "app"
|
29
|
+
RelaxDB.use_db "relaxdb_scratch"
|
30
|
+
|
31
|
+
RelaxDB.enable_view_creation # creates views when class definition is executed
|
57
32
|
</code>
|
58
33
|
</pre>
|
59
34
|
|
@@ -61,27 +36,37 @@ h3. Defining models
|
|
61
36
|
|
62
37
|
<pre>
|
63
38
|
<code>
|
64
|
-
class Writer < RelaxDB::Document
|
65
|
-
property :name, :default => "anon"
|
66
|
-
|
67
|
-
has_many :posts, :class => "Post"
|
68
|
-
has_many :ratings, :class => "Post", :known_as => :critic
|
69
|
-
end
|
70
39
|
|
71
|
-
|
72
|
-
|
73
|
-
|
40
|
+
class User < RelaxDB::Document
|
41
|
+
property :name
|
42
|
+
end
|
43
|
+
|
44
|
+
class Invite < RelaxDB::Document
|
45
|
+
|
46
|
+
property :created_at
|
47
|
+
|
48
|
+
property :event_name
|
49
|
+
|
50
|
+
property :state, :default => "awaiting_response",
|
51
|
+
:validator => lambda { |s| %w(accepted rejected awaiting_response).include? s }
|
74
52
|
|
75
|
-
|
76
|
-
|
53
|
+
references :sender, :validator => :required
|
54
|
+
|
55
|
+
references :recipient, :validator => :required
|
56
|
+
|
57
|
+
property :sender_name,
|
58
|
+
:derived => [:sender, lambda { |p, o| o.sender.name } ]
|
59
|
+
|
60
|
+
view_by :sender_name
|
61
|
+
view_by :sender_id
|
62
|
+
view_by :recipient_id, :created_at, :descending => true
|
63
|
+
|
64
|
+
def on_update_conflict
|
65
|
+
puts "conflict!"
|
77
66
|
end
|
67
|
+
|
68
|
+
end
|
78
69
|
|
79
|
-
class Rating < RelaxDB::Document
|
80
|
-
property :thumbs_up, :validator => lambda { |tu| tu >= 0 && tu < 3 }, :validation_msg => "No no"
|
81
|
-
|
82
|
-
belongs_to :post
|
83
|
-
belongs_to :critic
|
84
|
-
end
|
85
70
|
</code>
|
86
71
|
</pre>
|
87
72
|
|
@@ -89,16 +74,46 @@ h3. Exploring models
|
|
89
74
|
|
90
75
|
<pre>
|
91
76
|
<code>
|
92
|
-
|
77
|
+
# Saving objects
|
78
|
+
|
79
|
+
sofa = User.new(:name => "sofa").save!
|
80
|
+
futon = User.new(:name => "futon").save!
|
81
|
+
|
82
|
+
i = Invite.new :sender => sofa, :recipient => futon, :event_name => "CouchCamp"
|
83
|
+
i.save!
|
84
|
+
|
85
|
+
# Loading and querying
|
86
|
+
|
87
|
+
il = RelaxDB.load i._id
|
88
|
+
puts i == il # true
|
89
|
+
|
90
|
+
ir = Invite.by_sender_name "sofa"
|
91
|
+
puts i == ir # true
|
92
|
+
|
93
|
+
ix = Invite.by_sender_name(:key => "sofa").first
|
94
|
+
puts i == ix # true
|
95
|
+
|
96
|
+
# Denormalization
|
93
97
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
98
|
+
puts ix.sender_name # prints sofa, no requests to CouchDB made
|
99
|
+
puts ix.sender.name # prints sofa, a single CouchDB request made
|
100
|
+
|
101
|
+
# Saving with conflicts
|
102
|
+
|
103
|
+
idup = i.dup
|
104
|
+
i.save!
|
105
|
+
idup.save # conflict printed
|
106
|
+
|
107
|
+
# Saving with and without validations
|
108
|
+
|
109
|
+
i = Invite.new :sender => sofa, :name => "daily show"
|
110
|
+
i.save! rescue :ok # save! throws an exception on validation failure or conflict
|
111
|
+
i.save # returns false rather than throwing an exception
|
112
|
+
puts i.errors.inspect # {:recipient=>"invalid:"}
|
113
|
+
|
114
|
+
i.validation_skip_list << :recipient # Any and all validations may be skipped
|
115
|
+
i.save # succeeds
|
99
116
|
|
100
|
-
# Simple views are auto created
|
101
|
-
Rating.all.sorted_by(:thumbs_up) { |q| q.key(2).limit(1) } # query params map directly to CouchDB
|
102
117
|
</code>
|
103
118
|
</pre>
|
104
119
|
|
@@ -106,80 +121,57 @@ h3. Paginating models
|
|
106
121
|
|
107
122
|
<pre>
|
108
123
|
<code>
|
109
|
-
# Controller
|
110
|
-
|
111
|
-
def action(page_params={})
|
112
|
-
u_id = @user._id
|
124
|
+
# Controller
|
113
125
|
|
114
|
-
|
115
|
-
|
116
|
-
|
126
|
+
def show(page_params={})
|
127
|
+
uid = @user._id
|
128
|
+
@invites = Invite.paginate_by_sender_name :startkey => [uid, {}],
|
129
|
+
:endkey => [uid], :descending => true, :limit => 5, :page_params => page_params
|
117
130
|
render
|
118
131
|
end
|
119
132
|
|
120
133
|
# In your view
|
121
134
|
|
122
|
-
<% @
|
123
|
-
<%=
|
135
|
+
<% @invites.each do |i| %>
|
136
|
+
<%= i.event_name %>
|
124
137
|
<% end %>
|
125
138
|
|
126
|
-
<%= link_to "prev", "/
|
127
|
-
<%= link_to "next", "/
|
139
|
+
<%= link_to "prev", "/invites/?#{@invites.prev_query}" if @invites.prev_query %>
|
140
|
+
<%= link_to "next", "/invites/?#{@invites.next_query}" if @invites.next_query %>
|
128
141
|
</code>
|
129
142
|
</pre>
|
130
143
|
|
131
|
-
|
132
|
-
|
133
|
-
<pre>
|
134
|
-
<code>
|
135
|
-
|
136
|
-
RelaxDB.paginate_view(page_params, "Letter", "by_letter_and_number", :letter, :number) do |p|
|
137
|
-
p.startkey(["b"]).endkey(["b", {}]).limit(2)
|
138
|
-
end
|
139
|
-
|
140
|
-
</code>
|
141
|
-
</pre>
|
142
|
-
|
143
|
-
A more illustrative example is listed in the .paginate_view spec in spec/paginate_spec.rb
|
144
|
+
More illustrative examples are listed in the .paginate_view spec in spec/paginate_spec.rb
|
144
145
|
|
145
146
|
h3. Creating views by hand
|
146
147
|
|
147
148
|
<pre>
|
148
149
|
<code>
|
149
150
|
$ cat view.js
|
150
|
-
function
|
151
|
-
if(doc.
|
152
|
-
emit(
|
151
|
+
function Invites_by_state-map(doc) {
|
152
|
+
if(doc.relaxdb_class === "Invite")
|
153
|
+
emit(doc.state, doc);
|
153
154
|
}
|
154
155
|
|
155
|
-
function
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
156
|
+
function Invites_by_state-reduce(keys, values, rereduce) {
|
157
|
+
if (rereduce) {
|
158
|
+
return sum(values);
|
159
|
+
} else {
|
160
|
+
return values.length;
|
161
|
+
}
|
160
162
|
}
|
161
163
|
$
|
162
164
|
|
163
165
|
RelaxDB::ViewUploader.upload("view.js")
|
164
|
-
RelaxDB.view
|
166
|
+
RelaxDB.view "Invites_by_state", :key => "accepted", :reduce => true
|
165
167
|
</code>
|
166
168
|
</pre>
|
167
169
|
|
168
170
|
h3. Visualise
|
169
171
|
|
170
|
-
|
171
|
-
<pre>
|
172
|
-
<code>
|
173
|
-
RelaxDB::GraphCreator.create
|
174
|
-
</code>
|
175
|
-
</pre>
|
176
|
-
|
177
|
-
Requires graphviz. Useful for visualising relationships between a limited number of document e.g. test fixtures. "Description and example":http://dev.strawberrydiva.com/visually_explore_couchdb/.
|
172
|
+
"Fuschia":http://github.com/paulcarey/fuschia/tree/master offers a web front end for visualising inter-document relationships.
|
178
173
|
|
179
174
|
h2. Incomplete list of limitations
|
180
175
|
|
181
|
-
* Error handling is not robust
|
182
176
|
* Destroying an object results in non transactional nullification of child/peer references
|
183
|
-
* Objects can talk to only one database at a time
|
184
|
-
* No caching is used. Although adding an LRU cache would be fairly straightforward, this hasn't been done as it's not yet clear what caching strategies will be most effective.
|
185
|
-
|
177
|
+
* Objects can talk to only one database at a time. Similarly for design docs.
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'spec/rake/spectask'
|
|
4
4
|
|
5
5
|
PLUGIN = "relaxdb"
|
6
6
|
NAME = "relaxdb"
|
7
|
-
GEM_VERSION = "0.
|
7
|
+
GEM_VERSION = "0.3.0"
|
8
8
|
AUTHOR = "Paul Carey"
|
9
9
|
EMAIL = "paul.p.carey@gmail.com"
|
10
10
|
HOMEPAGE = "http://github.com/paulcarey/relaxdb/"
|
@@ -28,7 +28,7 @@ spec = Gem::Specification.new do |s|
|
|
28
28
|
|
29
29
|
s.require_path = 'lib'
|
30
30
|
s.autorequire = PLUGIN
|
31
|
-
s.files = %w(LICENSE README.textile Rakefile) + Dir.glob("{docs,lib,spec}/**/*")
|
31
|
+
s.files = %w(LICENSE README.textile readme.rb Rakefile) + Dir.glob("{docs,lib,spec}/**/*")
|
32
32
|
end
|
33
33
|
|
34
34
|
Rake::GemPackageTask.new(spec) do |pkg|
|
@@ -50,3 +50,14 @@ Spec::Rake::SpecTask.new('spec:html') do |t|
|
|
50
50
|
t.spec_files = FileList['spec/**/*.rb']
|
51
51
|
t.spec_opts = ["--format", "html:docs/spec_results.html"]
|
52
52
|
end
|
53
|
+
|
54
|
+
desc "Supports atomic bulk save"
|
55
|
+
task :atomic_bulk_save_support do |t|
|
56
|
+
require 'lib/more/atomic_bulk_save_support.rb'
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Create base spec db"
|
60
|
+
task :create_base_db do
|
61
|
+
require 'spec/spec_helper'
|
62
|
+
create_base_db
|
63
|
+
end
|