samflores-couchrest 0.2.1 → 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/README.md +10 -34
  2. data/Rakefile +5 -2
  3. data/bin/couchdir +20 -0
  4. data/examples/model/example.rb +13 -19
  5. data/examples/word_count/word_count.rb +24 -3
  6. data/examples/word_count/word_count_query.rb +6 -7
  7. data/lib/couchrest/core/database.rb +49 -126
  8. data/lib/couchrest/core/document.rb +25 -58
  9. data/lib/couchrest/core/model.rb +612 -0
  10. data/lib/couchrest/core/server.rb +10 -47
  11. data/lib/couchrest/core/validations.rb +328 -0
  12. data/lib/couchrest/monkeypatches.rb +0 -95
  13. data/lib/couchrest.rb +10 -57
  14. data/spec/couchrest/core/database_spec.rb +68 -183
  15. data/spec/couchrest/core/design_spec.rb +1 -1
  16. data/spec/couchrest/core/document_spec.rb +104 -285
  17. data/spec/couchrest/core/model_spec.rb +855 -0
  18. data/spec/couchrest/helpers/pager_spec.rb +1 -1
  19. data/spec/spec_helper.rb +7 -13
  20. metadata +17 -83
  21. data/examples/word_count/word_count_views.rb +0 -26
  22. data/lib/couchrest/core/response.rb +0 -16
  23. data/lib/couchrest/mixins/attachments.rb +0 -31
  24. data/lib/couchrest/mixins/callbacks.rb +0 -483
  25. data/lib/couchrest/mixins/design_doc.rb +0 -64
  26. data/lib/couchrest/mixins/document_queries.rb +0 -48
  27. data/lib/couchrest/mixins/extended_attachments.rb +0 -68
  28. data/lib/couchrest/mixins/extended_document_mixins.rb +0 -6
  29. data/lib/couchrest/mixins/properties.rb +0 -125
  30. data/lib/couchrest/mixins/validation.rb +0 -234
  31. data/lib/couchrest/mixins/views.rb +0 -168
  32. data/lib/couchrest/mixins.rb +0 -4
  33. data/lib/couchrest/more/casted_model.rb +0 -28
  34. data/lib/couchrest/more/extended_document.rb +0 -217
  35. data/lib/couchrest/more/property.rb +0 -40
  36. data/lib/couchrest/support/blank.rb +0 -42
  37. data/lib/couchrest/support/class.rb +0 -191
  38. data/lib/couchrest/validation/auto_validate.rb +0 -163
  39. data/lib/couchrest/validation/contextual_validators.rb +0 -78
  40. data/lib/couchrest/validation/validation_errors.rb +0 -118
  41. data/lib/couchrest/validation/validators/absent_field_validator.rb +0 -74
  42. data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -99
  43. data/lib/couchrest/validation/validators/format_validator.rb +0 -117
  44. data/lib/couchrest/validation/validators/formats/email.rb +0 -66
  45. data/lib/couchrest/validation/validators/formats/url.rb +0 -43
  46. data/lib/couchrest/validation/validators/generic_validator.rb +0 -120
  47. data/lib/couchrest/validation/validators/length_validator.rb +0 -134
  48. data/lib/couchrest/validation/validators/method_validator.rb +0 -89
  49. data/lib/couchrest/validation/validators/numeric_validator.rb +0 -104
  50. data/lib/couchrest/validation/validators/required_field_validator.rb +0 -109
  51. data/spec/couchrest/core/server_spec.rb +0 -35
  52. data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -40
  53. data/spec/couchrest/more/casted_model_spec.rb +0 -98
  54. data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -130
  55. data/spec/couchrest/more/extended_doc_spec.rb +0 -509
  56. data/spec/couchrest/more/extended_doc_view_spec.rb +0 -207
  57. data/spec/couchrest/more/property_spec.rb +0 -130
  58. data/spec/couchrest/support/class_spec.rb +0 -59
  59. data/spec/fixtures/more/article.rb +0 -34
  60. data/spec/fixtures/more/card.rb +0 -20
  61. data/spec/fixtures/more/course.rb +0 -14
  62. data/spec/fixtures/more/event.rb +0 -6
  63. data/spec/fixtures/more/invoice.rb +0 -17
  64. data/spec/fixtures/more/person.rb +0 -8
  65. data/spec/fixtures/more/question.rb +0 -6
  66. data/spec/fixtures/more/service.rb +0 -12
@@ -0,0 +1,328 @@
1
+ module CouchRest
2
+
3
+ class RecordInvalid < StandardError
4
+ attr_reader :record
5
+ def initialize(record)
6
+ @record = record
7
+ super("Validation failed: #{@self.errors.full_messages.join(", ")}")
8
+ end
9
+ end
10
+
11
+ class Errors
12
+ include Enumerable
13
+
14
+ def initialize(base) # :nodoc:
15
+ @base, @errors = base, {}
16
+ end
17
+
18
+ @@default_error_messages = {
19
+ :inclusion => "is not included in the list",
20
+ :exclusion => "is reserved",
21
+ :invalid => "is invalid",
22
+ :confirmation => "doesn't match confirmation",
23
+ :accepted => "must be accepted",
24
+ :empty => "can't be empty",
25
+ :blank => "can't be blank",
26
+ :too_long => "is too long (maximum is %d characters)",
27
+ :too_short => "is too short (minimum is %d characters)",
28
+ :wrong_length => "is the wrong length (should be %d characters)",
29
+ :taken => "has already been taken",
30
+ :not_a_number => "is not a number",
31
+ :greater_than => "must be greater than %d",
32
+ :greater_than_or_equal_to => "must be greater than or equal to %d",
33
+ :equal_to => "must be equal to %d",
34
+ :less_than => "must be less than %d",
35
+ :less_than_or_equal_to => "must be less than or equal to %d",
36
+ :odd => "must be odd",
37
+ :even => "must be even",
38
+ :kind => "must be of kind: %s"
39
+ }
40
+
41
+ cattr_accessor :default_error_messages
42
+
43
+ def add_to_base(msg)
44
+ add(:base, msg)
45
+ end
46
+
47
+ def add(attribute, msg = @@default_error_messages[:invalid])
48
+ @errors[attribute.to_s] ||= []
49
+ @errors[attribute.to_s] << msg
50
+ end
51
+
52
+ def add_on_empty(attributes, msg = @@default_error_messages[:empty])
53
+ for attr in [attributes].flatten
54
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
55
+ is_empty = value.respond_to?("empty?") ? value.empty? : false
56
+ add(attr, msg) unless !value.nil? && !is_empty
57
+ end
58
+ end
59
+
60
+ def add_on_blank(attributes, msg = @@default_error_messages[:blank])
61
+ for attr in [attributes].flatten
62
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
63
+ add(attr, msg) if value.blank?
64
+ end
65
+ end
66
+
67
+ def invalid?(attribute)
68
+ !@errors[attribute.to_s].nil?
69
+ end
70
+
71
+ def on(attribute)
72
+ errors = @errors[attribute.to_s]
73
+ return nil if errors.nil?
74
+ errors.size == 1 ? errors.first : errors
75
+ end
76
+
77
+ alias :[] :on
78
+
79
+ def on_base
80
+ on(:base)
81
+ end
82
+
83
+ def each
84
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
85
+ end
86
+
87
+ def each_full
88
+ full_messages.each { |msg| yield msg }
89
+ end
90
+
91
+ def full_messages
92
+ full_messages = []
93
+
94
+ @errors.each_key do |attr|
95
+ @errors[attr].each do |msg|
96
+ next if msg.nil?
97
+
98
+ if attr == "base"
99
+ full_messages << msg
100
+ else
101
+ full_messages << @base.class.human_attribute_name(attr) + " " + msg
102
+ end
103
+ end
104
+ end
105
+ full_messages
106
+ end
107
+
108
+ def empty?
109
+ @errors.empty?
110
+ end
111
+
112
+ def clear
113
+ @errors = {}
114
+ end
115
+
116
+ def size
117
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
118
+ end
119
+
120
+ alias_method :count, :size
121
+ alias_method :length, :size
122
+
123
+ def to_xml(options={})
124
+ options[:root] ||= "errors"
125
+ options[:indent] ||= 2
126
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
127
+
128
+ options[:builder].instruct! unless options.delete(:skip_instruct)
129
+ options[:builder].errors do |e|
130
+ full_messages.each { |msg| e.error(msg) }
131
+ end
132
+ end
133
+ end
134
+
135
+ module Validations
136
+ VALIDATIONS = %w( validate validate_on_create validate_on_update )
137
+
138
+ def self.included(base) # :nodoc:
139
+ base.extend ClassMethods
140
+ base.class_eval do
141
+ alias_method_chain :save, :validation
142
+ # alias_method_chain :save!, :validation
143
+ # alias_method_chain :update_attributes, :validation_skipping
144
+ end
145
+
146
+ base.send :include, ActiveSupport::Callbacks
147
+ base.define_callbacks *VALIDATIONS
148
+ end
149
+
150
+ module ClassMethods
151
+ DEFAULT_VALIDATION_OPTIONS = {
152
+ :on => :save,
153
+ :allow_nil => false,
154
+ :allow_blank => false,
155
+ :message => nil
156
+ }.freeze
157
+
158
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
159
+ ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
160
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
161
+ :odd => 'odd?', :even => 'even?' }.freeze
162
+
163
+ def validates_each(*attrs)
164
+ options = attrs.extract_options!.symbolize_keys
165
+ attrs = attrs.flatten
166
+
167
+ send(validation_method(options[:on] || :save), options) do |record|
168
+ attrs.each do |attr|
169
+ value = record.send(attr)
170
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
171
+ yield record, attr, value
172
+ end
173
+ end
174
+ end
175
+
176
+ def validates_presence_of(*attr_names)
177
+ configuration = { :message => CouchRest::Errors.default_error_messages[:blank], :on => :save }
178
+ configuration.update(attr_names.extract_options!)
179
+
180
+ send(validation_method(configuration[:on]), configuration) do |record|
181
+ record.errors.add_on_blank(attr_names, configuration[:message])
182
+ end
183
+ end
184
+
185
+ def validates_format_of(*attr_names)
186
+ configuration = { :message => CouchRest::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
187
+ configuration.update(attr_names.extract_options!)
188
+
189
+ raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
190
+
191
+ validates_each(attr_names, configuration) do |record, attr_name, value|
192
+ record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
193
+ end
194
+ end
195
+
196
+ def validates_kind_of(*attr_names)
197
+ configuration = { :message => CouchRest::Errors.default_error_messages[:kind], :on => :save, :is => nil, :allow_nil => true }
198
+ configuration.update(attr_names.extract_options!)
199
+
200
+ raise(ArgumentError, "A class must be supplied as the :as option of the configuration hash") unless configuration[:is].is_a?(Class)
201
+
202
+ validates_each(attr_names, configuration) do |record, attr_name, value|
203
+ record.errors.add(attr_name, configuration[:message] % configuration[:is]) unless value.class == configuration[:is]
204
+ end
205
+ end
206
+
207
+ def validates_numericality_of(*attr_names)
208
+ configuration = { :message => CouchRest::Errors.default_error_messages[:not_a_number], :only_integer => false, :allow_nil => false, :on => :save }
209
+ configuration.update(attr_names.extract_options!)
210
+
211
+ numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
212
+
213
+ validates_each(attr_names, configuration) do |record, attr_name, value|
214
+ next if value.nil? and configuration[:allow_nil]
215
+ if configuration[:only_integer]
216
+ unless value.to_s =~ /\A[+-]?\d+\Z/
217
+ record.errors.add(attr_name, configuration[:message] % value)
218
+ next
219
+ end
220
+ else
221
+ begin
222
+ Kernel.Float(value)
223
+ rescue ArgumentError, TypeError
224
+ record.errors.add(attr_name, configuration[:message] % value)
225
+ next
226
+ end
227
+ end
228
+
229
+ numericality_options.each do |option|
230
+ case option
231
+ when :odd, :even
232
+ record.errors.add(attr_name, CouchRest::Errors.default_error_messages[option] % value) unless value.send(ALL_NUMERICALITY_CHECKS[option])
233
+ else
234
+
235
+ record.errors.add(attr_name, CouchRest::Errors.default_error_messages[option] % configuration[option]) unless value.send(ALL_NUMERICALITY_CHECKS[option], configuration[option])
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ def validates_inclusion_of(*attr_names)
242
+ configuration = { :message => CouchRest::Errors.default_error_messages[:inclusion], :on => :save }
243
+ configuration.update(attr_name.extract_options!)
244
+
245
+ enum = configuration[:in] || configuration[:within]
246
+
247
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
248
+
249
+ validates_each(attr_names, configuration) do |record, attr_name, value|
250
+ record.errors.add(attr_name, configuration[:message]) if enum.include?(value)
251
+ end
252
+ end
253
+
254
+ def create!(attributes = nil, &block)
255
+ if attributes.is_a?(Array)
256
+ attributes.collect { |attr| create!(attr, &block) }
257
+ else
258
+ object = new(attributes)
259
+ yield(object) if block_given?
260
+ object.save!
261
+ object
262
+ end
263
+ end
264
+
265
+ private
266
+ def validation_method(on)
267
+ case on
268
+ when :save then :validate
269
+ when :create then :validate_on_create
270
+ when :update then :validate_on_update
271
+ end
272
+ end
273
+ end
274
+
275
+ def save_with_validation(perform_validation = true)
276
+ if perform_validation && valid? || !perform_validation
277
+ save_without_validation
278
+ else
279
+ false
280
+ end
281
+ end
282
+
283
+ def save_with_validation!
284
+ if valid?
285
+ save_without_validation!
286
+ else
287
+ raise RecordInvalid.new(self)
288
+ end
289
+ end
290
+
291
+ def update_attribute_with_validation_skipping(name, value)
292
+ send(name.to_s + '=', value)
293
+ save(false)
294
+ end
295
+
296
+ def valid?
297
+ errors.clear
298
+
299
+ run_callbacks(:validate)
300
+ validate
301
+
302
+ if new_record?
303
+ run_callbacks(:validate_on_create)
304
+ validate_on_create
305
+ else
306
+ run_callbacks(:validate_on_update)
307
+ validate_on_update
308
+ end
309
+
310
+ errors.empty?
311
+ end
312
+
313
+ def errors
314
+ @errors ||= Errors.new(self)
315
+ end
316
+
317
+ protected
318
+
319
+ def validate #:doc:
320
+ end
321
+
322
+ def validate_on_create #:doc:
323
+ end
324
+
325
+ def validate_on_update # :doc:
326
+ end
327
+ end
328
+ end
@@ -1,6 +1,3 @@
1
- require File.join(File.dirname(__FILE__), 'support', 'class')
2
- require File.join(File.dirname(__FILE__), 'support', 'blank')
3
-
4
1
  # This file must be loaded after the JSON gem and any other library that beats up the Time class.
5
2
  class Time
6
3
  # This date format sorts lexicographically
@@ -24,96 +21,4 @@ class Time
24
21
  # rescue
25
22
  # fallback
26
23
  # end
27
- end
28
-
29
- # Monkey patch for faster net/http io
30
- if RUBY_VERSION.to_f < 1.9
31
- class Net::BufferedIO #:nodoc:
32
- alias :old_rbuf_fill :rbuf_fill
33
- def rbuf_fill
34
- if @io.respond_to?(:read_nonblock)
35
- begin
36
- @rbuf << @io.read_nonblock(65536)
37
- rescue Errno::EWOULDBLOCK
38
- if IO.select([@io], nil, nil, @read_timeout)
39
- retry
40
- else
41
- raise Timeout::TimeoutError
42
- end
43
- end
44
- else
45
- timeout(@read_timeout) do
46
- @rbuf << @io.sysread(65536)
47
- end
48
- end
49
- end
50
- end
51
- end
52
-
53
- module RestClient
54
- def self.copy(url, headers={})
55
- Request.execute(:method => :copy,
56
- :url => url,
57
- :headers => headers)
58
- end
59
-
60
- def self.move(url, headers={})
61
- Request.execute(:method => :move,
62
- :url => url,
63
- :headers => headers)
64
- end
65
-
66
- # class Request
67
- #
68
- # def establish_connection(uri)
69
- # Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
70
- # p net_http_class
71
- # net = net_http_class.new(uri.host, uri.port)
72
- # net.use_ssl = uri.is_a?(URI::HTTPS)
73
- # net.verify_mode = OpenSSL::SSL::VERIFY_NONE
74
- # Thread.current[:connection] = net
75
- # Thread.current[:connection].start
76
- # Thread.current[:connection]
77
- # end
78
- #
79
- # def transmit(uri, req, payload)
80
- # setup_credentials(req)
81
- #
82
- # Thread.current[:host] ||= uri.host
83
- # Thread.current[:port] ||= uri.port
84
- #
85
- # if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
86
- # p "establishing a connection"
87
- # establish_connection(uri)
88
- # end
89
- #
90
- # display_log request_log
91
- # http = Thread.current[:connection]
92
- # http.read_timeout = @timeout if @timeout
93
- #
94
- # begin
95
- # res = http.request(req, payload)
96
- # rescue
97
- # p "Net::HTTP connection failed, reconnecting"
98
- # establish_connection(uri)
99
- # http = Thread.current[:connection]
100
- # require 'ruby-debug'
101
- # debugger
102
- # req.body_stream = nil
103
- #
104
- # res = http.request(req, payload)
105
- # display_log response_log(res)
106
- # result res
107
- # else
108
- # display_log response_log(res)
109
- # process_result res
110
- # end
111
- #
112
- # rescue EOFError
113
- # raise RestClient::ServerBrokeConnection
114
- # rescue Timeout::Error
115
- # raise RestClient::RequestTimeout
116
- # end
117
- # end
118
-
119
24
  end
data/lib/couchrest.rb CHANGED
@@ -13,61 +13,38 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require "rubygems"
16
- gem 'json'
17
16
  require 'json'
18
- gem 'rest-client'
19
17
  require 'rest_client'
18
+ require File.expand_path(File.dirname(__FILE__)) + '/couchrest/core/validations'
19
+
20
+ # require 'extlib'
20
21
 
21
22
  $:.unshift File.dirname(__FILE__) unless
22
23
  $:.include?(File.dirname(__FILE__)) ||
23
24
  $:.include?(File.expand_path(File.dirname(__FILE__)))
24
25
 
25
- $COUCHREST_DEBUG ||= false
26
26
 
27
27
  require 'couchrest/monkeypatches'
28
28
 
29
29
  # = CouchDB, close to the metal
30
30
  module CouchRest
31
- VERSION = '0.2.1' unless self.const_defined?("VERSION")
31
+ VERSION = '0.12.2'
32
32
 
33
33
  autoload :Server, 'couchrest/core/server'
34
34
  autoload :Database, 'couchrest/core/database'
35
- autoload :Response, 'couchrest/core/response'
36
35
  autoload :Document, 'couchrest/core/document'
37
- autoload :Design, 'couchrest/core/design'
36
+ autoload :Design, 'couchrest/core/design'
38
37
  autoload :View, 'couchrest/core/view'
39
38
  autoload :Model, 'couchrest/core/model'
40
39
  autoload :Pager, 'couchrest/helper/pager'
41
40
  autoload :FileManager, 'couchrest/helper/file_manager'
42
41
  autoload :Streamer, 'couchrest/helper/streamer'
43
42
 
44
- autoload :ExtendedDocument, 'couchrest/more/extended_document'
45
- autoload :CastedModel, 'couchrest/more/casted_model'
46
-
47
- require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
48
-
49
43
  # The CouchRest module methods handle the basic JSON serialization
50
44
  # and deserialization, as well as query parameters. The module also includes
51
45
  # some helpers for tasks like instantiating a new Database or Server instance.
52
46
  class << self
53
47
 
54
- # extracted from Extlib
55
- #
56
- # Constantize tries to find a declared constant with the name specified
57
- # in the string. It raises a NameError when the name is not in CamelCase
58
- # or is not initialized.
59
- #
60
- # @example
61
- # "Module".constantize #=> Module
62
- # "Class".constantize #=> Class
63
- def constantize(camel_cased_word)
64
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
65
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
66
- end
67
-
68
- Object.module_eval("::#{$1}", __FILE__, __LINE__)
69
- end
70
-
71
48
  # todo, make this parse the url and instantiate a Server or Database instance
72
49
  # depending on the specificity.
73
50
  def new(*opts)
@@ -125,42 +102,18 @@ module CouchRest
125
102
  cr.database(parsed[:database])
126
103
  end
127
104
 
128
- def put(uri, doc = nil)
105
+ def put uri, doc = nil
129
106
  payload = doc.to_json if doc
130
- begin
131
- JSON.parse(RestClient.put(uri, payload))
132
- rescue Exception => e
133
- if $COUCHREST_DEBUG == true
134
- raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
135
- else
136
- raise e
137
- end
138
- end
107
+ JSON.parse(RestClient.put(uri, payload))
139
108
  end
140
109
 
141
- def get(uri)
142
- begin
143
- JSON.parse(RestClient.get(uri), :max_nesting => false)
144
- rescue => e
145
- if $COUCHREST_DEBUG == true
146
- raise "Error while sending a GET request #{uri}\n: #{e}"
147
- else
148
- raise e
149
- end
150
- end
110
+ def get uri
111
+ JSON.parse(RestClient.get(uri), :max_nesting => false)
151
112
  end
152
113
 
153
114
  def post uri, doc = nil
154
115
  payload = doc.to_json if doc
155
- begin
156
- JSON.parse(RestClient.post(uri, payload))
157
- rescue Exception => e
158
- if $COUCHREST_DEBUG == true
159
- raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
160
- else
161
- raise e
162
- end
163
- end
116
+ JSON.parse(RestClient.post(uri, payload))
164
117
  end
165
118
 
166
119
  def delete uri