couchrest 0.33 → 0.34
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/README.md +8 -127
- data/Rakefile +20 -36
- data/THANKS.md +2 -1
- data/history.txt +25 -0
- data/lib/couchrest.rb +5 -4
- data/lib/couchrest/core/database.rb +26 -21
- data/lib/couchrest/core/document.rb +4 -3
- data/lib/couchrest/helper/streamer.rb +11 -4
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/callbacks.rb +187 -138
- data/lib/couchrest/mixins/collection.rb +3 -16
- data/lib/couchrest/mixins/extended_attachments.rb +1 -1
- data/lib/couchrest/mixins/extended_document_mixins.rb +1 -0
- data/lib/couchrest/mixins/properties.rb +71 -44
- data/lib/couchrest/mixins/validation.rb +18 -29
- data/lib/couchrest/more/casted_model.rb +29 -1
- data/lib/couchrest/more/extended_document.rb +73 -25
- data/lib/couchrest/more/property.rb +20 -1
- data/lib/couchrest/support/class.rb +81 -67
- data/lib/couchrest/support/rails.rb +12 -5
- data/lib/couchrest/validation/auto_validate.rb +5 -9
- data/lib/couchrest/validation/validators/confirmation_validator.rb +11 -3
- data/lib/couchrest/validation/validators/format_validator.rb +8 -3
- data/lib/couchrest/validation/validators/length_validator.rb +10 -5
- data/lib/couchrest/validation/validators/numeric_validator.rb +6 -1
- data/lib/couchrest/validation/validators/required_field_validator.rb +8 -3
- data/spec/couchrest/core/couchrest_spec.rb +48 -2
- data/spec/couchrest/core/database_spec.rb +22 -10
- data/spec/couchrest/core/document_spec.rb +9 -1
- data/spec/couchrest/helpers/streamer_spec.rb +31 -2
- data/spec/couchrest/more/attribute_protection_spec.rb +94 -0
- data/spec/couchrest/more/casted_extended_doc_spec.rb +2 -4
- data/spec/couchrest/more/casted_model_spec.rb +230 -1
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +2 -2
- data/spec/couchrest/more/extended_doc_spec.rb +173 -15
- data/spec/couchrest/more/extended_doc_view_spec.rb +17 -10
- data/spec/couchrest/more/property_spec.rb +97 -3
- data/spec/fixtures/more/article.rb +4 -3
- data/spec/fixtures/more/card.rb +1 -1
- data/spec/fixtures/more/cat.rb +5 -3
- data/spec/fixtures/more/event.rb +4 -1
- data/spec/fixtures/more/invoice.rb +2 -2
- data/spec/fixtures/more/person.rb +1 -0
- data/spec/fixtures/more/user.rb +22 -0
- metadata +46 -13
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
module CouchRest
|
|
2
2
|
module Mixins
|
|
3
|
-
module PaginatedResults
|
|
4
|
-
def amount_pages
|
|
5
|
-
@amount_pages ||= 0
|
|
6
|
-
end
|
|
7
|
-
def amount_pages=(value)
|
|
8
|
-
@amount_pages = value
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
3
|
module Collection
|
|
13
4
|
|
|
14
5
|
def self.included(base)
|
|
@@ -93,8 +84,6 @@ module CouchRest
|
|
|
93
84
|
DEFAULT_PAGE = 1
|
|
94
85
|
DEFAULT_PER_PAGE = 30
|
|
95
86
|
|
|
96
|
-
attr_accessor :amount_pages
|
|
97
|
-
|
|
98
87
|
# Create a new CollectionProxy to represent the specified view. If a
|
|
99
88
|
# container class is specified, the proxy will create an object of the
|
|
100
89
|
# given type for each row that comes back from the view. If no
|
|
@@ -122,11 +111,8 @@ module CouchRest
|
|
|
122
111
|
def paginate(options = {})
|
|
123
112
|
page, per_page = parse_options(options)
|
|
124
113
|
results = @database.view(@view_name, pagination_options(page, per_page))
|
|
125
|
-
@amount_pages ||= (results['total_rows'].to_f / per_page.to_f).ceil
|
|
126
114
|
remember_where_we_left_off(results, page)
|
|
127
115
|
results = convert_to_container_array(results)
|
|
128
|
-
results.extend(PaginatedResults)
|
|
129
|
-
results.amount_pages = @amount_pages
|
|
130
116
|
results
|
|
131
117
|
end
|
|
132
118
|
|
|
@@ -204,8 +190,9 @@ module CouchRest
|
|
|
204
190
|
def pagination_options(page, per_page)
|
|
205
191
|
view_options = @view_options.clone
|
|
206
192
|
if @last_key && @last_docid && @last_page == page - 1
|
|
207
|
-
view_options.delete(:key)
|
|
208
|
-
|
|
193
|
+
key = view_options.delete(:key)
|
|
194
|
+
end_key = view_options[:endkey] || key
|
|
195
|
+
options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
|
|
209
196
|
else
|
|
210
197
|
options = { :limit => per_page, :skip => per_page * (page - 1) }
|
|
211
198
|
end
|
|
@@ -64,7 +64,7 @@ module CouchRest
|
|
|
64
64
|
def set_attachment_attr(args)
|
|
65
65
|
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
|
|
66
66
|
self['_attachments'][args[:name]] = {
|
|
67
|
-
'
|
|
67
|
+
'content_type' => content_type,
|
|
68
68
|
'data' => encode_attachment(args[:file].read)
|
|
69
69
|
}
|
|
70
70
|
end
|
|
@@ -6,3 +6,4 @@ require File.join(File.dirname(__FILE__), 'validation')
|
|
|
6
6
|
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'class_proxy')
|
|
8
8
|
require File.join(File.dirname(__FILE__), 'collection')
|
|
9
|
+
require File.join(File.dirname(__FILE__), 'attribute_protection')
|
|
@@ -27,7 +27,7 @@ module CouchRest
|
|
|
27
27
|
class IncludeError < StandardError; end
|
|
28
28
|
|
|
29
29
|
def self.included(base)
|
|
30
|
-
base.class_eval <<-EOS, __FILE__, __LINE__
|
|
30
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
31
31
|
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
|
32
32
|
self.properties ||= []
|
|
33
33
|
EOS
|
|
@@ -36,7 +36,7 @@ module CouchRest
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def apply_defaults
|
|
39
|
-
return if self.respond_to?(:
|
|
39
|
+
return if self.respond_to?(:new?) && (new? == false)
|
|
40
40
|
return unless self.class.respond_to?(:properties)
|
|
41
41
|
return if self.class.properties.empty?
|
|
42
42
|
# TODO: cache the default object
|
|
@@ -56,50 +56,76 @@ module CouchRest
|
|
|
56
56
|
def cast_keys
|
|
57
57
|
return unless self.class.properties
|
|
58
58
|
self.class.properties.each do |property|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
cast_property(property)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cast_property(property, assigned=false)
|
|
64
|
+
return unless property.casted
|
|
65
|
+
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
|
66
|
+
# Don't cast the property unless it has a value
|
|
67
|
+
return unless self[key]
|
|
68
|
+
if property.type.is_a?(Array)
|
|
69
|
+
klass = ::CouchRest.constantize(property.type[0])
|
|
70
|
+
arr = self[key].dup.collect do |value|
|
|
71
|
+
unless value.instance_of?(klass)
|
|
72
|
+
value = convert_property_value(property, klass, value)
|
|
71
73
|
end
|
|
74
|
+
associate_casted_to_parent(value, assigned)
|
|
75
|
+
value
|
|
76
|
+
end
|
|
77
|
+
self[key] = klass != String ? CastedArray.new(arr) : arr
|
|
78
|
+
self[key].casted_by = self if self[key].respond_to?(:casted_by)
|
|
79
|
+
else
|
|
80
|
+
if property.type == 'boolean'
|
|
81
|
+
klass = TrueClass
|
|
72
82
|
else
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Using custom time parsing method because Ruby's default method is toooo slow
|
|
76
|
-
self[key].is_a?(String) ? Time.mktime_with_offset(self[key].dup) : self[key]
|
|
77
|
-
# Float instances don't get initialized with #new
|
|
78
|
-
elsif ((property.init_method == 'new') && target == 'Float')
|
|
79
|
-
cast_float(self[key])
|
|
80
|
-
# 'boolean' type is simply used to generate a property? accessor method
|
|
81
|
-
elsif ((property.init_method == 'new') && target == 'boolean')
|
|
82
|
-
self[key]
|
|
83
|
-
else
|
|
84
|
-
# Let people use :send as a Time parse arg
|
|
85
|
-
klass = ::CouchRest.constantize(target)
|
|
86
|
-
klass.send(property.init_method, self[key].dup)
|
|
87
|
-
end
|
|
88
|
-
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
|
89
|
-
end
|
|
83
|
+
klass = ::CouchRest.constantize(property.type)
|
|
84
|
+
end
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def cast_float(value)
|
|
94
|
-
begin
|
|
95
|
-
Float(value)
|
|
96
|
-
rescue
|
|
97
|
-
value
|
|
86
|
+
unless self[key].instance_of?(klass)
|
|
87
|
+
self[key] = convert_property_value(property, klass, self[property.name])
|
|
98
88
|
end
|
|
89
|
+
associate_casted_to_parent(self[property.name], assigned)
|
|
99
90
|
end
|
|
100
91
|
|
|
101
92
|
end
|
|
102
93
|
|
|
94
|
+
def associate_casted_to_parent(casted, assigned)
|
|
95
|
+
casted.casted_by = self if casted.respond_to?(:casted_by)
|
|
96
|
+
casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def convert_property_value(property, klass, value)
|
|
100
|
+
if ((property.init_method == 'new') && klass == Time)
|
|
101
|
+
# Using custom time parsing method because Ruby's default method is toooo slow
|
|
102
|
+
value.is_a?(String) ? Time.mktime_with_offset(value.dup) : value
|
|
103
|
+
# Float instances don't get initialized with #new
|
|
104
|
+
elsif ((property.init_method == 'new') && klass == Float)
|
|
105
|
+
cast_float(value)
|
|
106
|
+
# 'boolean' type is simply used to generate a property? accessor method
|
|
107
|
+
elsif ((property.init_method == 'new') && klass == TrueClass)
|
|
108
|
+
value
|
|
109
|
+
else
|
|
110
|
+
klass.send(property.init_method, value.dup)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def cast_property_by_name(property_name)
|
|
115
|
+
return unless self.class.properties
|
|
116
|
+
property = self.class.properties.detect{|property| property.name == property_name}
|
|
117
|
+
return unless property
|
|
118
|
+
cast_property(property, true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def cast_float(value)
|
|
122
|
+
begin
|
|
123
|
+
Float(value)
|
|
124
|
+
rescue
|
|
125
|
+
value
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
103
129
|
module ClassMethods
|
|
104
130
|
|
|
105
131
|
def property(name, options={})
|
|
@@ -125,7 +151,7 @@ module CouchRest
|
|
|
125
151
|
# defines the getter for the property (and optional aliases)
|
|
126
152
|
def create_property_getter(property)
|
|
127
153
|
# meth = property.name
|
|
128
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
|
154
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
129
155
|
def #{property.name}
|
|
130
156
|
self['#{property.name}']
|
|
131
157
|
end
|
|
@@ -144,7 +170,7 @@ module CouchRest
|
|
|
144
170
|
end
|
|
145
171
|
|
|
146
172
|
if property.alias
|
|
147
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
|
173
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
148
174
|
alias #{property.alias.to_sym} #{property.name.to_sym}
|
|
149
175
|
EOS
|
|
150
176
|
end
|
|
@@ -152,16 +178,17 @@ module CouchRest
|
|
|
152
178
|
|
|
153
179
|
# defines the setter for the property (and optional aliases)
|
|
154
180
|
def create_property_setter(property)
|
|
155
|
-
|
|
181
|
+
property_name = property.name
|
|
156
182
|
class_eval <<-EOS
|
|
157
|
-
def #{
|
|
158
|
-
self['#{
|
|
183
|
+
def #{property_name}=(value)
|
|
184
|
+
self['#{property_name}'] = value
|
|
185
|
+
cast_property_by_name('#{property_name}')
|
|
159
186
|
end
|
|
160
187
|
EOS
|
|
161
188
|
|
|
162
189
|
if property.alias
|
|
163
190
|
class_eval <<-EOS
|
|
164
|
-
alias #{property.alias.to_sym}= #{
|
|
191
|
+
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
|
165
192
|
EOS
|
|
166
193
|
end
|
|
167
194
|
end
|
|
@@ -50,7 +50,10 @@ module CouchRest
|
|
|
50
50
|
|
|
51
51
|
def self.included(base)
|
|
52
52
|
base.extlib_inheritable_accessor(:auto_validation)
|
|
53
|
-
base.class_eval <<-EOS, __FILE__, __LINE__
|
|
53
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
54
|
+
# Callbacks
|
|
55
|
+
define_callbacks :validate
|
|
56
|
+
|
|
54
57
|
# Turn off auto validation by default
|
|
55
58
|
self.auto_validation ||= false
|
|
56
59
|
|
|
@@ -71,9 +74,10 @@ module CouchRest
|
|
|
71
74
|
EOS
|
|
72
75
|
|
|
73
76
|
base.extend(ClassMethods)
|
|
74
|
-
base.class_eval <<-EOS, __FILE__, __LINE__
|
|
77
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
78
|
+
define_callbacks :validate
|
|
75
79
|
if method_defined?(:_run_save_callbacks)
|
|
76
|
-
|
|
80
|
+
set_callback :save, :before, :check_validations
|
|
77
81
|
end
|
|
78
82
|
EOS
|
|
79
83
|
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
@@ -115,8 +119,7 @@ module CouchRest
|
|
|
115
119
|
# Check if a resource is valid in a given context
|
|
116
120
|
#
|
|
117
121
|
def valid?(context = :default)
|
|
118
|
-
|
|
119
|
-
result && validate_casted_arrays
|
|
122
|
+
recursive_valid?(self, context, true)
|
|
120
123
|
end
|
|
121
124
|
|
|
122
125
|
# checking on casted objects
|
|
@@ -133,29 +136,24 @@ module CouchRest
|
|
|
133
136
|
result
|
|
134
137
|
end
|
|
135
138
|
|
|
136
|
-
# Begin a recursive walk of the model checking validity
|
|
137
|
-
#
|
|
138
|
-
def all_valid?(context = :default)
|
|
139
|
-
recursive_valid?(self, context, true)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
139
|
# Do recursive validity checking
|
|
143
140
|
#
|
|
144
141
|
def recursive_valid?(target, context, state)
|
|
145
142
|
valid = state
|
|
146
|
-
target.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
valid = valid && recursive_valid?(ivar_value, context, valid)
|
|
150
|
-
elsif ivar_value.respond_to?(:each)
|
|
151
|
-
ivar_value.each do |item|
|
|
143
|
+
target.each do |key, prop|
|
|
144
|
+
if prop.is_a?(Array)
|
|
145
|
+
prop.each do |item|
|
|
152
146
|
if item.validatable?
|
|
153
|
-
valid =
|
|
147
|
+
valid = recursive_valid?(item, context, valid) && valid
|
|
154
148
|
end
|
|
155
149
|
end
|
|
150
|
+
elsif prop.validatable?
|
|
151
|
+
valid = recursive_valid?(prop, context, valid) && valid
|
|
156
152
|
end
|
|
157
153
|
end
|
|
158
|
-
|
|
154
|
+
target._run_validate_callbacks do
|
|
155
|
+
target.class.validators.execute(context, target) && valid
|
|
156
|
+
end
|
|
159
157
|
end
|
|
160
158
|
|
|
161
159
|
|
|
@@ -212,21 +210,12 @@ module CouchRest
|
|
|
212
210
|
def create_context_instance_methods(context)
|
|
213
211
|
name = "valid_for_#{context.to_s}?" # valid_for_signup?
|
|
214
212
|
if !self.instance_methods.include?(name)
|
|
215
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
|
213
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
216
214
|
def #{name} # def valid_for_signup?
|
|
217
215
|
valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
|
|
218
216
|
end # end
|
|
219
217
|
EOS
|
|
220
218
|
end
|
|
221
|
-
|
|
222
|
-
all = "all_valid_for_#{context.to_s}?" # all_valid_for_signup?
|
|
223
|
-
if !self.instance_methods.include?(all)
|
|
224
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
|
225
|
-
def #{all} # def all_valid_for_signup?
|
|
226
|
-
all_valid?('#{context.to_s}'.to_sym) # all_valid?('signup'.to_sym)
|
|
227
|
-
end # end
|
|
228
|
-
EOS
|
|
229
|
-
end
|
|
230
219
|
end
|
|
231
220
|
|
|
232
221
|
# Create a new validator of the given klazz and push it onto the
|
|
@@ -5,8 +5,10 @@ module CouchRest
|
|
|
5
5
|
module CastedModel
|
|
6
6
|
|
|
7
7
|
def self.included(base)
|
|
8
|
+
base.send(:include, ::CouchRest::Callbacks)
|
|
8
9
|
base.send(:include, ::CouchRest::Mixins::Properties)
|
|
9
10
|
base.send(:attr_accessor, :casted_by)
|
|
11
|
+
base.send(:attr_accessor, :document_saved)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def initialize(keys={})
|
|
@@ -26,5 +28,31 @@ module CouchRest
|
|
|
26
28
|
def [] key
|
|
27
29
|
super(key.to_s)
|
|
28
30
|
end
|
|
31
|
+
|
|
32
|
+
# Gets a reference to the top level extended
|
|
33
|
+
# document that a model is saved inside of
|
|
34
|
+
def base_doc
|
|
35
|
+
return nil unless @casted_by
|
|
36
|
+
@casted_by.base_doc
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# False if the casted model has already
|
|
40
|
+
# been saved in the containing document
|
|
41
|
+
def new?
|
|
42
|
+
!@document_saved
|
|
43
|
+
end
|
|
44
|
+
alias :new_record? :new?
|
|
45
|
+
|
|
46
|
+
# Sets the attributes from a hash
|
|
47
|
+
def update_attributes_without_saving(hash)
|
|
48
|
+
hash.each do |k, v|
|
|
49
|
+
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
|
50
|
+
end
|
|
51
|
+
hash.each do |k, v|
|
|
52
|
+
self.send("#{k}=",v)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
alias :attributes= :update_attributes_without_saving
|
|
56
|
+
|
|
29
57
|
end
|
|
30
|
-
end
|
|
58
|
+
end
|
|
@@ -14,6 +14,7 @@ module CouchRest
|
|
|
14
14
|
include CouchRest::Mixins::ExtendedAttachments
|
|
15
15
|
include CouchRest::Mixins::ClassProxy
|
|
16
16
|
include CouchRest::Mixins::Collection
|
|
17
|
+
include CouchRest::Mixins::AttributeProtection
|
|
17
18
|
|
|
18
19
|
def self.subclasses
|
|
19
20
|
@subclasses ||= []
|
|
@@ -21,7 +22,7 @@ module CouchRest
|
|
|
21
22
|
|
|
22
23
|
def self.inherited(subklass)
|
|
23
24
|
subklass.send(:include, CouchRest::Mixins::Properties)
|
|
24
|
-
subklass.class_eval <<-EOS, __FILE__, __LINE__
|
|
25
|
+
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
25
26
|
def self.inherited(subklass)
|
|
26
27
|
subklass.properties = self.properties.dup
|
|
27
28
|
end
|
|
@@ -33,18 +34,14 @@ module CouchRest
|
|
|
33
34
|
attr_accessor :casted_by
|
|
34
35
|
|
|
35
36
|
# Callbacks
|
|
36
|
-
define_callbacks :create
|
|
37
|
-
define_callbacks :save
|
|
38
|
-
define_callbacks :update
|
|
39
|
-
define_callbacks :destroy
|
|
37
|
+
define_callbacks :create, "result == :halt"
|
|
38
|
+
define_callbacks :save, "result == :halt"
|
|
39
|
+
define_callbacks :update, "result == :halt"
|
|
40
|
+
define_callbacks :destroy, "result == :halt"
|
|
40
41
|
|
|
41
42
|
def initialize(passed_keys={})
|
|
42
43
|
apply_defaults # defined in CouchRest::Mixins::Properties
|
|
43
|
-
|
|
44
|
-
if self.respond_to?("#{k}=")
|
|
45
|
-
self.send("#{k}=", passed_keys.delete(k))
|
|
46
|
-
end
|
|
47
|
-
end if passed_keys
|
|
44
|
+
set_attributes(passed_keys) unless passed_keys.nil?
|
|
48
45
|
super
|
|
49
46
|
cast_keys # defined in CouchRest::Mixins::Properties
|
|
50
47
|
unless self['_id'] && self['_rev']
|
|
@@ -76,17 +73,17 @@ module CouchRest
|
|
|
76
73
|
# on the document whenever saving occurs. CouchRest uses a pretty
|
|
77
74
|
# decent time format by default. See Time#to_json
|
|
78
75
|
def self.timestamps!
|
|
79
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
|
76
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
|
80
77
|
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
|
81
78
|
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
set_callback :save, :before do |object|
|
|
84
81
|
object['updated_at'] = Time.now
|
|
85
|
-
object['created_at'] = object['updated_at'] if object.
|
|
82
|
+
object['created_at'] = object['updated_at'] if object.new?
|
|
86
83
|
end
|
|
87
84
|
EOS
|
|
88
85
|
end
|
|
89
|
-
|
|
86
|
+
|
|
90
87
|
# Name a method that will be called before the document is first saved,
|
|
91
88
|
# which returns a string to be used for the document's <tt>_id</tt>.
|
|
92
89
|
# Because CouchDB enforces a constraint that each id must be unique,
|
|
@@ -128,17 +125,32 @@ module CouchRest
|
|
|
128
125
|
self.class.properties
|
|
129
126
|
end
|
|
130
127
|
|
|
128
|
+
# Gets a reference to the actual document in the DB
|
|
129
|
+
# Calls up to the next document if there is one,
|
|
130
|
+
# Otherwise we're at the top and we return self
|
|
131
|
+
def base_doc
|
|
132
|
+
return self if base_doc?
|
|
133
|
+
@casted_by.base_doc
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Checks if we're the top document
|
|
137
|
+
def base_doc?
|
|
138
|
+
!@casted_by
|
|
139
|
+
end
|
|
140
|
+
|
|
131
141
|
# Takes a hash as argument, and applies the values by using writer methods
|
|
132
142
|
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
|
133
143
|
# missing. In case of error, no attributes are changed.
|
|
134
144
|
def update_attributes_without_saving(hash)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
hash.
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
# remove attributes that cannot be updated, silently ignoring them
|
|
146
|
+
# which matches Rails behavior when, for instance, setting created_at.
|
|
147
|
+
# make a copy, we don't want to change arguments
|
|
148
|
+
attrs = hash.dup
|
|
149
|
+
%w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
|
|
150
|
+
check_properties_exist(attrs)
|
|
151
|
+
set_attributes(attrs)
|
|
141
152
|
end
|
|
153
|
+
alias :attributes= :update_attributes_without_saving
|
|
142
154
|
|
|
143
155
|
# Takes a hash as argument, and applies the values by using writer methods
|
|
144
156
|
# for each key. Raises a NoMethodError if the corresponding methods are
|
|
@@ -149,7 +161,8 @@ module CouchRest
|
|
|
149
161
|
end
|
|
150
162
|
|
|
151
163
|
# for compatibility with old-school frameworks
|
|
152
|
-
alias :new_record? :
|
|
164
|
+
alias :new_record? :new?
|
|
165
|
+
alias :new_document? :new?
|
|
153
166
|
|
|
154
167
|
# Trigger the callbacks (before, after, around)
|
|
155
168
|
# and create the document
|
|
@@ -170,7 +183,7 @@ module CouchRest
|
|
|
170
183
|
# unlike save, create returns the newly created document
|
|
171
184
|
def create_without_callbacks(bulk =false)
|
|
172
185
|
raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
|
|
173
|
-
set_unique_id if
|
|
186
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
|
174
187
|
result = database.save_doc(self, bulk)
|
|
175
188
|
(result["ok"] == true) ? self : false
|
|
176
189
|
end
|
|
@@ -185,7 +198,7 @@ module CouchRest
|
|
|
185
198
|
# only if the document isn't new
|
|
186
199
|
def update(bulk = false)
|
|
187
200
|
caught = catch(:halt) do
|
|
188
|
-
if self.
|
|
201
|
+
if self.new?
|
|
189
202
|
save(bulk)
|
|
190
203
|
else
|
|
191
204
|
_run_update_callbacks do
|
|
@@ -201,7 +214,7 @@ module CouchRest
|
|
|
201
214
|
# and save the document
|
|
202
215
|
def save(bulk = false)
|
|
203
216
|
caught = catch(:halt) do
|
|
204
|
-
if self.
|
|
217
|
+
if self.new?
|
|
205
218
|
_run_save_callbacks do
|
|
206
219
|
save_without_callbacks(bulk)
|
|
207
220
|
end
|
|
@@ -215,8 +228,9 @@ module CouchRest
|
|
|
215
228
|
# Returns a boolean value
|
|
216
229
|
def save_without_callbacks(bulk = false)
|
|
217
230
|
raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
|
|
218
|
-
set_unique_id if
|
|
231
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
|
219
232
|
result = database.save_doc(self, bulk)
|
|
233
|
+
mark_as_saved
|
|
220
234
|
result["ok"] == true
|
|
221
235
|
end
|
|
222
236
|
|
|
@@ -224,6 +238,7 @@ module CouchRest
|
|
|
224
238
|
# if the document is not saved properly.
|
|
225
239
|
def save!
|
|
226
240
|
raise "#{self.inspect} failed to save" unless self.save
|
|
241
|
+
true
|
|
227
242
|
end
|
|
228
243
|
|
|
229
244
|
# Deletes the document from the database. Runs the :destroy callbacks.
|
|
@@ -242,5 +257,38 @@ module CouchRest
|
|
|
242
257
|
end
|
|
243
258
|
end
|
|
244
259
|
|
|
260
|
+
protected
|
|
261
|
+
|
|
262
|
+
# Set document_saved flag on all casted models to true
|
|
263
|
+
def mark_as_saved
|
|
264
|
+
self.each do |key, prop|
|
|
265
|
+
if prop.is_a?(Array)
|
|
266
|
+
prop.each do |item|
|
|
267
|
+
if item.respond_to?(:document_saved)
|
|
268
|
+
item.send(:document_saved=, true)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
elsif prop.respond_to?(:document_saved)
|
|
272
|
+
prop.send(:document_saved=, true)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
|
|
279
|
+
def check_properties_exist(attrs)
|
|
280
|
+
attrs.each do |attribute_name, attribute_value|
|
|
281
|
+
raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def set_attributes(hash)
|
|
286
|
+
attrs = remove_protected_attributes(hash)
|
|
287
|
+
attrs.each do |attribute_name, attribute_value|
|
|
288
|
+
if self.respond_to?("#{attribute_name}=")
|
|
289
|
+
self.send("#{attribute_name}=", attrs.delete(attribute_name))
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
245
293
|
end
|
|
246
294
|
end
|