What’s New?

  • 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.