couchrest_model 1.1.0.beta2 → 1.1.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +2 -1
  2. data/README.md +50 -3
  3. data/VERSION +1 -1
  4. data/benchmarks/dirty.rb +118 -0
  5. data/couchrest_model.gemspec +1 -0
  6. data/history.txt +12 -0
  7. data/lib/couchrest/model/base.rb +15 -23
  8. data/lib/couchrest/model/casted_array.rb +26 -1
  9. data/lib/couchrest/model/casted_by.rb +23 -0
  10. data/lib/couchrest/model/casted_hash.rb +76 -0
  11. data/lib/couchrest/model/casted_model.rb +9 -6
  12. data/lib/couchrest/model/collection.rb +10 -1
  13. data/lib/couchrest/model/configuration.rb +4 -4
  14. data/lib/couchrest/model/design_doc.rb +65 -71
  15. data/lib/couchrest/model/designs/view.rb +26 -19
  16. data/lib/couchrest/model/designs.rb +0 -2
  17. data/lib/couchrest/model/dirty.rb +49 -0
  18. data/lib/couchrest/model/extended_attachments.rb +7 -1
  19. data/lib/couchrest/model/persistence.rb +15 -4
  20. data/lib/couchrest/model/properties.rb +50 -9
  21. data/lib/couchrest/model/property.rb +6 -2
  22. data/lib/couchrest/model/proxyable.rb +13 -19
  23. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  24. data/lib/couchrest/model/views.rb +4 -18
  25. data/lib/couchrest_model.rb +8 -3
  26. data/spec/couchrest/base_spec.rb +1 -28
  27. data/spec/couchrest/casted_model_spec.rb +1 -1
  28. data/spec/couchrest/collection_spec.rb +0 -1
  29. data/spec/couchrest/design_doc_spec.rb +211 -0
  30. data/spec/couchrest/designs/view_spec.rb +41 -17
  31. data/spec/couchrest/designs_spec.rb +0 -5
  32. data/spec/couchrest/dirty_spec.rb +355 -0
  33. data/spec/couchrest/property_spec.rb +5 -2
  34. data/spec/couchrest/proxyable_spec.rb +0 -11
  35. data/spec/couchrest/subclass_spec.rb +6 -16
  36. data/spec/couchrest/view_spec.rb +6 -50
  37. data/spec/fixtures/base.rb +1 -1
  38. data/spec/fixtures/more/card.rb +2 -2
  39. data/spec/spec_helper.rb +2 -0
  40. metadata +9 -4
  41. data/Gemfile.lock +0 -76
  42. data/lib/couchrest/model/support/couchrest.rb +0 -19
@@ -3,19 +3,13 @@ module CouchRest
3
3
  module Model
4
4
  module DesignDoc
5
5
  extend ActiveSupport::Concern
6
-
6
+
7
7
  module ClassMethods
8
-
8
+
9
9
  def design_doc
10
- @design_doc ||= Design.new(default_design_doc)
10
+ @design_doc ||= ::CouchRest::Design.new(default_design_doc)
11
11
  end
12
-
13
- # Use when something has been changed, like a view, so that on the next request
14
- # the design docs will be updated (if changed!)
15
- def req_design_doc_refresh
16
- @design_doc_fresh = { }
17
- end
18
-
12
+
19
13
  def design_doc_id
20
14
  "_design/#{design_doc_slug}"
21
15
  end
@@ -24,42 +18,20 @@ module CouchRest
24
18
  self.to_s
25
19
  end
26
20
 
27
- def default_design_doc
28
- {
29
- "_id" => design_doc_id,
30
- "language" => "javascript",
31
- "views" => {
32
- 'all' => {
33
- 'map' => "function(doc) {
34
- if (doc['#{self.model_type_key}'] == '#{self.to_s}') {
35
- emit(doc['_id'],1);
36
- }
37
- }"
38
- }
39
- }
40
- }
21
+ def design_doc_uri(db = database)
22
+ "#{db.root}/#{design_doc_id}"
41
23
  end
42
24
 
43
- # DEPRECATED
44
- # use stored_design_doc to retrieve the current design doc
45
- def all_design_doc_versions(db = database)
46
- db.documents :startkey => "_design/#{self.to_s}",
47
- :endkey => "_design/#{self.to_s}-\u9999"
48
- end
49
-
50
25
  # Retreive the latest version of the design document directly
51
- # from the database.
26
+ # from the database. This is never cached and will return nil if
27
+ # the design is not present.
28
+ #
29
+ # Use this method if you'd like to compare revisions [_rev] which
30
+ # is not stored in the normal design doc.
52
31
  def stored_design_doc(db = database)
53
- db.get(design_doc_id) rescue nil
54
- end
55
- alias :model_design_doc :stored_design_doc
56
-
57
- def refresh_design_doc(db = database)
58
- raise "Database missing for design document refresh" if db.nil?
59
- unless design_doc_fresh(db)
60
- save_design_doc(db)
61
- design_doc_fresh(db, true)
62
- end
32
+ db.get(design_doc_id)
33
+ rescue RestClient::ResourceNotFound
34
+ nil
63
35
  end
64
36
 
65
37
  # Save the design doc onto a target database in a thread-safe way,
@@ -68,7 +40,7 @@ module CouchRest
68
40
  # See also save_design_doc! to always save the design doc even if there
69
41
  # are no changes.
70
42
  def save_design_doc(db = database, force = false)
71
- update_design_doc(Design.new(design_doc), db, force)
43
+ update_design_doc(db, force)
72
44
  end
73
45
 
74
46
  # Force the update of the model's design_doc even if it hasn't changed.
@@ -76,48 +48,70 @@ module CouchRest
76
48
  save_design_doc(db, true)
77
49
  end
78
50
 
79
- protected
51
+ private
80
52
 
81
- def design_doc_fresh(db, fresh = nil)
82
- @design_doc_fresh ||= {}
83
- if fresh.nil?
84
- @design_doc_fresh[db.uri] || false
85
- else
86
- @design_doc_fresh[db.uri] = fresh
87
- end
53
+ def design_doc_cache
54
+ Thread.current[:couchrest_design_cache] ||= {}
88
55
  end
56
+ def design_doc_cache_checksum(db)
57
+ design_doc_cache[design_doc_uri(db)]
58
+ end
59
+ def set_design_doc_cache_checksum(db, checksum)
60
+ design_doc_cache[design_doc_uri(db)] = checksum
61
+ end
62
+
63
+ # Writes out a design_doc to a given database if forced
64
+ # or the stored checksum is not the same as the current
65
+ # generated checksum.
66
+ #
67
+ # Returns the original design_doc provided, but does
68
+ # not update it with the revision.
69
+ def update_design_doc(db, force = false)
70
+ return design_doc unless force || auto_update_design_doc
89
71
 
90
- # Writes out a design_doc to a given database, returning the
91
- # updated design doc
92
- def update_design_doc(design_doc, db, force = false)
72
+ # Grab the design doc's checksum
73
+ checksum = design_doc.checksum!
74
+
75
+ # If auto updates enabled, check checksum cache
76
+ return design_doc if auto_update_design_doc && design_doc_cache_checksum(db) == checksum
77
+
78
+ # Load up the stored doc (if present), update, and save
93
79
  saved = stored_design_doc(db)
94
80
  if saved
95
- changes = force
96
- design_doc['views'].each do |name, view|
97
- if !compare_views(saved['views'][name], view)
98
- changes = true
99
- saved['views'][name] = view
100
- end
101
- end
102
- if changes
81
+ if force || saved['couchrest-hash'] != checksum
82
+ saved.merge!(design_doc)
103
83
  db.save_doc(saved)
104
84
  end
105
- design_doc
106
85
  else
107
- design_doc.database = db
108
- design_doc.save
109
- design_doc
86
+ db.save_doc(design_doc)
87
+ design_doc.delete('_rev') # Prevent conflicts, never store rev as DB specific
110
88
  end
89
+
90
+ # Ensure checksum cached for next attempt if using auto updates
91
+ set_design_doc_cache_checksum(db, checksum) if auto_update_design_doc
92
+ design_doc
111
93
  end
112
94
 
113
- # Return true if the two views match
114
- def compare_views(orig, repl)
115
- return false if orig.nil? or repl.nil?
116
- (orig['map'].to_s.strip == repl['map'].to_s.strip) && (orig['reduce'].to_s.strip == repl['reduce'].to_s.strip)
95
+ def default_design_doc
96
+ {
97
+ "_id" => design_doc_id,
98
+ "language" => "javascript",
99
+ "views" => {
100
+ 'all' => {
101
+ 'map' => "function(doc) {
102
+ if (doc['#{self.model_type_key}'] == '#{self.to_s}') {
103
+ emit(doc['_id'],1);
104
+ }
105
+ }"
106
+ }
107
+ }
108
+ }
117
109
  end
118
110
 
111
+
112
+
119
113
  end # module ClassMethods
120
-
114
+
121
115
  end
122
116
  end
123
117
  end
@@ -90,6 +90,13 @@ module CouchRest
90
90
  result ? all.last : limit(1).descending.all.last
91
91
  end
92
92
 
93
+ # Return the number of documents in the currently defined result set.
94
+ # Use <tt>#count</tt> for the total number of documents regardless
95
+ # of the current limit defined.
96
+ def length
97
+ docs.length
98
+ end
99
+
93
100
  # Perform a count operation based on the current view. If the view
94
101
  # can be reduced, the reduce will be performed and return the first
95
102
  # value. This is okay for most simple queries, but may provide
@@ -106,7 +113,7 @@ module CouchRest
106
113
  def count
107
114
  raise "View#count cannot be used with group options" if query[:group]
108
115
  if can_reduce?
109
- row = reduce.rows.first
116
+ row = reduce.skip(0).limit(1).rows.first
110
117
  row.nil? ? 0 : row.value
111
118
  else
112
119
  limit(0).total_rows
@@ -232,11 +239,19 @@ module CouchRest
232
239
  end
233
240
 
234
241
 
235
- # The results should be provided in descending order.
242
+ # The results should be provided in descending order. If the startkey or
243
+ # endkey query options have already been seen set, calling this method
244
+ # will automatically swap the options around. If you don't want this,
245
+ # simply set descending before any other option.
236
246
  #
237
- # Descending is false by default, this method will enable it and cannot
238
- # be undone.
247
+ # Descending is false by default, and this method cannot
248
+ # be undone once used, it has no inverse option.
239
249
  def descending
250
+ if query[:startkey] || query[:endkey]
251
+ query[:startkey], query[:endkey] = query[:endkey], query[:startkey]
252
+ elsif query[:startkey_docid] || query[:endkey_docid]
253
+ query[:startkey_docid], query[:endkey_docid] = query[:endkey_docid], query[:startkey_docid]
254
+ end
240
255
  update_query(:descending => true)
241
256
  end
242
257
 
@@ -383,30 +398,22 @@ module CouchRest
383
398
  def execute
384
399
  return self.result if result
385
400
  raise "Database must be defined in model or view!" if use_database.nil?
386
- retryable = true
387
- # Remove the reduce value if its not needed
401
+
402
+ # Remove the reduce value if its not needed to prevent CouchDB errors
388
403
  query.delete(:reduce) unless can_reduce?
389
- begin
390
- self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
391
- rescue RestClient::ResourceNotFound => e
392
- if retryable
393
- model.save_design_doc(use_database)
394
- retryable = false
395
- retry
396
- else
397
- raise e
398
- end
399
- end
404
+
405
+ model.save_design_doc(use_database)
406
+
407
+ self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
400
408
  end
401
409
 
402
410
  # Class Methods
403
411
  class << self
404
-
405
412
  # Simplified view creation. A new view will be added to the
406
413
  # provided model's design document using the name and options.
407
414
  #
408
415
  # If the view name starts with "by_" and +:by+ is not provided in
409
- # the options, the new view's map method will be interpretted and
416
+ # the options, the new view's map method will be interpreted and
410
417
  # generated automatically. For example:
411
418
  #
412
419
  # View.create(Meeting, "by_date_and_name")
@@ -28,8 +28,6 @@ module CouchRest
28
28
  mapper.create_view_method(:all)
29
29
 
30
30
  mapper.instance_eval(&block) if block_given?
31
-
32
- req_design_doc_refresh
33
31
  end
34
32
 
35
33
  # Override the default page pagination value:
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ I18n.load_path << File.join(
4
+ File.dirname(__FILE__), "validations", "locale", "en.yml"
5
+ )
6
+
7
+ module CouchRest
8
+ module Model
9
+
10
+ # This applies to both Model::Base and Model::CastedModel
11
+ module Dirty
12
+ extend ActiveSupport::Concern
13
+ include CouchRest::Model::CastedBy # needed for base_doc
14
+ include ActiveModel::Dirty
15
+
16
+ included do
17
+ # internal dirty setting - overrides global setting.
18
+ # this is used to temporarily disable dirty tracking when setting
19
+ # attributes directly, for performance reasons.
20
+ self.send(:attr_accessor, :disable_dirty)
21
+ end
22
+
23
+ def use_dirty?
24
+ bdoc = base_doc
25
+ bdoc && !bdoc.disable_dirty
26
+ end
27
+
28
+ def couchrest_attribute_will_change!(attr)
29
+ return if attr.nil? || !use_dirty?
30
+ attribute_will_change!(attr)
31
+ couchrest_parent_will_change!
32
+ end
33
+
34
+ def couchrest_parent_will_change!
35
+ @casted_by.couchrest_attribute_will_change!(casted_by_attribute) if @casted_by
36
+ end
37
+
38
+ private
39
+
40
+ # return the attribute name this object is referenced by in the parent
41
+ def casted_by_attribute
42
+ return @casted_by_attribute if @casted_by_attribute
43
+ attr = @casted_by.attributes
44
+ @casted_by_attribute = attr.keys.detect { |k| attr[k] == self }
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,7 @@
1
1
  module CouchRest
2
2
  module Model
3
3
  module ExtendedAttachments
4
+ extend ActiveSupport::Concern
4
5
 
5
6
  # Add a file attachment to the current document. Expects
6
7
  # :file and :name to be included in the arguments.
@@ -35,7 +36,10 @@ module CouchRest
35
36
  # deletes a file attachment from the current doc
36
37
  def delete_attachment(attachment_name)
37
38
  return unless attachments
38
- attachments.delete attachment_name
39
+ if attachments.include?(attachment_name)
40
+ attribute_will_change!("_attachments")
41
+ attachments.delete attachment_name
42
+ end
39
43
  end
40
44
 
41
45
  # returns true if attachment_name exists
@@ -66,6 +70,8 @@ module CouchRest
66
70
  def set_attachment_attr(args)
67
71
  content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
68
72
  content_type ||= (get_mime_type(args[:name]) || 'text/plain')
73
+
74
+ attribute_will_change!("_attachments")
69
75
  attachments[args[:name]] = {
70
76
  'content_type' => content_type,
71
77
  'data' => args[:file].read
@@ -12,7 +12,9 @@ module CouchRest
12
12
  _run_save_callbacks do
13
13
  set_unique_id if new? && self.respond_to?(:set_unique_id)
14
14
  result = database.save_doc(self)
15
- (result["ok"] == true) ? self : false
15
+ ret = (result["ok"] == true) ? self : false
16
+ @changed_attributes.clear if ret && @changed_attributes
17
+ ret
16
18
  end
17
19
  end
18
20
  end
@@ -28,10 +30,13 @@ module CouchRest
28
30
  def update(options = {})
29
31
  raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
30
32
  return false unless perform_validations(options)
33
+ return true if !self.changed?
31
34
  _run_update_callbacks do
32
35
  _run_save_callbacks do
33
36
  result = database.save_doc(self)
34
- result["ok"] == true
37
+ ret = result["ok"] == true
38
+ @changed_attributes.clear if ret && @changed_attributes
39
+ ret
35
40
  end
36
41
  end
37
42
  end
@@ -140,12 +145,18 @@ module CouchRest
140
145
  # should use the class name as part of the unique id.
141
146
  def unique_id method = nil, &block
142
147
  if method
148
+ define_method :get_unique_id do
149
+ self.send(method)
150
+ end
143
151
  define_method :set_unique_id do
144
- self['_id'] ||= self.send(method)
152
+ self['_id'] ||= get_unique_id
145
153
  end
146
154
  elsif block
155
+ define_method :get_unique_id do
156
+ block.call(self)
157
+ end
147
158
  define_method :set_unique_id do
148
- uniqid = block.call(self)
159
+ uniqid = get_unique_id
149
160
  raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
150
161
  self['_id'] ||= uniqid
151
162
  end
@@ -6,7 +6,9 @@ module CouchRest
6
6
 
7
7
  included do
8
8
  extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
9
+ extlib_inheritable_accessor(:property_by_name) unless self.respond_to?(:property_by_name)
9
10
  self.properties ||= []
11
+ self.property_by_name ||= {}
10
12
  raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=))
11
13
  end
12
14
 
@@ -37,10 +39,33 @@ module CouchRest
37
39
  end
38
40
 
39
41
  # Store a casted value in the current instance of an attribute defined
40
- # with a property.
42
+ # with a property and update dirty status
41
43
  def write_attribute(property, value)
42
44
  prop = find_property!(property)
43
- self[prop.to_s] = prop.is_a?(String) ? value : prop.cast(self, value)
45
+ value = prop.is_a?(String) ? value : prop.cast(self, value)
46
+ attribute_will_change!(prop.name) if use_dirty? && self[prop.name] != value
47
+ self[prop.name] = value
48
+ end
49
+
50
+ def []=(key,value)
51
+ return super(key,value) unless use_dirty?
52
+
53
+ has_changes = self.changed?
54
+ if !has_changes && self.respond_to?(:get_unique_id)
55
+ check_id_change = true
56
+ old_id = get_unique_id
57
+ end
58
+
59
+ ret = super(key, value)
60
+
61
+ if check_id_change
62
+ # if we have set an attribute that results in the _id changing (unique_id),
63
+ # force changed? to return true so that the record can be saved
64
+ new_id = get_unique_id
65
+ changed_attributes["_id"] = new_id if old_id != new_id
66
+ end
67
+
68
+ ret
44
69
  end
45
70
 
46
71
  # Takes a hash as argument, and applies the values by using writer methods
@@ -54,29 +79,47 @@ module CouchRest
54
79
  end
55
80
  alias :attributes= :update_attributes_without_saving
56
81
 
82
+ # 'attributes' needed for Dirty
83
+ alias :attributes :properties_with_values
84
+
85
+ def set_attributes(hash)
86
+ attrs = remove_protected_attributes(hash)
87
+ directly_set_attributes(attrs)
88
+ end
89
+
90
+ protected
91
+
92
+ def find_property(property)
93
+ property.is_a?(Property) ? property : self.class.property_by_name[property.to_s]
94
+ end
57
95
 
58
- private
59
96
  # The following methods should be accessable by the Model::Base Class, but not by anything else!
60
97
  def apply_all_property_defaults
61
98
  return if self.respond_to?(:new?) && (new? == false)
62
99
  # TODO: cache the default object
100
+ # Never mark default options as dirty!
101
+ dirty, self.disable_dirty = self.disable_dirty, true
63
102
  self.class.properties.each do |property|
64
103
  write_attribute(property, property.default_value)
65
104
  end
105
+ self.disable_dirty = dirty
66
106
  end
67
107
 
68
108
  def prepare_all_attributes(doc = {}, options = {})
109
+ self.disable_dirty = !!options[:directly_set_attributes]
69
110
  apply_all_property_defaults
70
111
  if options[:directly_set_attributes]
71
112
  directly_set_read_only_attributes(doc)
72
113
  else
73
114
  doc = remove_protected_attributes(doc)
74
115
  end
75
- directly_set_attributes(doc) unless doc.nil?
116
+ res = doc.nil? ? doc : directly_set_attributes(doc)
117
+ self.disable_dirty = false
118
+ res
76
119
  end
77
120
 
78
121
  def find_property!(property)
79
- prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
122
+ prop = find_property(property)
80
123
  raise ArgumentError, "Missing property definition for #{property.to_s}" if prop.nil?
81
124
  prop
82
125
  end
@@ -107,15 +150,12 @@ module CouchRest
107
150
  end
108
151
  end
109
152
 
110
- def set_attributes(hash)
111
- attrs = remove_protected_attributes(hash)
112
- directly_set_attributes(attrs)
113
- end
114
153
 
115
154
 
116
155
  module ClassMethods
117
156
 
118
157
  def property(name, *options, &block)
158
+ raise "Invalid property definition, '#{name}' already used for CouchRest Model type field" if name.to_s == model_type_key.to_s
119
159
  opts = { }
120
160
  type = options.shift
121
161
  if type.class != Hash
@@ -172,6 +212,7 @@ module CouchRest
172
212
  validates_casted_model property.name
173
213
  end
174
214
  properties << property
215
+ property_by_name[property.to_s] = property
175
216
  property
176
217
  end
177
218
 
@@ -38,8 +38,12 @@ module CouchRest::Model
38
38
  end
39
39
  arr = value.collect { |data| cast_value(parent, data) }
40
40
  # allow casted_by calls to be passed up chain by wrapping in CastedArray
41
- value = type_class != String ? CastedArray.new(arr, self) : arr
42
- value.casted_by = parent if value.respond_to?(:casted_by)
41
+ value = CastedArray.new(arr, self)
42
+ value.casted_by = parent
43
+ elsif (type == Object || type == Hash) && (value.class == Hash)
44
+ # allow casted_by calls to be passed up chain by wrapping in CastedHash
45
+ value = CouchRest::Model::CastedHash[value]
46
+ value.casted_by = parent
43
47
  elsif !value.nil?
44
48
  value = cast_value(parent, value)
45
49
  end
@@ -48,7 +48,6 @@ module CouchRest
48
48
  end
49
49
 
50
50
  # Base
51
-
52
51
  def new(*args)
53
52
  proxy_update(model.new(*args))
54
53
  end
@@ -56,7 +55,7 @@ module CouchRest
56
55
  def build_from_database(doc = {})
57
56
  proxy_update(model.build_from_database(doc))
58
57
  end
59
-
58
+
60
59
  def method_missing(m, *args, &block)
61
60
  if has_view?(m)
62
61
  if model.respond_to?(m)
@@ -73,32 +72,32 @@ module CouchRest
73
72
  end
74
73
  super
75
74
  end
76
-
75
+
77
76
  # DocumentQueries
78
-
77
+
79
78
  def all(opts = {}, &block)
80
79
  proxy_update_all(@model.all({:database => @database}.merge(opts), &block))
81
80
  end
82
-
81
+
83
82
  def count(opts = {})
84
83
  @model.count({:database => @database}.merge(opts))
85
84
  end
86
-
85
+
87
86
  def first(opts = {})
88
87
  proxy_update(@model.first({:database => @database}.merge(opts)))
89
88
  end
90
-
89
+
91
90
  def last(opts = {})
92
91
  proxy_update(@model.last({:database => @database}.merge(opts)))
93
92
  end
94
-
93
+
95
94
  def get(id)
96
95
  proxy_update(@model.get(id, @database))
97
96
  end
98
97
  alias :find :get
99
-
98
+
100
99
  # Views
101
-
100
+
102
101
  def has_view?(view)
103
102
  @model.has_view?(view)
104
103
  end
@@ -106,27 +105,22 @@ module CouchRest
106
105
  def view_by(*args)
107
106
  @model.view_by(*args)
108
107
  end
109
-
108
+
110
109
  def view(name, query={}, &block)
111
110
  proxy_update_all(@model.view(name, {:database => @database}.merge(query), &block))
112
111
  end
113
-
112
+
114
113
  def first_from_view(name, *args)
115
114
  # add to first hash available, or add to end
116
115
  (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
117
116
  proxy_update(@model.first_from_view(name, *args))
118
117
  end
119
-
118
+
120
119
  # DesignDoc
121
-
122
120
  def design_doc
123
121
  @model.design_doc
124
122
  end
125
-
126
- def refresh_design_doc(db = nil)
127
- @model.refresh_design_doc(db || @database)
128
- end
129
-
123
+
130
124
  def save_design_doc(db = nil)
131
125
  @model.save_design_doc(db || @database)
132
126
  end
@@ -0,0 +1,33 @@
1
+
2
+ CouchRest::Design.class_eval do
3
+
4
+ # Calculate and update the checksum of the Design document.
5
+ # Used for ensuring the latest version has been sent to the database.
6
+ #
7
+ # This will generate an flatterned, ordered array of all the elements of the
8
+ # design document, convert to string then generate an MD5 Hash. This should
9
+ # result in a consisitent Hash accross all platforms.
10
+ #
11
+ def checksum!
12
+ # create a copy of basic elements
13
+ base = self.dup
14
+ base.delete('_id')
15
+ base.delete('_rev')
16
+ base.delete('couchrest-hash')
17
+ result = nil
18
+ flatten =
19
+ lambda {|r|
20
+ (recurse = lambda {|v|
21
+ if v.is_a?(Hash)
22
+ v.to_a.map{|v| recurse.call(v)}.flatten
23
+ elsif v.is_a?(Array)
24
+ v.flatten.map{|v| recurse.call(v)}
25
+ else
26
+ v.to_s
27
+ end
28
+ }).call(r)
29
+ }
30
+ self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
31
+ end
32
+
33
+ end