Ruby sittin' on the Couch

  • 5,949 views
Uploaded on

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.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
5,949
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
111
Comments
0
Likes
11

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. * #sor09
  • 2. Ruby sittin’ on the Couch
  • 3. About me • Alexander Lang • Upstream Agile GmbH, Berlin programmer, owner • http://upstream-berlin.com
  • 4. 11/52
  • 5. Web development w/ Ruby
  • 6. BDD
  • 7. Buzzword Driven Development
  • 8. TDD BDD “agile” Pair Programming * all the f****** time
  • 9. About me • playing with CouchDB since 09/2008 • helped hack the CouchDB statistics module (with @janl) • wrote Couch Potato • working on smaller production apps
  • 10. Who are you?
  • 11. Who is in this room? • Chris Anderson • George Palmer • Paul Carey • Johan Sørensen • Cheah Chu Yeow/Carlos Villela
  • 12. 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)
  • 13. Forget it!
  • 14. Agenda • CouchDB introduction • The CouchDB example wiki • The frameworks • Conclusion: where to go from here? • Q &A
  • 15. CouchDB introduction
  • 16. What is CouchDB?
  • 17. Apache Project so it has to be good
  • 18. Document oriented Database
  • 19. Store/read any JSON document
  • 20. Powerful map/reduce views for querying* * we’ll see what that is
  • 21. Why CouchDB?
  • 22. Buzzword compliant
  • 23. JavaScript
  • 24. REST HTTP interface
  • 25. JSON
  • 26. Map/Reduce the thing that made Google rich
  • 27. Fun !
  • 28. No more SQL/Schema/ Migrations
  • 29. JavaScript views instead
  • 30. Simple!
  • 31. HTTP interface
  • 32. can use existing clients, libraries
  • 33. It scales
  • 34. just like Ruby :)
  • 35. No locks, instead MVCC
  • 36. integrated replication (yes, multi master)
  • 37. use existing load balancer, proxies etc. HTTP ftw
  • 38. and so on...
  • 39. So how does Couch work?
  • 40. 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”} }
  • 41. 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’
  • 42. 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
  • 43. Map/Reduce views { title: “page one”, tags: [“first”, important”] } { title: “page 2”, tags: [“funny”] }
  • 44. Map/Reduce views { function(doc) { title: “page one”, emit(doc.title, doc.tags.length) tags: [“first”, important”] } } { title: “page 2”, tags: [“funny”] }
  • 45. 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 }
  • 46. 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); }
  • 47. 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 }
  • 48. Query a View
  • 49. Query a View /my_db/_design/wiki/_view/tags_count
  • 50. Query a View /my_db/_design/wiki/_view/tags_count /my_db/_design/wiki/_view/tags_count?reduce=false
  • 51. 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
  • 52. 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
  • 53. 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
  • 54. Query a View
  • 55. Query a View ?key=‘page one’
  • 56. Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’
  • 57. Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’]
  • 58. Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’] ?keys=[‘set’, ‘of’, ‘keys’]
  • 59. Query a View ?key=‘page one’ ?startkey=‘page 1’&endkey=’page 999’ ?key=[‘composite’, ‘key’] ?keys=[‘set’, ‘of’, ‘keys’]
  • 60. 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
  • 61. The CouchDB example wiki
  • 62. Example Wiki • ActiveRecord • RelaxDB • CouchRest • Couch Potato • ActiveCouch • CouchOject • CouchFoo • CouchApp
  • 63. • 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
  • 64. http://github.com/langalex/couchdb_example_wiki
  • 65. Example Wiki • a few simple features • create a page • add new pages by clicking on a CamelCase link • list of pages
  • 66. Example Wiki • and a few special cases • keep history of each page, browse old versions • statistics: count occurrences of all words in all pages
  • 67. Example Wiki • creating pages is easy • versioning, statistics harder • views are the source of CouchDB’s power
  • 68. let me show you how it works Example Wiki
  • 69. ActiveRecord Wiki
  • 70. routes.rb map.resources :pages do |pages| pages.resources :versions end map.resources :statistics map.root :controller => quot;pagesquot;, :action => 'show'
  • 71. page.rb class Page < ActiveRecord::Base acts_as_versioned def to_param title end end
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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
  • 76. AR Summary • Page has_many PageVersions • ugly schema with duplicated table • acts_as_versioned does the magic for us • statistics - ?!?
  • 77. CouchDB Wiki How does it work?
  • 78. The Page { _id: “89765”, _rev: “lb7tlb”, type: “Page”, title: “page one”, body: “this is page one” }
  • 79. Page Versions { _id: “765”, _rev: “lhjb97” type: “PageVersion”, title: “page one”, body: “this is page one”, version: 23, page_id: “89765” }
  • 80. 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” } }
  • 81. Finding pages function(doc) { key value if(doc.type == “Page”) { emit(doc.title, doc); “page 2” {...} } } “page one” {...}
  • 82. Finding Page Versions function(doc) { if(doc.type == “PageVersion”) { emit([doc.page_id, doc.version], doc); } } key value [“ladsb7gi”, 1] {...} [“ladsb7gi”, 2] {...} [“nloh79d”, 1] {...}
  • 83. Finding Page Versions key value [“ladsb7gi”, 1] {...} [“ladsb7gi”, 2] {...} [“nloh79d”, 1] {...} GET /mydb/_design/page_versions/_view/by_page? startkey=[“ladsb7gi”, 1]&endkey=[“ ladsb7gi”,{}]
  • 84. Counting Words - Map
  • 85. Counting Words - Map {body: “page one”} {body: “page 2”}
  • 86. 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”}
  • 87. 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
  • 88. Counting Words - reduce
  • 89. Counting Words - reduce key value “page” 1 “page” 1 “one” 1 “2” 1
  • 90. Counting Words - reduce key value “page” 1 “page” 1 “one” 1 “2” 1 function(keys, values) { return sum(values); }
  • 91. 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
  • 92. 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!
  • 93. Show us the frameworks already
  • 94. What’s the framework’s job?
  • 95. What’s the framework’s job? ActiveRecord
  • 96. What’s the framework’s job? ActiveRecord • map objects to relations and back • do the whole SQL thing
  • 97. 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
  • 98. What’s the framework’s job? ActiveRecord Page.all :include => :tags SELECT pages.id ... JOIN tags ON ... WHERE ... ... [10 more lines] ...
  • 99. What’s the framework’s job? ActiveRecord big fat abstraction SQL
  • 100. What’s the framework’s job? ActiveCouchRelaxFooDBObject
  • 101. What’s the framework’s job? ActiveCouchRelaxFooDBObject <#134 Page title=”page one”> {type: “Page”, title: “page one”}
  • 102. What’s the framework’s job? ActiveCouchRelaxFooDBObject POST /mydb/, {title: “page one”, type: “Page”} GET /mydb/page-one
  • 103. What’s the framework’s job? ActiveCouchRelaxFooDBObject skinny CouchDB abstraction
  • 104. ActiveCouchRelaxFooDBObject ... are not *that* important
  • 105. The frameworks CouchPoato RelaxDB CouchObject ActiveCouch CouchRest CouchFoo
  • 106. CouchRest • 2 in 1 • foundation for most other frameworks
  • 107. Low level part • relatively thin layer on top of RestClient • store and retrieve JSON structures • query views the Couch way
  • 108. Class mapping part • map Ruby classes to JSON documents • CRUD • declarative views with CouchDB semantics
  • 109. 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
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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.”
  • 114. ActiveCouch • one database per model • View class to upload #@!? views via Rake task • no support for custom views
  • 115. Use the source
  • 116. CouchRest Wiki
  • 117. 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
  • 118. PageVersion class PageVersion < CouchRest::ExtendedDocument property :body property :version property :page_id view_by [:page_id, :version] end
  • 119. 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
  • 120. 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
  • 121. One more thing
  • 122. CouchApp • serve entire apps directly from CouchDB • just JSON, HTML & JavaScript
  • 123. What is CouchApp • bunch of Ruby Python scripts to help with development/deployment • data, CouchDB views, validations, shows, lists and assets in one database
  • 124. 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
  • 125. “real” apps • lists • shows • server HTML, XML etc. from CouchDB
  • 126. Conclusion
  • 127. 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
  • 128. DON’T or CouchFoo unless you want more power than AR can give you
  • 129. Conclusion forgetting about the ActiveRecord way is more important than what framework
  • 130. Conclusion think in documents & views, not in records and associations
  • 131. Conclusion CouchDB is not about finding your records, it’s about clever map/reduce to get exactly what you want
  • 132. What you want is a thin abstraction CouchDB semantics, not ActiveRecord start simple with CouchREST or RelaxDB
  • 133. and relax... time to relax
  • 134. 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
  • 135. Questions? • Email: alex@upstream-berlin.com • Twitter: @langalex time to relax