Developer Data Modeling Mistakes: From Postgres to NoSQL
A cool, clear drink of Ruby object persistence
1. Aqua A cool, clear drink of Ruby object persistence Kane Baccigalupi RubyConf 09
2. Aqua A cool, clear drink of Ruby object persistence Kane Baccigalupi RubyConf ‘09
3. Aqua A cool, clear drink of Ruby object persistence Got unemployment? Relax with CouchDB Kane Baccigalupi RubyConf ‘09
4. Aqua A cool, clear drink of Ruby object persistence ORMs are great, but normalization is expensive. # DataMapper class Mammal include DataMapper::Resource property :my_id, Serial has n, :legs end # ActiveRecord class Bird < ActiveRecord::Base # properties defined by migration has_many, :legs end Kane Baccigalupi RubyConf ‘09
5. Aqua A cool, clear drink of Ruby object persistence CouchDB + Ruby ~= CouchRest # CouchRest class Reptile < CouchRest::ExtendedDocument use_database MY_DB unique_id :my_id property :legs # a collection! end Kane Baccigalupi RubyConf ‘09
6. Aqua A cool, clear drink of Ruby object persistence CouchRest is different from ORMs because: It allows collections Models are hashes Instance variables are discarded Kane Baccigalupi RubyConf ‘09
7. Aqua A cool, clear drink of Ruby object persistence What defines Ruby object state? Instance variables @my_variable Fundamental data Hash key-values, Array values, etc. Kane Baccigalupi RubyConf ‘09
8. Aqua A cool, clear drink of Ruby object persistence Database abstractions focus on databases # DataMapper class Mammal include DataMapper::Resource property :my_id, Serial has n, :legs end # ActiveRecord class Bird < ActiveRecord::Base # properties defined by schema has_many, :legs end # CouchRest class Reptile < CouchRest::ExtendedDocument use_database MY_DB unique_id :my_id property :legs # a collection! end Kane Baccigalupi RubyConf ‘09
9. Aqua A cool, clear drink of Ruby object persistence Aqua’s goal: Focus on objects object.commit! Kane Baccigalupi RubyConf ‘09
10. Aqua A cool, clear drink of Ruby object persistence Because Ruby is awesome class Event < Range attr_accessor :name end rubyconf = Event.new(Date.parse('11/19/2009'), Date.parse('11/21/2009')) rubyconf.name = 'RubyConf 09' rubyconf.include?(Date.parse('11/20/2009')) # true Kane Baccigalupi RubyConf ‘09
11. Aqua A cool, clear drink of Ruby object persistence How does Aqua work? Kane Baccigalupi RubyConf ‘09
12. Aqua A cool, clear drink of Ruby object persistence Just add Aqua class User aquatic # ... end Kane Baccigalupi RubyConf ‘09
13. Aqua A cool, clear drink of Ruby object persistence Just add Aqua class User aquatic # ... end user = User.new # ... More stuff happens to the user # saving an object user.commit! # commit without the ! also works but raises no errors. Kane Baccigalupi RubyConf ‘09
14. Aqua A cool, clear drink of Ruby object persistence Behind the scene user.commit! Kane Baccigalupi RubyConf ‘09
15. Aqua A cool, clear drink of Ruby object persistence Behind the scene serialization user.commit! { "class"=>"User", "ivars"=>{ "@username"=>"kane", "@email"=>"baccigalupi@gmail.com", "@password"=>"secret" } } Kane Baccigalupi RubyConf ‘09
16. Aqua A cool, clear drink of Ruby object persistence Behind the scene serialization user.commit! { "class"=>"User", "ivars"=>{ "@username"=>"kane", "@email"=>"baccigalupi@gmail.com", "@password"=>"secret" } } data post Kane Baccigalupi RubyConf ‘09
17. Aqua A cool, clear drink of Ruby object persistence Objects ~= Documents, && Documents ~= Hashes # YAML for a Ruby User object --- &id001 !ruby/object:User email: baccigalupi@gmail.com password: secret username: kane # Aqua Serialization for same object { "class"=>"User", "ivars"=>{ "@username"=>"kane", "@email"=>"baccigalupi@gmail.com", "@password"=>"secret" } } Kane Baccigalupi RubyConf ‘09
18. Aqua A cool, clear drink of Ruby object persistence Sometimes state should not hang around. There is a method for that. class User aquatic attr_accessor :username, :email, :password hide_attributes :password end Kane Baccigalupi RubyConf ‘09
19. Aqua A cool, clear drink of Ruby object persistence Going deeper with embedded objects … class Address # not an aquatic object, just plain ruby attr_accessor :name, :street, :city, :state, :zip end address = Address.new # . . . user = User.new user.addresses = [ address ] Kane Baccigalupi RubyConf ‘09
20. Aqua A cool, clear drink of Ruby object persistence Ordinary objects get serialized inside aquatic objects { "class"=>"User", "ivars"=>{ "@addresses"=>{ "class"=>"Array", "init"=>[{ "class"=>"Address", "ivars"=>{ "@city"=>"San Francisco", "@name"=>"work", "@street"=>"P0 Box 58", "@state"=>"94102" } }] }, "@username"=>"kane", "@email"=>"baccigalupi@gmail.com" } } Kane Baccigalupi RubyConf ‘09
21. Aqua A cool, clear drink of Ruby object persistence Embedded aquatic objects are saved by reference. { "class"=>"User", "ivars"=>{ # ... "@friends"=>{ "class"=>"Array", "init"=>[{ "class"=>"Aqua::Stub", "init"=>{"class"=>"User", "id"=>"32"} }] } } } class User aquatic # ... def friends @friends ||= [] end end user.friends << alex # where alex is another user object Kane Baccigalupi RubyConf ‘09
25. Aqua A cool, clear drink of Ruby object persistence Stubbing methods in Aqua::Stub { "class"=>"User", "ivars"=>{ # ... "@friends"=>{ "class"=>"Array", "init"=>[{ "class"=>"Aqua::Stub", "init"=>{ "class"=>"User", "id"=>"32”, "methods"=>{"username"=>"alex"}, } }] } } } class User aquatic :embed => { :stub => :username } # ... end Kane Baccigalupi RubyConf ‘09
26. Aqua A cool, clear drink of Ruby object persistence Stubbed behavior user.reload friend = user.friends.first friend.class # Aqua::Stub friend.username# ‘alex’ # username was cached friend.email # this triggers the database call # for the friend object Kane Baccigalupi RubyConf ‘09
27. Aqua A cool, clear drink of Ruby object persistence Got Files? { "class"=>"User", "ivars"=>{ # ... "@avatar"=>{ "class"=>"Aqua::FileStub” "init"=>{ "methods"=>{ "content_type"=>"image/png", "content_length"=>{ "class"=>"Fixnum", "init"=>"26551” } } "id"=>"image_attach.png" }, } } } class User aquatic # ... attr_accessor :avatar end user.avatar = my_pic Kane Baccigalupi RubyConf ‘09
28. Aqua A cool, clear drink of Ruby object persistence Getting Objects: The basics User.load( some_id ) user.reload Kane Baccigalupi RubyConf ‘09
29. Aqua A cool, clear drink of Ruby object persistence Indexed Searches class User aquatic # ... index_on :username end " // javascript map function function(doc) { if( doc['class'] == 'User' && doc['ivars'] && doc['ivars']['@username'] ){ emit( doc['ivars']['@username'], 1 ); } } " User.query(:username, ’kane') # returns an array of all users named kane Kane Baccigalupi RubyConf ‘09
44. Aqua A cool, clear drink of Ruby object persistence More Info Rdocs: ruby-aqua.org Get the gem: sudo gem install aqua Explore the code: github.com/baccigalupi/aqua Kane Baccigalupi RubyConf ‘09