Refactoring design doc manipulation for a much simpler and more reliable approach
This commit is contained in:
parent
2eed3581af
commit
5805f6e27b
19 changed files with 285 additions and 195 deletions
|
@ -1,7 +1,13 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
# Warning! The Collection module is seriously depricated.
|
||||
# Use the new Design Views instead, as this code copies many other parts
|
||||
# of CouchRest Model.
|
||||
#
|
||||
# Expect this to be removed soon.
|
||||
#
|
||||
module Collection
|
||||
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
@ -131,6 +137,9 @@ module CouchRest
|
|||
else
|
||||
@view_name = "#{design_doc}/#{view_name}"
|
||||
end
|
||||
|
||||
# Save the design doc, ready for use
|
||||
@container_class.save_design_doc(@database)
|
||||
end
|
||||
|
||||
# See Collection.paginate
|
||||
|
|
|
@ -10,10 +10,12 @@ module CouchRest
|
|||
included do
|
||||
add_config :model_type_key
|
||||
add_config :mass_assign_any_attribute
|
||||
|
||||
add_config :auto_update_design_doc
|
||||
|
||||
configure do |config|
|
||||
config.model_type_key = 'couchrest-type' # 'model'?
|
||||
config.mass_assign_any_attribute = false
|
||||
config.auto_update_design_doc = true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,19 +3,13 @@ module CouchRest
|
|||
module Model
|
||||
module DesignDoc
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
||||
module ClassMethods
|
||||
|
||||
|
||||
def design_doc
|
||||
@design_doc ||= ::CouchRest::Design.new(default_design_doc)
|
||||
end
|
||||
|
||||
# Use when something has been changed, like a view, so that on the next request
|
||||
# the design docs will be updated (if changed!)
|
||||
def req_design_doc_refresh
|
||||
@design_doc_fresh = { }
|
||||
end
|
||||
|
||||
|
||||
def design_doc_id
|
||||
"_design/#{design_doc_slug}"
|
||||
end
|
||||
|
@ -24,6 +18,80 @@ module CouchRest
|
|||
self.to_s
|
||||
end
|
||||
|
||||
def design_doc_full_url(db = database)
|
||||
"#{db.uri}/#{design_doc_id}"
|
||||
end
|
||||
|
||||
# Retreive the latest version of the design document directly
|
||||
# from the database. This is never cached and will return nil if
|
||||
# the design is not present.
|
||||
#
|
||||
# Use this method if you'd like to compare revisions [_rev] which
|
||||
# is not stored in the normal design doc.
|
||||
def stored_design_doc(db = database)
|
||||
db.get(design_doc_id)
|
||||
rescue RestClient::ResourceNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
# Save the design doc onto a target database in a thread-safe way,
|
||||
# not modifying the model's design_doc
|
||||
#
|
||||
# See also save_design_doc! to always save the design doc even if there
|
||||
# are no changes.
|
||||
def save_design_doc(db = database, force = false)
|
||||
update_design_doc(db, force)
|
||||
end
|
||||
|
||||
# Force the update of the model's design_doc even if it hasn't changed.
|
||||
def save_design_doc!(db = database)
|
||||
save_design_doc(db, true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def design_doc_cache
|
||||
Thread.current[:couchrest_design_cache] ||= {}
|
||||
end
|
||||
def design_doc_cache_checksum(db)
|
||||
design_doc_cache[design_doc_full_url(db)]
|
||||
end
|
||||
def set_design_doc_cache_checksum(db, checksum)
|
||||
design_doc_cache[design_doc_full_url(db)] = checksum
|
||||
end
|
||||
|
||||
# Writes out a design_doc to a given database if forced
|
||||
# or the stored checksum is not the same as the current
|
||||
# generated checksum.
|
||||
#
|
||||
# Returns the original design_doc provided, but does
|
||||
# not update it with the revision.
|
||||
def update_design_doc(db, force = false)
|
||||
return design_doc unless force || auto_update_design_doc
|
||||
|
||||
# Grab the design doc's checksum
|
||||
checksum = design_doc.checksum!
|
||||
|
||||
# If auto updates enabled, check checksum cache
|
||||
return design_doc if auto_update_design_doc && design_doc_cache_checksum(db) == checksum
|
||||
|
||||
# Load up the stored doc (if present), update, and save
|
||||
saved = stored_design_doc(db)
|
||||
if saved
|
||||
if force || saved['couchrest-hash'] != checksum
|
||||
saved.merge!(design_doc)
|
||||
db.save_doc(saved)
|
||||
end
|
||||
else
|
||||
db.save_doc(design_doc)
|
||||
design_doc.delete('_rev') # Prevent conflicts, never store rev as DB specific
|
||||
end
|
||||
|
||||
# Ensure checksum cached for next attempt if using auto updates
|
||||
set_design_doc_cache_checksum(db, checksum) if auto_update_design_doc
|
||||
design_doc
|
||||
end
|
||||
|
||||
def default_design_doc
|
||||
{
|
||||
"_id" => design_doc_id,
|
||||
|
@ -40,68 +108,7 @@ module CouchRest
|
|||
}
|
||||
end
|
||||
|
||||
# DEPRECATED
|
||||
# use stored_design_doc to retrieve the current design doc
|
||||
def all_design_doc_versions(db = database)
|
||||
db.documents :startkey => "_design/#{self.to_s}",
|
||||
:endkey => "_design/#{self.to_s}-\u9999"
|
||||
end
|
||||
|
||||
# Retreive the latest version of the design document directly
|
||||
# from the database.
|
||||
def stored_design_doc(db = database)
|
||||
db.get(design_doc_id) rescue nil
|
||||
end
|
||||
alias :model_design_doc :stored_design_doc
|
||||
|
||||
def refresh_design_doc(db = database)
|
||||
raise "Database missing for design document refresh" if db.nil?
|
||||
unless design_doc_fresh(db)
|
||||
save_design_doc(db)
|
||||
design_doc_fresh(db, true)
|
||||
end
|
||||
end
|
||||
|
||||
# Save the design doc onto a target database in a thread-safe way,
|
||||
# not modifying the model's design_doc
|
||||
#
|
||||
# See also save_design_doc! to always save the design doc even if there
|
||||
# are no changes.
|
||||
def save_design_doc(db = database, force = false)
|
||||
update_design_doc(Design.new(design_doc), db, force)
|
||||
end
|
||||
|
||||
# Force the update of the model's design_doc even if it hasn't changed.
|
||||
def save_design_doc!(db = database)
|
||||
save_design_doc(db, true)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def design_doc_fresh(db, fresh = nil)
|
||||
@design_doc_fresh ||= {}
|
||||
if fresh.nil?
|
||||
@design_doc_fresh[db.uri] || false
|
||||
else
|
||||
@design_doc_fresh[db.uri] = fresh
|
||||
end
|
||||
end
|
||||
|
||||
# Writes out a design_doc to a given database, returning the
|
||||
# updated design doc
|
||||
def update_design_doc(design_doc, db, force = false)
|
||||
design_doc['couchrest-hash'] = design_doc.checksum
|
||||
saved = stored_design_doc(db)
|
||||
if saved
|
||||
if force || saved['couchrest-hash'] != design_doc['couchrest-hash']
|
||||
saved.merge!(design_doc)
|
||||
db.save_doc(saved)
|
||||
end
|
||||
else
|
||||
db.save_doc(design_doc)
|
||||
end
|
||||
design_doc
|
||||
end
|
||||
|
||||
end # module ClassMethods
|
||||
|
||||
|
|
|
@ -28,8 +28,6 @@ module CouchRest
|
|||
mapper.create_view_method(:all)
|
||||
|
||||
mapper.instance_eval(&block) if block_given?
|
||||
|
||||
req_design_doc_refresh
|
||||
end
|
||||
|
||||
# Override the default page pagination value:
|
||||
|
|
|
@ -48,7 +48,6 @@ module CouchRest
|
|||
end
|
||||
|
||||
# Base
|
||||
|
||||
def new(*args)
|
||||
proxy_update(model.new(*args))
|
||||
end
|
||||
|
@ -56,7 +55,7 @@ module CouchRest
|
|||
def build_from_database(doc = {})
|
||||
proxy_update(model.build_from_database(doc))
|
||||
end
|
||||
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
if model.respond_to?(m)
|
||||
|
@ -73,32 +72,32 @@ module CouchRest
|
|||
end
|
||||
super
|
||||
end
|
||||
|
||||
|
||||
# DocumentQueries
|
||||
|
||||
|
||||
def all(opts = {}, &block)
|
||||
proxy_update_all(@model.all({:database => @database}.merge(opts), &block))
|
||||
end
|
||||
|
||||
|
||||
def count(opts = {})
|
||||
@model.count({:database => @database}.merge(opts))
|
||||
end
|
||||
|
||||
|
||||
def first(opts = {})
|
||||
proxy_update(@model.first({:database => @database}.merge(opts)))
|
||||
end
|
||||
|
||||
|
||||
def last(opts = {})
|
||||
proxy_update(@model.last({:database => @database}.merge(opts)))
|
||||
end
|
||||
|
||||
|
||||
def get(id)
|
||||
proxy_update(@model.get(id, @database))
|
||||
end
|
||||
alias :find :get
|
||||
|
||||
|
||||
# Views
|
||||
|
||||
|
||||
def has_view?(view)
|
||||
@model.has_view?(view)
|
||||
end
|
||||
|
@ -106,27 +105,22 @@ module CouchRest
|
|||
def view_by(*args)
|
||||
@model.view_by(*args)
|
||||
end
|
||||
|
||||
|
||||
def view(name, query={}, &block)
|
||||
proxy_update_all(@model.view(name, {:database => @database}.merge(query), &block))
|
||||
end
|
||||
|
||||
|
||||
def first_from_view(name, *args)
|
||||
# add to first hash available, or add to end
|
||||
(args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
|
||||
proxy_update(@model.first_from_view(name, *args))
|
||||
end
|
||||
|
||||
|
||||
# DesignDoc
|
||||
|
||||
def design_doc
|
||||
@model.design_doc
|
||||
end
|
||||
|
||||
def refresh_design_doc(db = nil)
|
||||
@model.refresh_design_doc(db || @database)
|
||||
end
|
||||
|
||||
|
||||
def save_design_doc(db = nil)
|
||||
@model.save_design_doc(db || @database)
|
||||
end
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
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
|
|
@ -1,18 +1,19 @@
|
|||
|
||||
CouchRest::Design.class_eval do
|
||||
|
||||
# Calculate a checksum of the Design document. Used for ensuring the latest
|
||||
# version has been sent to the database.
|
||||
# Calculate and update the 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
|
||||
def checksum!
|
||||
# create a copy of basic elements
|
||||
base = self.dup
|
||||
base.delete('_id')
|
||||
base.delete('_rev')
|
||||
base.delete('couchrest-hash')
|
||||
result = nil
|
||||
flatten =
|
||||
lambda {|r|
|
||||
|
@ -26,7 +27,7 @@ CouchRest::Design.class_eval do
|
|||
end
|
||||
}).call(r)
|
||||
}
|
||||
Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
|
||||
self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -81,7 +81,6 @@ module CouchRest
|
|||
end
|
||||
keys.push opts
|
||||
design_doc.view_by(*keys)
|
||||
req_design_doc_refresh
|
||||
end
|
||||
|
||||
# returns stored defaults if there is a view named this in the design doc
|
||||
|
@ -99,9 +98,9 @@ module CouchRest
|
|||
def view(name, query={}, &block)
|
||||
query = query.dup # Modifications made on copy!
|
||||
db = query.delete(:database) || database
|
||||
refresh_design_doc(db)
|
||||
query[:raw] = true if query[:reduce]
|
||||
raw = query.delete(:raw)
|
||||
save_design_doc(db)
|
||||
fetch_view_with_docs(db, name, query, raw, &block)
|
||||
end
|
||||
|
||||
|
@ -140,23 +139,10 @@ module CouchRest
|
|||
|
||||
def fetch_view(db, view_name, opts, &block)
|
||||
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
||||
retryable = true
|
||||
begin
|
||||
design_doc.view_on(db, view_name, opts, &block)
|
||||
# the design doc may not have been saved yet on this database
|
||||
rescue RestClient::ResourceNotFound => e
|
||||
if retryable
|
||||
save_design_doc(db)
|
||||
retryable = false
|
||||
retry
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
design_doc.view_on(db, view_name, opts, &block)
|
||||
end
|
||||
|
||||
end # module ClassMethods
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue