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 CHANGED
@@ -1,44 +1,12 @@
1
1
  h3. What's New?
2
2
 
3
- * 02/02/09 - mostly fixes and tweaks for CouchDB trunk (tested against revision 0.9.0a740000). This includes a couple of breaking API changes - count became limit, 409 rather than 412 is returned on document update conflict, and '/' in design docs is treated differently.
3
+ RelaxDB 0.3 released - compatible with CouchDB 0.9.
4
4
 
5
- * Potentially breaking change. Skipping validations is now done by adding attribute symbols to an object's list rather than passing them to @save@. For example @my_obj.validation_skip_list << :foo@. This offers per object granularity over validations when working with bulk_save.
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 the mighty CouchDB. Combined with the schema free nature of CouchDB, RelaxDB's current strength lies in quick prototyping of object models.
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
- h2. Details
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
- RelaxDB.configure :host => "localhost", :port => 5984
56
- RelaxDB.use_db "scratch"
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
- class Post < RelaxDB::Document
72
- property :created_at
73
- property :contents
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
- belongs_to :writer
76
- has_many :ratings, :class => "Rating"
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
- paul = Writer.new(:name => "paul").save
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
- post = Post.new(:contents => "foo")
95
- paul.posts << post # post writer is set and post is saved
96
- post.created_at # right now
97
- paul.ratings << Rating.new(:thumbs_up => 3, :post => post) # returns false as rating fails validation
98
- paul.ratings.size # 0
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 (merb-action-args used for extracting view_params)
110
-
111
- def action(page_params={})
112
- u_id = @user._id
124
+ # Controller
113
125
 
114
- @posts = Post.paginate_by(page_params, :writer_id, :created_at) do |p|
115
- p.startkey([u_id, {}]).endkey([u_id]).descending(true).limit(5)
116
- end
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
- <% @posts.each do |p| %>
123
- <%= p.contents %>
135
+ <% @invites.each do |i| %>
136
+ <%= i.event_name %>
124
137
  <% end %>
125
138
 
126
- <%= link_to "prev", "/posts/?#{@posts.prev_query}" if @posts.prev_query %>
127
- <%= link_to "next", "/posts/?#{@posts.next_query}" if @posts.next_query %>
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
- h3. Paginating over your own views
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 Writer-allnames-map(doc) {
151
- if(doc.class == "Writer")
152
- emit(null, doc.name);
151
+ function Invites_by_state-map(doc) {
152
+ if(doc.relaxdb_class === "Invite")
153
+ emit(doc.state, doc);
153
154
  }
154
155
 
155
- function Writer-allnames-reduce(keys, values) {
156
- var allnames = "";
157
- for(var i = 0; i < values.length; i++)
158
- allnames += values[i];
159
- return allnames;
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("Writer", "allnames") # paul
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
- Create an object graph by simply running
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.2.8"
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