Working on adding checksum support to design documents to handle updates

This commit is contained in:
Sam Lown 2011-04-13 15:42:28 +02:00
parent a6becd7305
commit 221e5a5470
8 changed files with 89 additions and 41 deletions

View file

@ -12,21 +12,22 @@ GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
abstract (1.0.0) abstract (1.0.0)
actionpack (3.0.5) actionpack (3.0.6)
activemodel (= 3.0.5) activemodel (= 3.0.6)
activesupport (= 3.0.5) activesupport (= 3.0.6)
builder (~> 2.1.2) builder (~> 2.1.2)
erubis (~> 2.6.6) erubis (~> 2.6.6)
i18n (~> 0.4) i18n (~> 0.5.0)
rack (~> 1.2.1) rack (~> 1.2.1)
rack-mount (~> 0.6.13) rack-mount (~> 0.6.14)
rack-test (~> 0.5.7) rack-test (~> 0.5.7)
tzinfo (~> 0.3.23) tzinfo (~> 0.3.23)
activemodel (3.0.5) activemodel (3.0.6)
activesupport (= 3.0.5) activesupport (= 3.0.6)
builder (~> 2.1.2) builder (~> 2.1.2)
i18n (~> 0.4) i18n (~> 0.5.0)
activesupport (3.0.5) activesupport (3.0.6)
bouncy-castle-java (1.5.0145.2)
builder (2.1.2) builder (2.1.2)
couchrest (1.1.0.pre2) couchrest (1.1.0.pre2)
json (~> 1.5.1) json (~> 1.5.1)
@ -36,16 +37,19 @@ GEM
erubis (2.6.6) erubis (2.6.6)
abstract (>= 1.0.0) abstract (>= 1.0.0)
i18n (0.5.0) i18n (0.5.0)
jruby-openssl (0.7.3)
bouncy-castle-java
json (1.5.1) json (1.5.1)
json (1.5.1-java)
mime-types (1.16) mime-types (1.16)
rack (1.2.1) rack (1.2.1)
rack-mount (0.6.14) rack-mount (0.6.14)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (0.5.7) rack-test (0.5.7)
rack (>= 1.0) rack (>= 1.0)
railties (3.0.5) railties (3.0.6)
actionpack (= 3.0.5) actionpack (= 3.0.6)
activesupport (= 3.0.5) activesupport (= 3.0.6)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (~> 0.14.4) thor (~> 0.14.4)
rake (0.8.7) rake (0.8.7)
@ -63,12 +67,14 @@ GEM
tzinfo (0.3.26) tzinfo (0.3.26)
PLATFORMS PLATFORMS
java
ruby ruby
DEPENDENCIES DEPENDENCIES
activemodel (~> 3.0.0) activemodel (~> 3.0.0)
couchrest (= 1.1.0.pre2) couchrest (= 1.1.0.pre2)
couchrest_model! couchrest_model!
jruby-openssl (>= 0.7.3)
mime-types (~> 1.15) mime-types (~> 1.15)
rack-test (>= 0.5.7) rack-test (>= 0.5.7)
railties (~> 3.0.0) railties (~> 3.0.0)

View file

@ -30,5 +30,6 @@ Gem::Specification.new do |s|
s.add_dependency(%q<railties>, "~> 3.0.0") s.add_dependency(%q<railties>, "~> 3.0.0")
s.add_development_dependency(%q<rspec>, ">= 2.0.0") s.add_development_dependency(%q<rspec>, ">= 2.0.0")
s.add_development_dependency(%q<rack-test>, ">= 0.5.7") s.add_development_dependency(%q<rack-test>, ">= 0.5.7")
s.add_development_dependency("jruby-openssl", ">= 0.7.3")
end end

View file

@ -7,7 +7,7 @@ module CouchRest
module ClassMethods module ClassMethods
def design_doc def design_doc
@design_doc ||= Design.new(default_design_doc) @design_doc ||= ::CouchRest::Design.new(default_design_doc)
end end
# Use when something has been changed, like a view, so that on the next request # Use when something has been changed, like a view, so that on the next request
@ -90,24 +90,17 @@ module CouchRest
# Writes out a design_doc to a given database, returning the # Writes out a design_doc to a given database, returning the
# updated design doc # updated design doc
def update_design_doc(design_doc, db, force = false) def update_design_doc(design_doc, db, force = false)
design_doc['couchrest-hash'] = design_doc.checksum
saved = stored_design_doc(db) saved = stored_design_doc(db)
if saved if saved
changes = force if force || saved['couchrest-hash'] != design_doc['couchrest-hash']
design_doc['views'].each do |name, view| saved.merge!(design_doc)
if !compare_views(saved['views'][name], view)
changes = true
saved['views'][name] = view
end
end
if changes
db.save_doc(saved) db.save_doc(saved)
end end
design_doc
else else
design_doc.database = db db.save_doc(design_doc)
design_doc.save
design_doc
end end
design_doc
end end
# Return true if the two views match # Return true if the two views match

View file

@ -90,6 +90,13 @@ module CouchRest
result ? all.last : limit(1).descending.all.last result ? all.last : limit(1).descending.all.last
end end
# Return the number of documents in the currently defined result set.
# Use <tt>#count</tt> for the total number of documents regardless
# of the current limit defined.
def length
docs.length
end
# Perform a count operation based on the current view. If the view # Perform a count operation based on the current view. If the view
# can be reduced, the reduce will be performed and return the first # can be reduced, the reduce will be performed and return the first
# value. This is okay for most simple queries, but may provide # value. This is okay for most simple queries, but may provide
@ -383,30 +390,24 @@ module CouchRest
def execute def execute
return self.result if result return self.result if result
raise "Database must be defined in model or view!" if use_database.nil? raise "Database must be defined in model or view!" if use_database.nil?
retryable = true
# Remove the reduce value if its not needed # Remove the reduce value if its not needed
query.delete(:reduce) unless can_reduce? query.delete(:reduce) unless can_reduce?
begin
self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?}) # Save the design doc for the current database. This should be efficient
rescue RestClient::ResourceNotFound => e # and check for changes
if retryable
model.save_design_doc(use_database) model.save_design_doc(use_database)
retryable = false
retry self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
else
raise e
end
end
end end
# Class Methods # Class Methods
class << self class << self
# Simplified view creation. A new view will be added to the # Simplified view creation. A new view will be added to the
# provided model's design document using the name and options. # provided model's design document using the name and options.
# #
# If the view name starts with "by_" and +:by+ is not provided in # If the view name starts with "by_" and +:by+ is not provided in
# the options, the new view's map method will be interpretted and # the options, the new view's map method will be interpreted and
# generated automatically. For example: # generated automatically. For example:
# #
# View.create(Meeting, "by_date_and_name") # View.create(Meeting, "by_date_and_name")

View file

@ -0,0 +1,15 @@
CouchRest::Database.class_eval do
alias :delete_orig! :delete!
def delete!
clear_model_fresh_cache
delete_orig!
end
# If the database is deleted, ensure that the design docs will be refreshed.
def clear_model_fresh_cache
::CouchRest::Model::Base.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
end
end

View file

@ -0,0 +1,24 @@
CouchRest::Design.class_eval do
# Calculate a checksum of the Design document. Used for ensuring the latest
# version has been sent to the database.
#
# This will generate an flatterned, ordered array of all the elements of the
# design document, convert to string then generate an MD5 Hash. This should
# result in a consisitent Hash accross all platforms.
#
def checksum
# create a copy of basic elements
base = self.dup
base.delete('_id')
base.delete('_rev')
result = nil
flatten =
lambda{|v|
v.is_a?(Hash) ? v.flatten.map{|v| flatten.call(v)}.flatten : v
}
Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
end
end

View file

@ -45,7 +45,8 @@ require "couchrest/model/designs"
require "couchrest/model/designs/view" require "couchrest/model/designs/view"
# Monkey patches applied to couchrest # Monkey patches applied to couchrest
require "couchrest/model/support/couchrest" require "couchrest/model/support/couchrest_database"
require "couchrest/model/support/couchrest_design"
# Core Extensions # Core Extensions
require "couchrest/model/core_extensions/hash" require "couchrest/model/core_extensions/hash"
require "couchrest/model/core_extensions/time_parsing" require "couchrest/model/core_extensions/time_parsing"

View file

@ -163,6 +163,13 @@ describe "Design View" do
end end
end end
describe "#length" do
it "should provide a length from the docs array" do
@obj.should_receive(:docs).and_return([1, 2, 3])
@obj.length.should eql(3)
end
end
describe "#count" do describe "#count" do
it "should raise an error if view prepared for group" do it "should raise an error if view prepared for group" do
@obj.should_receive(:query).and_return({:group => true}) @obj.should_receive(:query).and_return({:group => true})