samflores-couchrest 0.2.1 → 0.12.3

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.
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