# # CouchDB membership views, two approaches. Sho Fukamachi # You will need to install edge DataMapper to run this # require 'rubygems' require 'dm-core' # Specify the db to be used. Create it in CouchDB yourself locally. DataMapper.setup(:default, Addressable::URI.parse("couchdb://localhost:5984/phototest2")) # create classes class Tag include DataMapper::Resource property :id, String, :key => true, :field => :_id property :rev, String, :field => :_rev property :name, String property :photos_cache, JsonObject, :default => {} view :all, { "map" => "function(doc) { if (doc.type == 'tag') { emit(null, doc); } }" } view :by_name, { "map" => "function(doc) { if (doc.type == 'tag') { emit(doc.name, doc); } }" } view :by_photo_id, { "map" => " function(doc) { if(doc.type == 'tag' && doc.photos_cache) { for (i in doc.photos_cache) { emit(i, doc); } } }"} view :by_photo_name, { "map" => " function(doc) { if(doc.type == 'tag' && doc.photos_cache) { for (i in doc.photos_cache) { emit(doc.photos_cache[i]['photo_name'], doc); } } }"} def photo_array Membership.photos_for_tag(self.id).first.value end def hash_for_cache {:tag_name => self.name, :tag_id => self.id, :tag_rev => self.rev} end end class Photo include DataMapper::Resource property :id, String, :key => true, :field => :_id property :rev, String, :field => :_rev property :name, String property :tags_cache, JsonObject, :default => {} property :test, JsonObject view :all, { "map" => "function(doc) { if (doc.type == 'photo') { emit(null, doc); } }" } view :by_name, { "map" => "function(doc) { if (doc.type == 'photo') { emit(doc.name, doc); } }" } view :by_tag_id, { "map" => " function(doc) { if(doc.type == 'photo' && doc.tags_cache) { for (i in doc.tags_cache) { emit(i, doc); } } }"} view :by_tag_name, { "map" => " function(doc) { if(doc.type == 'photo' && doc.tags_cache) { for (i in doc.tags_cache) { emit(doc.tags_cache[i]['tag_name'], doc); } } }"} def hash_for_cache {:photo_name => self.name, :photo_id => self.id, :photo_rev => self.rev} end def tags_array Membership.tags_for_photo(self.id).first.value end def apply_tag(tag) Membership.tag_photo(self, tag) end end class Membership include DataMapper::Resource property :id, String, :key => true, :field => :_id property :rev, String, :field => :_rev property :tag_id, String property :tag_name, String property :tag_rev, String property :photo_id, String property :photo_name, String property :photo_rev, String after :save, :populate_caches view :all, { "map" => "function(doc) { if (doc.type == 'membership') { emit(null, doc); } }" } view :existence_check, { "map" => "function(doc) { if (doc.type == 'membership') { emit([doc.photo_id, doc.tag_id], doc); } }" } view :tags_for_photo_by_photo_id, { "map" => "function(doc) { if (doc.type == 'membership') { emit(doc.photo_id, doc); } }", "reduce" => "function(keys, vals) { results = []; for (var i in vals) { results.push(vals[i]['tag_id']); } return results; }" } view :photos_for_tag_by_tag_id, { "map" => "function(doc) { if (doc.type == 'membership') { emit(doc.tag_id, doc); } }" , "reduce" => "function(keys, vals) { results = []; for (var i in vals) { results.push(vals[i]['photo_id']); } return results; }" } view :tags_for_photo_by_photo_name, { "map" => "function(doc) { if (doc.type == 'membership') { emit(doc.photo_name, doc); } }", "reduce" => "function(keys, vals) { results = []; for (var i in vals) { results.push(vals[i]['tag_name']); } return results; }" } view :photos_for_tag_by_tag_name, { "map" => "function(doc) { if (doc.type == 'membership') { emit(doc.tag_name, doc); } }" , "reduce" => "function(keys, vals) { results = []; for (var i in vals) { results.push(vals[i]['photo_name']); } return results; }" } def assemble_hash_for_caches(remote_hash) remote_hash[:membership_id] = self.id remote_hash[:membership_rev] = self.rev remote_hash end def populate_caches photo = Photo.get(self.photo_id) tag = Tag.get(self.tag_id) tag.photos_cache.delete(photo.id) photo.tags_cache.delete(tag.id) tag.photos_cache[photo.id] = assemble_hash_for_caches(photo.hash_for_cache) photo.tags_cache[tag.id] = assemble_hash_for_caches(tag.hash_for_cache) tag.save photo.save end def self.tag_photo(photo, tag) print "trying to apply tag #{tag.name} to photo #{photo.name} .." if self.existence_check([photo.id, tag.id]).blank? hash = photo.hash_for_cache.merge(tag.hash_for_cache) m = Membership.new(hash).save print "done" else print "tag exists, skipping" end puts end def self.renew_all_caches Photo.all.each do |p| p.tags_cache = {} p.save end Tag.all.each do |t| t.photos_cache = {} t.save end Membership.all.each do |m| m.populate_caches end end end # # Create the views # Photo.auto_migrate! Tag.auto_migrate! Membership.auto_migrate! # # Insert some data # mg = Photo.create(:id => "mg", :name => "Migration") des = Photo.create(:id => "des", :name => "Dar es Salaam") syd = Photo.create(:id => "syd", :name => "Sydney") tok = Photo.create(:id => "tok", :name => "Tokyo") wd = Tag.create(:id => "wd", :name => "wildebeest") tz = Tag.create(:id => "tz", :name => "tanzania") city = Tag.create(:id => "city", :name => "City") night = Tag.create(:id => "night", :name => "Night View") au = Tag.create(:id => "au", :name => "Australia") jp = Tag.create(:id => "tz", :name => "Japan") Membership.tag_photo(mg, wd) Membership.tag_photo(des, tz) Membership.tag_photo(syd, city) Membership.tag_photo(tok, city) Membership.tag_photo(des, night) Membership.tag_photo(tok, night) Membership.tag_photo(syd, au) Membership.tag_photo(tok, jp) Membership.tag_photo(des, city) # # Make various requests using the different methods, confirm that they all work # # first up, using the memberships views and the cached information therein puts "perform some lookups using membership views" tagged_city = Membership.photos_for_tag_by_tag_name('City').first.value.join(', ') puts "Photos tagged 'city': #{tagged_city}" tagged_night = Membership.photos_for_tag_by_tag_name('Night View').first.value.join(', ') puts "Photos tagged 'Night View': #{tagged_night}" tags_for_des = Membership.tags_for_photo_by_photo_name('Dar es Salaam').first.value.join(', ') puts "Tags for photo 'Dar es Salaam': #{tags_for_des}" # now try and do the same via in-document caches puts "perform some lookups using in-record caches" tagged_city = Photo.by_tag_name('City').collect {|x| x.name}.join(', ') puts "Photos tagged 'city': #{tagged_city}" tagged_night = Photo.by_tag_name('Night View').collect {|x| x.name}.join(', ') puts "Photos tagged 'Night View': #{tagged_night}" tags_for_des = Tag.by_photo_name('Dar es Salaam').collect {|x| x.name}.join(', ') puts "Tags for photo 'Dar es Salaam': #{tags_for_des}" puts 'done!'