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.
- data/.gitignore +2 -1
- data/README.md +50 -3
- data/VERSION +1 -1
- data/benchmarks/dirty.rb +118 -0
- data/couchrest_model.gemspec +1 -0
- data/history.txt +12 -0
- data/lib/couchrest/model/base.rb +15 -23
- data/lib/couchrest/model/casted_array.rb +26 -1
- data/lib/couchrest/model/casted_by.rb +23 -0
- data/lib/couchrest/model/casted_hash.rb +76 -0
- data/lib/couchrest/model/casted_model.rb +9 -6
- data/lib/couchrest/model/collection.rb +10 -1
- data/lib/couchrest/model/configuration.rb +4 -4
- data/lib/couchrest/model/design_doc.rb +65 -71
- data/lib/couchrest/model/designs/view.rb +26 -19
- data/lib/couchrest/model/designs.rb +0 -2
- data/lib/couchrest/model/dirty.rb +49 -0
- data/lib/couchrest/model/extended_attachments.rb +7 -1
- data/lib/couchrest/model/persistence.rb +15 -4
- data/lib/couchrest/model/properties.rb +50 -9
- data/lib/couchrest/model/property.rb +6 -2
- data/lib/couchrest/model/proxyable.rb +13 -19
- data/lib/couchrest/model/support/couchrest_design.rb +33 -0
- data/lib/couchrest/model/views.rb +4 -18
- data/lib/couchrest_model.rb +8 -3
- data/spec/couchrest/base_spec.rb +1 -28
- data/spec/couchrest/casted_model_spec.rb +1 -1
- data/spec/couchrest/collection_spec.rb +0 -1
- data/spec/couchrest/design_doc_spec.rb +211 -0
- data/spec/couchrest/designs/view_spec.rb +41 -17
- data/spec/couchrest/designs_spec.rb +0 -5
- data/spec/couchrest/dirty_spec.rb +355 -0
- data/spec/couchrest/property_spec.rb +5 -2
- data/spec/couchrest/proxyable_spec.rb +0 -11
- data/spec/couchrest/subclass_spec.rb +6 -16
- data/spec/couchrest/view_spec.rb +6 -50
- data/spec/fixtures/base.rb +1 -1
- data/spec/fixtures/more/card.rb +2 -2
- data/spec/spec_helper.rb +2 -0
- metadata +9 -4
- data/Gemfile.lock +0 -76
- 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
|
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)
|
54
|
-
|
55
|
-
|
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(
|
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
|
-
|
51
|
+
private
|
80
52
|
|
81
|
-
def
|
82
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
96
|
-
|
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
|
108
|
-
design_doc.
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
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
|
-
|
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
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
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
|
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")
|
@@ -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.
|
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'] ||=
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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 =
|
42
|
-
value.casted_by = parent
|
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
|