What’s New?
- 2010-05-04
- RelaxDB 0.5 released. This is a leaner version of RelaxDB with significant performance improvements and breaking changes. See the release notes for upgrading notes.
- 2010-04-10
- RelaxDB 0.4 released. Supports Ruby 1.9.1 and CouchDB 0.11.0.
- Auto-generated views no longer emit the document as a value by default
- Erlang view shorthand supported e.g. _sum and _count
- Added single query pagination
- Performance improvements
- Time.to_json fix. Thanks to Karmi
- Note: This release includes a number of breaking changes. Please see the release notes for upgrading notes.
For those interested in using RelaxDB with an ETag based cache, please see Fred Cheung’s work
Overview
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.
A few facilities are provided including pretty printing of GET requests and uploading of JavaScript views.
A basic merb plugin, merb_relaxdb is also available.
For more complete documentation take a look at docs/spec_results.html and the corresponding specs.
Details
Getting started
require 'rubygems'
require 'relaxdb'
RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "app"
RelaxDB.use_db "relaxdb_scratch"
RelaxDB.enable_view_creation # creates views when class definition is executed
RelaxDB::View.design_doc.save # save views to CouchDB after executing class definitions
Defining models
class User < RelaxDB::Document
property :name
end
class Invite < RelaxDB::Document
property :created_at
property :event_name
property :state, :default => "awaiting_response",
:validator => lambda { |s| %w(accepted rejected awaiting_response).include? s }
references :sender, :validator => :required
references :recipient, :validator => :required
property :sender_name,
:derived => [:sender, lambda { |p, o| o.sender.name } ]
view_by :sender_name # Emits 1 as the map value
view_docs_by :sender_id # Emits the doc as the map value
view_by :recipient_id, :created_at, :descending => true
def on_update_conflict
puts "conflict!"
end
end
Exploring models
# Saving objects
sofa = User.new(:name => "sofa").save!
futon = User.new(:name => "futon").save!
i = Invite.new :sender => sofa, :recipient => futon, :event_name => "CouchCamp"
i.save!
# Loading and querying
il = RelaxDB.load i._id
puts i == il # true
ir = Invite.by_sender_name "sofa"
puts i == ir # true
ix_ids = Invite.by_sender_name :key => "sofa"
ix = ix_ids.load!.first
puts i == ix # true
# Denormalization
puts ix.sender_name # prints sofa, no requests to CouchDB made
puts ix.sender.name # prints sofa, a single CouchDB request made
# Saving with conflicts
idup = i.dup
i.save!
idup.save # conflict printed
# Saving with and without validations
i = Invite.new :sender => sofa, :event_name => "CouchCamp"
i.save! rescue :ok # save! throws an exception on validation failure or conflict
i.save # returns false rather than throwing an exception
puts i.errors.inspect # prints {:recipient=>"invalid:"}
i.validation_skip_list << :recipient # Any and all validations may be skipped
i.save # succeeds
Paginating models
# Controller
def show(page_params={})
uid = @user._id
@invites = Invite.paginate_by_sender_name :startkey => [uid, {}],
:endkey => [uid], :descending => true, :limit => 5, :page_params => page_params
render
end
# In your view
<% @invites.each do |i| %>
<%= i.event_name %>
<% end %>
<%= link_to "prev", "/invites/?#{@invites.prev_query}" if @invites.prev_query %>
<%= link_to "next", "/invites/?#{@invites.next_query}" if @invites.next_query %>
More illustrative examples are listed in the .paginate_view spec in spec/paginate_spec.rb
Creating views by hand
$ cat view.js
function Invites_by_state-map(doc) {
if(doc.relaxdb_class === "Invite")
emit(doc.state, doc);
}
// Uses the CouchDB builtin to invoke an Erlang reduce fun
function Invites_by_state-reduce(keys, values, rereduce) {
_count
}
$
RelaxDB::ViewUploader.upload("view.js")
RelaxDB.view "Invites_by_state", :key => "accepted", :reduce => true
Migrations
$ cat 001_double.rb
RelaxDB::Migration.run Primitives do |p|
p.num *= 2
p
end
$ ruby -e 'RelaxDB::Migration.run_all Dir["./*.rb"]'
Visualise
Fuschia offers a web front end for visualising inter-document relationships.
Incomplete list of limitations
- Destroying an object results in non transactional nullification of child/peer references
- Objects can talk to only one database at a time. Similarly for design docs.