norr-couchrest 0.33.02 → 0.33.06

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -30,11 +30,14 @@ module CouchRest
30
30
 
31
31
  def default_design_doc
32
32
  {
33
- "language" => "ruby",
33
+ "language" => "javascript",
34
34
  "views" => {
35
35
  'all' => {
36
- 'map' => "proc {|doc|emit(nil,1) if doc['couchrest-type'].eql?('#{self.to_s}')}",
37
- 'reduce' => "proc{|ks,vs,rereduce|sum(vs)}"
36
+ 'map' => "function(doc) {
37
+ if (doc['couchrest-type'] == '#{self.to_s}') {
38
+ emit(doc['_id'],1);
39
+ }
40
+ }"
38
41
  }
39
42
  }
40
43
  }
@@ -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,51 +56,79 @@ 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
- self[key].is_a?(String) ? Time.parse(self[key].dup) : self[key]
78
- # Float instances don't get initialized with #new
79
- elsif ((property.init_method == 'new') && target == 'Float')
80
- cast_float(self[key])
81
- # 'boolean' type is simply used to generate a property? accessor method
82
- elsif ((property.init_method == 'new') && target == 'boolean')
83
- self[key]
84
- else
85
- # Let people use :send as a Time parse arg
86
- klass = ::CouchRest.constantize(target)
87
- klass.send(property.init_method, self[key].dup)
88
- end
89
- self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
90
- end
83
+ klass = ::CouchRest.constantize(property.type)
84
+ end
91
85
 
92
- end
93
-
94
- def cast_float(value)
95
- begin
96
- Float(value)
97
- rescue
98
- value
86
+ unless self[key].instance_of?(klass)
87
+ self[key] = convert_property_value(property, klass, self[property.name])
99
88
  end
89
+ associate_casted_to_parent(self[property.name], assigned)
100
90
  end
101
91
 
102
92
  end
103
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
+ # This fast method doesn't work 100% of the time...
104
+ # I get TypeError: no implicit conversion from nil to integer (line: 17)
105
+ self[key].is_a?(String) ? Time.parse(self[key].dup) : self[key]
106
+ # Float instances don't get initialized with #new
107
+ elsif ((property.init_method == 'new') && klass == Float)
108
+ cast_float(value)
109
+ # 'boolean' type is simply used to generate a property? accessor method
110
+ elsif ((property.init_method == 'new') && klass == TrueClass)
111
+ value
112
+ else
113
+ klass.send(property.init_method, value.dup)
114
+ end
115
+ end
116
+
117
+ def cast_property_by_name(property_name)
118
+ return unless self.class.properties
119
+ property = self.class.properties.detect{|property| property.name == property_name}
120
+ return unless property
121
+ cast_property(property, true)
122
+ end
123
+
124
+ def cast_float(value)
125
+ begin
126
+ Float(value)
127
+ rescue
128
+ value
129
+ end
130
+ end
131
+
104
132
  module ClassMethods
105
133
 
106
134
  def property(name, options={})
@@ -126,7 +154,7 @@ module CouchRest
126
154
  # defines the getter for the property (and optional aliases)
127
155
  def create_property_getter(property)
128
156
  # meth = property.name
129
- class_eval <<-EOS, __FILE__, __LINE__
157
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
130
158
  def #{property.name}
131
159
  self['#{property.name}']
132
160
  end
@@ -145,7 +173,7 @@ module CouchRest
145
173
  end
146
174
 
147
175
  if property.alias
148
- class_eval <<-EOS, __FILE__, __LINE__
176
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
149
177
  alias #{property.alias.to_sym} #{property.name.to_sym}
150
178
  EOS
151
179
  end
@@ -153,16 +181,17 @@ module CouchRest
153
181
 
154
182
  # defines the setter for the property (and optional aliases)
155
183
  def create_property_setter(property)
156
- meth = property.name
184
+ property_name = property.name
157
185
  class_eval <<-EOS
158
- def #{meth}=(value)
159
- self['#{meth}'] = value
186
+ def #{property_name}=(value)
187
+ self['#{property_name}'] = value
188
+ cast_property_by_name('#{property_name}')
160
189
  end
161
190
  EOS
162
191
 
163
192
  if property.alias
164
193
  class_eval <<-EOS
165
- alias #{property.alias.to_sym}= #{meth.to_sym}=
194
+ alias #{property.alias.to_sym}= #{property_name.to_sym}=
166
195
  EOS
167
196
  end
168
197
  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,9 +235,10 @@ 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
@@ -242,5 +263,22 @@ module CouchRest
242
263
  end
243
264
  end
244
265
 
266
+ protected
267
+
268
+ # Set document_saved flag on all casted models to true
269
+ def mark_as_saved
270
+ self.each do |key, prop|
271
+ if prop.is_a?(Array)
272
+ prop.each do |item|
273
+ if item.respond_to?(:document_saved)
274
+ item.send(:document_saved=, true)
275
+ end
276
+ end
277
+ elsif prop.respond_to?(:document_saved)
278
+ prop.send(:document_saved=, true)
279
+ end
280
+ end
281
+ end
282
+
245
283
  end
246
284
  end