Skip to content

Instantly share code, notes, and snippets.

@hallettj
Created August 4, 2009 19:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hallettj/161472 to your computer and use it in GitHub Desktop.
Save hallettj/161472 to your computer and use it in GitHub Desktop.
require 'couch_foo'
## Setup code. Put this in an initializer to set up a database connection.
CouchFoo::Base.set_database(:host => "http://localhost:5984",
:database => "couch_foo_example")
# Uncomment this line if you are using Rails
#CouchFoo::Base.logger = Rails.logger
## Examples of models
class User < CouchFoo::Base
property :name, String
property :handle, String
property :email, String
property :created_at, Time
# Type declarations are encouraged but are now required. Properties can be
# given with no declared type. Values for those properties will be
# serialized and stored inside a string value.
has_many :posts
end
class Post < CouchFoo::Base
property :title, String
property :text, String
property :user_id, String
property :created_at, Time
belongs_to :user
# Views can be defined explicitly. This map function creates a view that
# indexes documents by content length.
view :length, <<-EOF
function(doc) {
if (doc.ruby_class == "#{self.class.name}" && doc.text) {
emit(doc.text.length, doc);
}
}
EOF
end
## couch_foo in action
user = User.create(:name => "Jesse", :handle => "hallettj")
# Associations are handled transparently.
user.posts.create(:title => "First Post", :text => "Hello, world!")
# Views are created on demand. This query implicitly creates a 'title' view.
post = Post.find(:first, :conditions => { :title => "First Post" })
# Elegant API for retrieving multiple documents.
short_posts = Post.find(:all, :conditions => { :length => 0..140 })
long_posts = Post.find(:all, :use_key => :length, :startkey => 141)
# All of the views created through a given class are stored in one design
# document with a matching name. Thus model classes map to design documents.
# Supports transactions via CouchDB's bulk API
Post.bulk_save_default = true
(2..1000).each do |i|
Post.create(:user => user, :title => "Post ##{i}", :text => Array.new(i, "post").join(" "))
end
Post.database.commit
Post.bulk_save_default = false
# Sort based on a column other than the lookup key. Normally query results are
# sorted by the lookup key. In this case CouchFoo 'cheats' by creating a view
# with keys that are a composite of two fields.
Post.find(:all, :use_key => [:title, :user_id], :conditions => { :user_id => user.id })
# Retrieve records that match a set of discrete keys. This requires CouchDB >= 0.9.
User.find(:all, :conditions => { :handle => ['hallettj', 'igalko', 'pdxruby'] })
## Examples copied from the CouchRest examples directory.
## examples/model/example.rb
require 'couchrest'
def show obj
puts obj.inspect
puts
end
## Setup code. Put this in an initializer to set up a database connection.
COUCHDB_SERVER = CouchRest.new
COUCHDB_SERVER.default_database = 'couchrest-extendeddoc-example'
## Examples of CouchRest models.
class Author < CouchRest::ExtendedDocument
use_database COUCHDB_SERVER.default_database
property :name
end
class Post < CouchRest::ExtendedDocument
use_database COUCHDB_SERVER.default_database
property :title
property :body
property :author, :cast_as => 'Author'
timestamps!
end
class Comment < CouchRest::ExtendedDocument
use_database COUCHDB_SERVER.default_database
property :commenter, :cast_as => 'Author'
timestamps!
def post= post
self["post_id"] = post.id
end
def post
Post.get(self['post_id']) if self['post_id']
end
end
## CouchRest models in action.
puts "Act I: CRUD"
puts "Create an author."
quentin = Author.new("name" => "Quentin Hazel")
puts "Create a new post."
post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
puts "Add the author to the post."
post.author = quentin
puts "Save the post."
post.save
puts "Load the post."
reloaded = Post.get(post.id)
puts "\nAdd some comments to the post."
comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
# TODO - maybe add some magic here?
comment_one.post = post
comment_two.post = post
comment_three.post = post
comment_one.save
comment_two.save
comment_three.save
puts "We can load a post through its comment (no magic here)."
show post = comment_one.post
puts "\nLet's save an author to her own document."
jane = comment_two['commenter']
jane.save
puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
show comment_two
puts "Of course, we'd better remember to save it."
comment_two.save
puts "Oooh, denormalized... feel the burn!"
puts "Act II: Views"
puts "Let's find all the comments that go with our post."
puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
class Comment
view_by :post_id
end
comments = Comment.by_post_id :key => post.id
puts "That was too easy."
puts "We can even wrap it up in a finder on the Post class."
class Post
def comments
Comment.by_post_id :key => id
end
end
puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
puts
puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
## See more examples at:
## http://jchrisa.net/drl/_design/sofa/_show/post/couchrest__model___orm__the_cou
Overview of Object-Document Mapping libraries for Ruby and CouchDB.
require 'couch_potato'
CouchPotato::Config.database_name = 'odm_overview'
class Address
attr_accessor :name, :street, :city, :state, :zip
def initialize(attrs={})
attrs.each do |k,v|
setter = k.to_s + '='
send(k.to_s + '=', v) if respond_to? setter
end
end
end
class LineItem
attr_accessor :product_code, :qty, :price_per_unit
def initialize(attrs={})
attrs.each do |k,v|
setter = k.to_s + '='
send(k.to_s + '=', v) if respond_to? setter
end
end
end
## The document class
class Invoice
include CouchPotato::Persistence
property :shipping_address #, :class => Address
property :billing_address #, :class => Address
property :ordered_at, :type => Time
property :items
property :tax
property :shipping
property :total
validates_presence_of :ordered_at
view :all, :key => :ordered_at
# view(:total_sales, :type => :aggregate,
# :key => :ordered_at, :sum => 'items.qty * items.price_per_unit')
# view(:average_sale, :type => :aggregate,
# :key => :ordered_at, :average => 'items.qty * items.price_per_unit')
view(:total_sales, :type => :raw, :map => %q{
function(doc) {
var i;
if (doc.items) {
for (i = 0; i < doc.items.length; i += 1) {
emit(doc.ordered_at, doc.items[i].qty * doc.items[i].price_per_unit);
}
}
}
},
:reduce => %q{
function(keys, values, rereduce) {
return sum(values);
}
}, :results_filter => lambda { |results| results['rows'].map { |r| r['value'] } })
view(:average_sale, :type => :raw, :map => %q{
function(doc) {
var i, total = 0;
if (doc.items) {
for (i = 0; i < doc.items.length; i += 1) {
total += doc.items[i].qty * doc.items[i].price_per_unit;
}
emit(doc.ordered_at, total);
}
}
},
:reduce => %q{
function(keys, values, rereduce) {
return sum(values) / values.length;
}
}, :results_filter => lambda { |results| results['rows'].map { |r| r['value'] } })
end
# Initialize views
CouchPotato.database.view Invoice.all
CouchPotato.database.view Invoice.total_sales
## Creating a document
invoice = Invoice.new(
:shipping_address => Address.new(:name => 'Joe Smith',
:street => '123 Main St',
:city => 'Anytown',
:state => 'CA',
:zip => '12345'),
:billing_address => Address.new(:name => 'Joe Smith',
:street => '123 Main St',
:city => 'Anytown',
:state => 'CA',
:zip => '12345'),
:ordered_at => Time.now.utc,
:items => [
LineItem.new(:product_code => 'AX5718', :qty => 1, :price_per_unit => 5.00),
LineItem.new(:product_code => 'BB9388', :qty => 3, :price_per_unit => 2.00),
],
:tax => 0.00,
:shipping => 10.00,
:total => 21.00
)
CouchPotato.database.save_document invoice
## Looking up a single document
invoice_copy = CouchPotato.database.load_document invoice._id
## Querying up documents
invoices = CouchPotato.database.view Invoice.all(:key => (Time.now - 300000)..(Time.now),
:descending => true)
## Aggregations
count = CouchPotato.database.view Invoice.all(:reduce => true)
puts "%i invoices" % count
sales = CouchPotato.database.view Invoice.total_sales(:reduce => true)
puts "$%0.2f total sales" % sales
average = CouchPotato.database.view Invoice.average_sale(:reduce => true)
puts "$%0.2f average sale" % average
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment