Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
RelaxDB provides a simple Ruby interface to CouchDB
Ruby
tree: 5d522417cd

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
docs
lib
scratch
spec
.gitignore
.irbrc
CONTRIBUTORS
LICENSE
README.textile
Rakefile
readme.rb
relaxdb.gemspec

README.textile

What’s New?

  • 2009-10-31
    • Confirmed that the test suite passes against CouchDB 0.10.0
  • 2009-08-15
    • A few tweaks, patches and fixes push the version to 0.3.5, compatible with CouchDB 0.9.1 and the 0.10 branch.
    • The Rails error_messages_for helper is now supported. Thanks to Balint Erdi.
  • 2009-05-27
    • Added minimal support for data migrations. Although CouchDB’s nature removes the necessity for migrations, certain knowledge that all objects possess a particular property can simplify client logic. This desire for simplification is the rationale behind this change.
  • 2009-04-19
    • Defaults to taf2-curb, falling back to Net/HTTP if it taf2-curb can’t be loaded. Thanks to Fred Cheung.
    • For those interested in using RelaxDB with an ETag based cache, look here
  • 2009-03-31
    • RelaxDB 0.3 released – compatible with CouchDB 0.9.

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.

Note: While RelaxDB 0.3 is explicitly compatible with CouchDB 0.9, HEAD typically tracks CouchDB HEAD.

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

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_docs_by :sender_name
  view_docs_by :sender_id
  view_docs_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 = Invite.by_sender_name(:key => "sofa").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);
  }

  function Invites_by_state-reduce(keys, values, rereduce) {
    if (rereduce) {
      return sum(values);
    } else {
      return values.length;
    }
  }
  $

  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.
Something went wrong with that request. Please try again.