Ruby sittin' on the Couch
Upcoming SlideShare
Loading in...5
×
 

Ruby sittin' on the Couch

on

  • 10,143 views

The talk gives an introduction to CouchDB by showing the implementing of a simple Wiki Rails application that I have implemented in all the available Ruby frameworks I could find. It then compares ...

The talk gives an introduction to CouchDB by showing the implementing of a simple Wiki Rails application that I have implemented in all the available Ruby frameworks I could find. It then compares these frameworks and suggests which to use in order to get started with CouchDB and Ruby.

Statistics

Views

Total Views
10,143
Views on SlideShare
10,092
Embed Views
51

Actions

Likes
11
Downloads
110
Comments
0

2 Embeds 51

http://www.slideshare.net 49
http://123.237.242.114 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Ruby sittin' on the Couch Ruby sittin' on the Couch Presentation Transcript

  • * #sor09
  • Ruby sittin’ on the Couch
  • About me • Alexander Lang • Upstream Agile GmbH, Berlin programmer, owner • http://upstream-berlin.com
  • 11/52
  • Web development w/ Ruby
  • BDD
  • Buzzword Driven Development
  • TDD BDD “agile” Pair Programming * all the f****** time
  • About me • playing with CouchDB since 09/2008 • helped hack the CouchDB statistics module (with @janl) • wrote Couch Potato • working on smaller production apps
  • Who are you?
  • Who is in this room? • Chris Anderson • George Palmer • Paul Carey • Johan Sørensen • Cheah Chu Yeow/Carlos Villela
  • Who is in this room? • Chris Anderson (couchrest + couchapp) • George Palmer (couch_foo) • Paul Carey (relaxdb) • Johan Sørensen (couchbject) • Cheah Chu Yeow/Carlos Villela (activecouch)
  • Forget it!
  • Agenda • CouchDB introduction • The CouchDB example wiki • The frameworks • Conclusion: where to go from here? • Q &A
  • CouchDB introduction
  • What is CouchDB?
  • Apache Project so it has to be good
  • Document oriented Database
  • Store/read any JSON document
  • Powerful map/reduce views for querying* * we’ll see what that is
  • Why CouchDB?
  • Buzzword compliant
  • JavaScript
  • REST HTTP interface
  • JSON
  • Map/Reduce the thing that made Google rich
  • Fun !
  • No more SQL/Schema/ Migrations
  • JavaScript views instead
  • Simple!
  • HTTP interface
  • can use existing clients, libraries
  • It scales
  • just like Ruby :)
  • No locks, instead MVCC
  • integrated replication (yes, multi master)
  • use existing load balancer, proxies etc. HTTP ftw
  • and so on...
  • So how does Couch work?
  • JSON { _id: “some UUID”, _rev: “MVCC key”, title: “page one” body: “this is page one.”, tags: [“ruby”, “couchdb”, “sor09”] metadata: {created_at: “2009/03/28 06:34:00”, author: “alex”} }
  • HTTP API • POST ‘/my_db’, {my_json} • GET ‘/my_db/my_document_id’ • PUT ‘/my_db/my_document_id’, {new_json} • DELETE ‘/my_db/my_document_id’
  • Map/Reduce views • views are documens • provide a map (and optional reduce function) written in JavaScript • this creates an index over all documents • query that index via GET
  • Map/Reduce views { title: “page one”, tags: [“first”, important”] } { title: “page 2”, tags: [“funny”] }
  • Map/Reduce views { function(doc) { title: “page one”, emit(doc.title, doc.tags.length) tags: [“first”, important”] } } { title: “page 2”, tags: [“funny”] }
  • Map/Reduce views { function(doc) { title: “page one”, emit(doc.title, doc.tags.length) tags: [“first”, important”] } } key value { “page one” 2 title: “page 2”, tags: [“funny”] “page 2” 1 }
  • Map/Reduce views { function(doc) { title: “page one”, emit(doc.title, doc.tags.length) tags: [“first”, important”] } } key value { “page one” 2 title: “page 2”, tags: [“funny”] “page 2” 1 } function(keys, values) { return sum(values); }
  • Map/Reduce views { function(doc) { title: “page one”, emit(doc.title, doc.tags.length) tags: [“first”, important”] } } key value { “page one” 2 title: “page 2”, tags: [“funny”] “page 2” 1 } function(keys, values) { return sum(values); 3 }
  • Query a View
  • Query a View /my_db/_design/wiki/_view/tags_count
  • Query a View /my_db/_design/wiki/_view/tags_count /my_db/_design/wiki/_view/tags_count?reduce=false
  • Query a View /my_db/_design/wiki/_view/tags_count /my_db/_design/wiki/_view/tags_count?reduce=false /my_db/_design/wiki/_view/tags_count?limit=1
  • Query a View /my_db/_design/wiki/_view/tags_count /my_db/_design/wiki/_view/tags_count?reduce=false /my_db/_design/wiki/_view/tags_count?limit=1 /my_db/_design/wiki/_view/tags_count?key=page one
  • Query a View /my_db/_design/wiki/_view/tags_count /my_db/_design/wiki/_view/tags_count?reduce=false /my_db/_design/wiki/_view/tags_count?limit=1 /my_db/_design/wiki/_view/tags_count?key=page one
  • Query a View
  • Query a View ?key=‘page one’
  • Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’
  • Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’]
  • Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’] ?keys=[‘set’, ‘of’, ‘keys’]
  • Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’] ?keys=[‘set’, ‘of’, ‘keys’]
  • That is CouchDB (the basics) • upload documents via POST/PUT • read documents via GET • create indexes by providing map/reduce functions • query views via GET, pass keys + other options
  • The CouchDB example wiki
  • Example Wiki • ActiveRecord • RelaxDB • CouchRest • Couch Potato • ActiveCouch • CouchOject • CouchFoo • CouchApp
  • • a wiki example app implemented in all frameworks I could find • + in ActiveRecord for comparison DISCLAIMER: implementations are insecure, have bugs and aren’t meant for production at all
  • http://github.com/langalex/couchdb_example_wiki
  • Example Wiki • a few simple features • create a page • add new pages by clicking on a CamelCase link • list of pages
  • Example Wiki • and a few special cases • keep history of each page, browse old versions • statistics: count occurrences of all words in all pages
  • Example Wiki • creating pages is easy • versioning, statistics harder • views are the source of CouchDB’s power
  • let me show you how it works Example Wiki
  • ActiveRecord Wiki
  • routes.rb map.resources :pages do |pages| pages.resources :versions end map.resources :statistics map.root :controller => quot;pagesquot;, :action => 'show'
  • page.rb class Page < ActiveRecord::Base acts_as_versioned def to_param title end end
  • pages/show.html.erb <%= <%= simple_format linkify(@page.body) %> simple_format linkify(@page.body) %> def linkify(text) text.gsub(/([A-Z][a-z]+([A-Z][a-z]+)+)/) do link_to($1, page_path($1)) end end replace CamelCase words with links to #show
  • pages_controller.rb def show @page = Page.first unless params[:id] @page ||= Page.find_by_title params[:id] redirect_to new_page_path(:title => params[:id]) unless @page end redirect to #new if no page found
  • statistics def self.word_counts Page.all.map(&:body).join(quot; quot;).split(/s +/).grep(/w+/i).inject(Hash.new(0)) do |res, word| res[word] += 1 res end end
  • schema.rb create_table quot;page_versionsquot;, :force => true do |t| t.integer quot;page_idquot; t.integer quot;versionquot; t.text quot;bodyquot; t.datetime quot;created_atquot; t.datetime quot;updated_atquot; end create_table quot;pagesquot;, :force => true do |t| t.string quot;titlequot; t.text quot;bodyquot; t.datetime quot;created_atquot; t.datetime quot;updated_atquot; t.integer quot;versionquot;, :default => 1 end
  • AR Summary • Page has_many PageVersions • ugly schema with duplicated table • acts_as_versioned does the magic for us • statistics - ?!?
  • CouchDB Wiki How does it work?
  • The Page { _id: “89765”, _rev: “lb7tlb”, type: “Page”, title: “page one”, body: “this is page one” }
  • Page Versions { _id: “765”, _rev: “lhjb97” type: “PageVersion”, title: “page one”, body: “this is page one”, version: 23, page_id: “89765” }
  • All in one namespace { { _id: “ 89765”, { _id: “ 9753”, _id: “97865”, _rev: “lb7tlb”, _rev: “lb7tlb”, _rev: “lhjb97” type: “Page”, type: “PageVersion”, type: “Page”, title: “page one”, title: “page one”, title: “page one”, body: “this is page one”, body: “this is page one” version: 23, body: “this is page one” page_id: “89765” } { } } _id: “6437”, _rev: “lhjb97” { type: “PageVersion”, _id: “6367”, title: “page one”, { { _rev: “lhjb97” body: “this is page one”, type: “PageVersion”, _id: “ 76538”, _id: “ 8975763”, version: 23, title: “page one”, page_id: “89765” _rev: “lb7tlb”, } _rev: “lb7tlb”, body: “this is page one”, version: 23, type: “Page”, type: “Page”, page_id: “89765” title: “page one”, title: “page one”, } body: “this is page one” body: “this is page one” } }
  • Finding pages function(doc) { key value if(doc.type == “Page”) { emit(doc.title, doc); “page 2” {...} } } “page one” {...}
  • Finding Page Versions function(doc) { if(doc.type == “PageVersion”) { emit([doc.page_id, doc.version], doc); } } key value [“ladsb7gi”, 1] {...} [“ladsb7gi”, 2] {...} [“nloh79d”, 1] {...}
  • Finding Page Versions key value [“ladsb7gi”, 1] {...} [“ladsb7gi”, 2] {...} [“nloh79d”, 1] {...} GET /mydb/_design/page_versions/_view/by_page? startkey=[“ladsb7gi”, 1]&endkey=[“ ladsb7gi”,{}]
  • Counting Words - Map
  • Counting Words - Map {body: “page one”} {body: “page 2”}
  • Counting Words - Map function(doc) { if(doc.type == 'Page') { var words = doc.body.split(/W/); words.forEach(function(word) { if (word.length > 0) emit(word, 1); });}} {body: “page one”} {body: “page 2”}
  • Counting Words - Map function(doc) { if(doc.type == 'Page') { var words = doc.body.split(/W/); words.forEach(function(word) { if (word.length > 0) emit(word, 1); });}} key value “page” 1 {body: “page one”} “page” 1 {body: “page 2”} “one” 1 “2” 1
  • Counting Words - reduce
  • Counting Words - reduce key value “page” 1 “page” 1 “one” 1 “2” 1
  • Counting Words - reduce key value “page” 1 “page” 1 “one” 1 “2” 1 function(keys, values) { return sum(values); }
  • Counting Words - reduce key value “page” 1 “page” 1 “one” 1 “2” 1 key value “page” 2 function(keys, values) { return sum(values); “one” 1 } “2” 1
  • CouchDB summary • no schema, arbitrary documents in one namespace • still use foreign keys to implement associations • create views instead of join tables • views! views! views!
  • Show us the frameworks already
  • What’s the framework’s job?
  • What’s the framework’s job? ActiveRecord
  • What’s the framework’s job? ActiveRecord • map objects to relations and back • do the whole SQL thing
  • What’s the framework’s job? ActiveRecord schema attribute auto tracking detection serialized attributes pagination groups JOINS unique lazy loading caching • map objects to relations and back count • do the whole SQL thing HABTM create or update? has_many :through conditions eager association loading connection DDL :dependent => :destroy
  • What’s the framework’s job? ActiveRecord Page.all :include => :tags SELECT pages.id ... JOIN tags ON ... WHERE ... ... [10 more lines] ...
  • What’s the framework’s job? ActiveRecord big fat abstraction SQL
  • What’s the framework’s job? ActiveCouchRelaxFooDBObject
  • What’s the framework’s job? ActiveCouchRelaxFooDBObject <#134 Page title=”page one”> {type: “Page”, title: “page one”}
  • What’s the framework’s job? ActiveCouchRelaxFooDBObject POST /mydb/, {title: “page one”, type: “Page”} GET /mydb/page-one
  • What’s the framework’s job? ActiveCouchRelaxFooDBObject skinny CouchDB abstraction
  • ActiveCouchRelaxFooDBObject ... are not *that* important
  • The frameworks CouchPoato RelaxDB CouchObject ActiveCouch CouchRest CouchFoo
  • CouchRest • 2 in 1 • foundation for most other frameworks
  • Low level part • relatively thin layer on top of RestClient • store and retrieve JSON structures • query views the Couch way
  • Class mapping part • map Ruby classes to JSON documents • CRUD • declarative views with CouchDB semantics
  • RelaxDB • CRUD + very basic associations • automatic view generation via view_by • a bit of support for custom views via RelaxDB::View • CouchDB like view API
  • Couch Object • last updated in 2007 • pretty low level - okay for learning the details of Couch the DIY way • no update, no properties, no automatic view creation
  • Couch Potato • CRUD, associations + JSON mapping • built-in acts_as_versioned - 60 LOC but doing it by hand only requires 7 • ViewQuery class for creating/querying custom views • AR like finders
  • CouchFoo • takes ActiveRecord and makes it work with CouchDB • if you (have) to migrate an AR app... • doesn’t give you the power of CouchDB
  • ActiveCouch • “Object Relational Mapper for [..] CouchDB” • “Since, the Rubyists here at Wego are already very familiar with ActiveRecord semantics, care has been taken to ensure that ActiveCouch resembled it in many ways.”
  • ActiveCouch • one database per model • View class to upload #@!? views via Rake task • no support for custom views
  • Use the source
  • CouchRest Wiki
  • Page CRUD class Page < CouchRest::ExtendedDocument update_callback :after, :create_version property :title can’t infer attributes from table property :body view_by :title auto-generate simple views def create_version PageVersion.new(:page_id => id, :body => @body_was, :version => versions_count + 1).save! end create version on update end
  • PageVersion class PageVersion < CouchRest::ExtendedDocument property :body property :version property :page_id view_by [:page_id, :version] end
  • Word Count class WordCount < CouchRest::ExtendedDocument view_by :all, :map => quot;function(doc) { if(doc['couchrest-type'] == 'Page') { var words = doc.body.split(/W/); words.forEach(function(word) { if (word.length > 0) emit(word, 1); }); } }quot;, :reduce => quot;function(keys, values) { return sum(values); }quot; end
  • Querying Views Page.by_title Page.by_created_at(:limit => 1) PageVersion.get params[:id] Page.by_title(:key => title, :limit => 1).first WordCount.all
  • One more thing
  • CouchApp • serve entire apps directly from CouchDB • just JSON, HTML & JavaScript
  • What is CouchApp • bunch of Ruby Python scripts to help with development/deployment • data, CouchDB views, validations, shows, lists and assets in one database
  • AJAX apps • serve HTML, CSS as assets • do all the work in JavaScript • in the browser: GET from couch and append HTML, POST form data to CouchDB to update documents • in CouchDB: views to retrieve, compute data, validations
  • “real” apps • lists • shows • server HTML, XML etc. from CouchDB
  • Conclusion
  • DON’T use ActiveCouch unless you understand why it is the way it is or Couch Potato I’ll break all of its bones and APIS
  • DON’T or CouchFoo unless you want more power than AR can give you
  • Conclusion forgetting about the ActiveRecord way is more important than what framework
  • Conclusion think in documents & views, not in records and associations
  • Conclusion CouchDB is not about finding your records, it’s about clever map/reduce to get exactly what you want
  • What you want is a thin abstraction CouchDB semantics, not ActiveRecord start simple with CouchREST or RelaxDB
  • and relax... time to relax
  • Resources • The CouchDB book http://books.couchdb.org/relax/ • jchris CouchApp talk http://jchrisa.net/drl/CouchDB%20Talk%20at%20Vidoop/ VidoopCouchTalk.pdf • janl CouchDB talk http://www.slideshare.net/bbcwebdev/introduction-into-couchdb- jan-lehnardt-presentation • The example wiki http://github.com/langalex/couchdb_example_wiki/ • http://couch.io support, training, hosting, development
  • Questions? • Email: alex@upstream-berlin.com • Twitter: @langalex time to relax