Permalink
Browse files

Continuing working on tests for new design doc support

  • Loading branch information...
1 parent 5cf9d2e commit b9f21c694d8587200081b546dae9a438ee7ea28d @samlown samlown committed Jun 5, 2012
@@ -27,6 +27,8 @@ module ClassMethods
def design(prefix = nil, &block)
mapper = DesignMapper.new(self, prefix)
mapper.instance_eval(&block) if block_given?
+ # Create an 'all' view, using the previous settings.
+ mapper.view :all if prefix.nil?
end
# Override the default page pagination value:
@@ -62,9 +64,6 @@ def initialize(model, prefix = nil)
self.prefix = prefix
self.method = (prefix ? "#{prefix}_" : '') + 'design_doc'
- # Only create the all method for the master design
- create_view_method(:all) if prefix.nil?
-
# Create design doc method in model, then call it so we have a copy
create_design_doc_method
self.design_doc = model.send(method)
@@ -81,11 +80,10 @@ def enable_auto_update
design_doc.auto_update = true
end
- # Generate a method that will provide a new View instance when
- # requested. This will also define the view in CouchDB unless
- # auto_update_design_doc is disabled.
+ # Add the specified view to the design doc the definition was made in
+ # and create quick access methods in the model.
def view(name, opts = {})
- View.create(model, design_doc, name, opts)
+ View.define(model, design_doc, name, opts)
create_view_method(name)
end
@@ -110,7 +108,7 @@ def model_type_key
def create_design_doc_method
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{method}
- @_#{method} ||= ::CouchRest::Model::Designs::Design.new(self, #{prefix ? '"'+prefix.to_s+'"' : ''})
+ @_#{method} ||= ::CouchRest::Model::Designs::Design.new(self, #{prefix ? '"'+prefix.to_s+'"' : 'nil'})
end
EOS
end
@@ -119,7 +117,7 @@ def create_view_method(name, prefix = nil)
prefix = prefix ? "#{prefix}_" : ''
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{name}(opts = {})
- #{method}.view(opts, '#{name}')
+ #{method}.view('#{name}', opts)
end
def self.find_#{name}(*key)
#{name}.key(*key).first()
@@ -0,0 +1,140 @@
+
+module CouchRest
+ module Model
+ module Designs
+
+ class Design < ::CouchRest::Design
+
+ # The model Class that this design belongs to
+ attr_accessor :model
+
+ # Can this design save itself to the database?
+ # If false, the design will be loaded automatically before a view is executed.
+ attr_accessor :auto_update
+
+
+ # Instantiate a new design document for this model
+ def initialize(model, prefix = nil)
+ self.model = model
+ suffix = prefix ? "_#{prefix}" : ''
+ self["_id"] = "_design/#{model.to_s}#{suffix}"
+ apply_defaults
+ end
+
+ # Create a new view object.
+ # This overrides the normal CouchRest Design view method
+ def view(name, opts = {})
+ CouchRest::Model::Designs::View.new(self, model, opts, name)
+ end
+
+ def sync(db = nil)
+ if auto_update
+ db ||= database
+
+ # do we need to continue?
+ return self if cache_checksum(db) == checksum
+
+ # Load up the last copy. We never overwrite the remote copy
+ # as it may contain views that are not used or known about by
+ # our model.
+ doc = load_from_database(database)
+
+ if !doc || doc['couchrest-hash'] != checksum
+ # We need to save something
+ if doc
+ # Different! Update.
+ doc.merge!(to_hash)
+ else
+ # No previous doc, use our version.
+ doc = self
+ end
+ db.save_doc(doc)
+ end
+
+ set_cache_checksum(db, checksum)
+ end
+ self
+ end
+
+ def checksum
+ sum = self['couchrest-hash']
+ if sum && (@_original_hash == to_hash)
+ sum
+ else
+ checksum!
+ end
+ end
+
+ def database
+ model.database
+ end
+
+ protected
+
+ def load_from_database(db = database)
+ db.get(self['_id'])
+ rescue RestClient::ResourceNotFound
+ nil
+ end
+
+ # 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!
+ # Get a deep copy of hash to compare with
+ @_original_hash = Marshal.load(Marshal.dump(to_hash))
+ # create a copy of basic elements
+ base = self.dup
+ base.delete('_id')
+ base.delete('_rev')
+ base.delete('couchrest-hash')
+ result = nil
+ flatten =
+ lambda {|r|
+ (recurse = lambda {|v|
+ if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
+ v.to_a.map{|v| recurse.call(v)}.flatten
+ elsif v.is_a?(Array)
+ v.flatten.map{|v| recurse.call(v)}
+ else
+ v.to_s
+ end
+ }).call(r)
+ }
+ self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
+ end
+
+ # Override the default #uri method for one that accepts
+ # the current database.
+ # This is used by the caching code.
+ def uri(db)
+ "#{db.root}/#{self['_id']}"
+ end
+
+ def cache
+ Thread.current[:couchrest_design_cache] ||= {}
+ end
+ def cache_checksum(db)
+ cache[uri(db)]
+ end
+ def set_cache_checksum(db, checksum)
+ cache[uri(db)] = checksum
+ end
+
+ def apply_defaults
+ merge!(
+ "language" => "javascript",
+ "views" => { }
+ )
+ end
+
+ end
+ end
+ end
+end
+
+
@@ -19,6 +19,7 @@ class View
# Initialize a new View object. This method should not be called from
# outside CouchRest Model.
def initialize(design_doc, parent, new_query = {}, name = nil)
+ self.design_doc = design_doc
if parent.is_a?(Class) && parent < CouchRest::Model::Base
raise "Name must be provided for view to be initialized" if name.nil?
self.model = parent
@@ -380,7 +381,7 @@ def include_docs?
end
def update_query(new_query = {})
- self.class.new(self, new_query)
+ self.class.new(design_doc, self, new_query)
end
def can_reduce?
@@ -405,20 +406,20 @@ def execute
# Class Methods
class << self
- # Simplified view creation. A new view will be added to the
+ # Simplified view definition. A new view will be added to the
# provided design document using the name and options.
#
# If the view name starts with "by_" and +:by+ is not provided in
# the options, the new view's map method will be interpreted and
# generated automatically. For example:
#
- # View.create(Meeting, design, "by_date_and_name")
+ # View.define(Meeting, design, "by_date_and_name")
#
# Will create a view that searches by the date and name properties.
# Explicity setting the attributes to use is possible using the
# +:by+ option. For example:
#
- # View.create(Meeting, design, "by_date_and_name", :by => [:date, :firstname, :lastname])
+ # View.define(Meeting, design, "by_date_and_name", :by => [:date, :firstname, :lastname])
#
# The view name is the same, but three keys would be used in the
# subsecuent index.
@@ -433,11 +434,19 @@ class << self
# like to enable this, set the <tt>:allow_blank</tt> option to false. The default
# is true, empty strings are permited in the indexes.
#
- def create(model, design_doc, name, opts = {})
-
+ def define(model, design_doc, name, opts = {})
# Don't create the map or reduce method if auto updates are disabled
if design_doc.auto_update
- unless !opts[:map]
+ # Is this an all view?
+ if name.to_s == 'all'
+ opts[:map] = <<-EOF
+ function(doc) {
+ if (doc['#{model.model_type_key}'] == '#{model.to_s}') {
+ emit(doc._id, null);
+ }
+ }
+ EOF
+ elsif !opts[:map]
if opts[:by].nil? && name.to_s =~ /^by_(.+)/
opts[:by] = $1.split(/_and_/)
end
@@ -467,7 +476,7 @@ def create(model, design_doc, name, opts = {})
end
else
# Assume there is always a map method
- view['map'] = true
+ opts['map'] ||= true
end
design_doc['views'] ||= {}
@@ -43,7 +43,7 @@
require "couchrest/model/configuration"
require "couchrest/model/connection"
require "couchrest/model/designs"
-require "couchrest/model/designs/design_doc"
+require "couchrest/model/designs/design"
require "couchrest/model/designs/view"
# Monkey patches applied to couchrest
@@ -3,7 +3,7 @@ class Article < CouchRest::Model::Base
unique_id :slug
design do
- view :by_date, :descending => true
+ view :by_date # Default options not supported: :descending => true
view :by_user_id_and_date
view :by_tags,
@@ -104,6 +104,7 @@ class WithTemplateAndUniqueID < CouchRest::Model::Base
property :slug
property :preset, :default => 'value'
property :has_no_default
+ design
end
class WithGetterAndSetterMethods < CouchRest::Model::Base
View
@@ -28,7 +28,7 @@
cr = TEST_SERVER
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
test_dbs.each do |db|
- cr.database(db).delete! rescue nil
+ #cr.database(db).delete! rescue nil
end
end
end
Oops, something went wrong.

0 comments on commit b9f21c6

Please sign in to comment.