couchrest_model 1.1.0.beta2 → 1.1.0.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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