couchrest 0.33 → 0.34
Sign up to get free protection for your applications and to get access to all the features.
- 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
|