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.
- 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
|