mattetti-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.
@@ -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
- options = { :startkey => @last_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
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
- 'content-type' => content_type,
67
+ 'content_type' => content_type,
68
68
  'data' => encode_attachment(args[:file].read)
69
69
  }
70
70
  end
@@ -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?(:new_document?) && (new_document? == false)
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
- next unless property.casted
60
- key = self.has_key?(property.name) ? property.name : property.name.to_sym
61
- # Don't cast the property unless it has a value
62
- next unless self[key]
63
- target = property.type
64
- if target.is_a?(Array)
65
- klass = ::CouchRest.constantize(target[0])
66
- self[property.name] = self[key].collect do |value|
67
- # Auto parse Time objects
68
- obj = ( (property.init_method == 'new') && klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
69
- obj.casted_by = self if obj.respond_to?(:casted_by)
70
- obj
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
- # Auto parse Time objects
74
- self[property.name] = if ((property.init_method == 'new') && target == 'Time')
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
- end
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
- meth = property.name
181
+ property_name = property.name
156
182
  class_eval <<-EOS
157
- def #{meth}=(value)
158
- self['#{meth}'] = value
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}= #{meth.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
- save_callback :before, :check_validations
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
- result = self.class.validators.execute(context, self)
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.instance_variables.each do |ivar|
147
- ivar_value = target.instance_variable_get(ivar)
148
- if ivar_value.validatable?
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 = valid && recursive_valid?(item, context, 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
- return valid && target.valid?
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
@@ -21,7 +21,7 @@ module CouchRest
21
21
 
22
22
  def self.inherited(subklass)
23
23
  subklass.send(:include, CouchRest::Mixins::Properties)
24
- subklass.class_eval <<-EOS, __FILE__, __LINE__
24
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
25
25
  def self.inherited(subklass)
26
26
  subklass.properties = self.properties.dup
27
27
  end
@@ -33,10 +33,10 @@ module CouchRest
33
33
  attr_accessor :casted_by
34
34
 
35
35
  # Callbacks
36
- define_callbacks :create
37
- define_callbacks :save
38
- define_callbacks :update
39
- define_callbacks :destroy
36
+ define_callbacks :create, "result == :halt"
37
+ define_callbacks :save, "result == :halt"
38
+ define_callbacks :update, "result == :halt"
39
+ define_callbacks :destroy, "result == :halt"
40
40
 
41
41
  def initialize(passed_keys={})
42
42
  apply_defaults # defined in CouchRest::Mixins::Properties
@@ -76,17 +76,17 @@ module CouchRest
76
76
  # on the document whenever saving occurs. CouchRest uses a pretty
77
77
  # decent time format by default. See Time#to_json
78
78
  def self.timestamps!
79
- class_eval <<-EOS, __FILE__, __LINE__
79
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
80
80
  property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
81
81
  property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
82
82
 
83
- save_callback :before do |object|
83
+ set_callback :save, :before do |object|
84
84
  object['updated_at'] = Time.now
85
- object['created_at'] = object['updated_at'] if object.new_document?
85
+ object['created_at'] = object['updated_at'] if object.new?
86
86
  end
87
87
  EOS
88
88
  end
89
-
89
+
90
90
  # Name a method that will be called before the document is first saved,
91
91
  # which returns a string to be used for the document's <tt>_id</tt>.
92
92
  # Because CouchDB enforces a constraint that each id must be unique,
@@ -128,17 +128,36 @@ module CouchRest
128
128
  self.class.properties
129
129
  end
130
130
 
131
+ # Gets a reference to the actual document in the DB
132
+ # Calls up to the next document if there is one,
133
+ # Otherwise we're at the top and we return self
134
+ def base_doc
135
+ return self if base_doc?
136
+ @casted_by.base_doc
137
+ end
138
+
139
+ # Checks if we're the top document
140
+ def base_doc?
141
+ !@casted_by
142
+ end
143
+
131
144
  # Takes a hash as argument, and applies the values by using writer methods
132
145
  # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
133
146
  # missing. In case of error, no attributes are changed.
134
147
  def update_attributes_without_saving(hash)
135
- hash.each do |k, v|
148
+ # remove attributes that cannot be updated, silently ignoring them
149
+ # which matches Rails behavior when, for instance, setting created_at.
150
+ # make a copy, we don't want to change arguments
151
+ attrs = hash.dup
152
+ %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
153
+ attrs.each do |k, v|
136
154
  raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
137
155
  end
138
- hash.each do |k, v|
156
+ attrs.each do |k, v|
139
157
  self.send("#{k}=",v)
140
158
  end
141
159
  end
160
+ alias :attributes= :update_attributes_without_saving
142
161
 
143
162
  # Takes a hash as argument, and applies the values by using writer methods
144
163
  # for each key. Raises a NoMethodError if the corresponding methods are
@@ -149,7 +168,8 @@ module CouchRest
149
168
  end
150
169
 
151
170
  # for compatibility with old-school frameworks
152
- alias :new_record? :new_document?
171
+ alias :new_record? :new?
172
+ alias :new_document? :new?
153
173
 
154
174
  # Trigger the callbacks (before, after, around)
155
175
  # and create the document
@@ -170,7 +190,7 @@ module CouchRest
170
190
  # unlike save, create returns the newly created document
171
191
  def create_without_callbacks(bulk =false)
172
192
  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 new_document? && self.respond_to?(:set_unique_id)
193
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
174
194
  result = database.save_doc(self, bulk)
175
195
  (result["ok"] == true) ? self : false
176
196
  end
@@ -185,7 +205,7 @@ module CouchRest
185
205
  # only if the document isn't new
186
206
  def update(bulk = false)
187
207
  caught = catch(:halt) do
188
- if self.new_document?
208
+ if self.new?
189
209
  save(bulk)
190
210
  else
191
211
  _run_update_callbacks do
@@ -201,7 +221,7 @@ module CouchRest
201
221
  # and save the document
202
222
  def save(bulk = false)
203
223
  caught = catch(:halt) do
204
- if self.new_document?
224
+ if self.new?
205
225
  _run_save_callbacks do
206
226
  save_without_callbacks(bulk)
207
227
  end
@@ -215,15 +235,17 @@ module CouchRest
215
235
  # Returns a boolean value
216
236
  def save_without_callbacks(bulk = false)
217
237
  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 new_document? && self.respond_to?(:set_unique_id)
238
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
219
239
  result = database.save_doc(self, bulk)
220
- result["ok"] == true
240
+ mark_as_saved
241
+ true
221
242
  end
222
243
 
223
244
  # Saves the document to the db using save. Raises an exception
224
245
  # if the document is not saved properly.
225
246
  def save!
226
247
  raise "#{self.inspect} failed to save" unless self.save
248
+ true
227
249
  end
228
250
 
229
251
  # Deletes the document from the database. Runs the :destroy callbacks.
@@ -242,5 +264,22 @@ module CouchRest
242
264
  end
243
265
  end
244
266
 
267
+ protected
268
+
269
+ # Set document_saved flag on all casted models to true
270
+ def mark_as_saved
271
+ self.each do |key, prop|
272
+ if prop.is_a?(Array)
273
+ prop.each do |item|
274
+ if item.respond_to?(:document_saved)
275
+ item.send(:document_saved=, true)
276
+ end
277
+ end
278
+ elsif prop.respond_to?(:document_saved)
279
+ prop.send(:document_saved=, true)
280
+ end
281
+ end
282
+ end
283
+
245
284
  end
246
285
  end