samlown-couchrest 0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +46 -0
  3. data/Rakefile +67 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/history.txt +114 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/adapters/restclient.rb +35 -0
  20. data/lib/couchrest/core/database.rb +377 -0
  21. data/lib/couchrest/core/design.rb +79 -0
  22. data/lib/couchrest/core/document.rb +84 -0
  23. data/lib/couchrest/core/http_abstraction.rb +48 -0
  24. data/lib/couchrest/core/response.rb +16 -0
  25. data/lib/couchrest/core/rest_api.rb +49 -0
  26. data/lib/couchrest/core/server.rb +88 -0
  27. data/lib/couchrest/core/view.rb +4 -0
  28. data/lib/couchrest/helper/pager.rb +103 -0
  29. data/lib/couchrest/helper/streamer.rb +51 -0
  30. data/lib/couchrest/helper/upgrade.rb +51 -0
  31. data/lib/couchrest/middlewares/logger.rb +263 -0
  32. data/lib/couchrest/mixins/attachments.rb +31 -0
  33. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  34. data/lib/couchrest/mixins/callbacks.rb +532 -0
  35. data/lib/couchrest/mixins/class_proxy.rb +124 -0
  36. data/lib/couchrest/mixins/collection.rb +260 -0
  37. data/lib/couchrest/mixins/design_doc.rb +103 -0
  38. data/lib/couchrest/mixins/document_queries.rb +80 -0
  39. data/lib/couchrest/mixins/extended_attachments.rb +70 -0
  40. data/lib/couchrest/mixins/extended_document_mixins.rb +9 -0
  41. data/lib/couchrest/mixins/properties.rb +154 -0
  42. data/lib/couchrest/mixins/validation.rb +246 -0
  43. data/lib/couchrest/mixins/views.rb +173 -0
  44. data/lib/couchrest/mixins.rb +4 -0
  45. data/lib/couchrest/monkeypatches.rb +113 -0
  46. data/lib/couchrest/more/casted_model.rb +58 -0
  47. data/lib/couchrest/more/extended_document.rb +310 -0
  48. data/lib/couchrest/more/property.rb +50 -0
  49. data/lib/couchrest/more/typecast.rb +175 -0
  50. data/lib/couchrest/support/blank.rb +42 -0
  51. data/lib/couchrest/support/class.rb +190 -0
  52. data/lib/couchrest/support/rails.rb +42 -0
  53. data/lib/couchrest/validation/auto_validate.rb +157 -0
  54. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  55. data/lib/couchrest/validation/validation_errors.rb +125 -0
  56. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  57. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  58. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  59. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  60. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  61. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  62. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  63. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  64. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  65. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  66. data/lib/couchrest.rb +162 -0
  67. data/spec/couchrest/core/couchrest_spec.rb +184 -0
  68. data/spec/couchrest/core/database_spec.rb +840 -0
  69. data/spec/couchrest/core/design_spec.rb +138 -0
  70. data/spec/couchrest/core/document_spec.rb +275 -0
  71. data/spec/couchrest/core/server_spec.rb +35 -0
  72. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  73. data/spec/couchrest/helpers/streamer_spec.rb +52 -0
  74. data/spec/couchrest/more/attribute_protection_spec.rb +150 -0
  75. data/spec/couchrest/more/casted_extended_doc_spec.rb +79 -0
  76. data/spec/couchrest/more/casted_model_spec.rb +406 -0
  77. data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
  78. data/spec/couchrest/more/extended_doc_inherited_spec.rb +40 -0
  79. data/spec/couchrest/more/extended_doc_spec.rb +797 -0
  80. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  81. data/spec/couchrest/more/extended_doc_view_spec.rb +456 -0
  82. data/spec/couchrest/more/property_spec.rb +628 -0
  83. data/spec/fixtures/attachments/README +3 -0
  84. data/spec/fixtures/attachments/couchdb.png +0 -0
  85. data/spec/fixtures/attachments/test.html +11 -0
  86. data/spec/fixtures/more/article.rb +35 -0
  87. data/spec/fixtures/more/card.rb +22 -0
  88. data/spec/fixtures/more/cat.rb +20 -0
  89. data/spec/fixtures/more/course.rb +22 -0
  90. data/spec/fixtures/more/event.rb +8 -0
  91. data/spec/fixtures/more/invoice.rb +17 -0
  92. data/spec/fixtures/more/person.rb +9 -0
  93. data/spec/fixtures/more/question.rb +6 -0
  94. data/spec/fixtures/more/service.rb +12 -0
  95. data/spec/fixtures/more/user.rb +22 -0
  96. data/spec/fixtures/views/lib.js +3 -0
  97. data/spec/fixtures/views/test_view/lib.js +3 -0
  98. data/spec/fixtures/views/test_view/only-map.js +4 -0
  99. data/spec/fixtures/views/test_view/test-map.js +3 -0
  100. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  101. data/spec/spec.opts +6 -0
  102. data/spec/spec_helper.rb +49 -0
  103. data/utils/remap.rb +27 -0
  104. data/utils/subset.rb +30 -0
  105. metadata +223 -0
@@ -0,0 +1,310 @@
1
+ require 'mime/types'
2
+ require File.join(File.dirname(__FILE__), "property")
3
+ require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
4
+ require "enumerator"
5
+
6
+ module CouchRest
7
+
8
+ # Same as CouchRest::Document but with properties and validations
9
+ class ExtendedDocument < Document
10
+ include CouchRest::Callbacks
11
+ include CouchRest::Mixins::DocumentQueries
12
+ include CouchRest::Mixins::Views
13
+ include CouchRest::Mixins::DesignDoc
14
+ include CouchRest::Mixins::ExtendedAttachments
15
+ include CouchRest::Mixins::ClassProxy
16
+ include CouchRest::Mixins::Collection
17
+ include CouchRest::Mixins::AttributeProtection
18
+
19
+ def self.subclasses
20
+ @subclasses ||= []
21
+ end
22
+
23
+ def self.inherited(subklass)
24
+ super
25
+ subklass.send(:include, CouchRest::Mixins::Properties)
26
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
27
+ def self.inherited(subklass)
28
+ super
29
+ subklass.properties = self.properties.dup
30
+ end
31
+ EOS
32
+ subclasses << subklass
33
+ end
34
+
35
+ # Accessors
36
+ attr_accessor :casted_by
37
+
38
+ # Callbacks
39
+ define_callbacks :create, "result == :halt"
40
+ define_callbacks :save, "result == :halt"
41
+ define_callbacks :update, "result == :halt"
42
+ define_callbacks :destroy, "result == :halt"
43
+
44
+ # Creates a new instance, bypassing attribute protection
45
+ #
46
+ # ==== Returns
47
+ # a document instance
48
+ def self.create_from_database(passed_keys={})
49
+ new(passed_keys, :directly_set_attributes => true)
50
+ end
51
+
52
+ def initialize(passed_keys={}, options={})
53
+ apply_defaults # defined in CouchRest::Mixins::Properties
54
+ remove_protected_attributes(passed_keys) unless options[:directly_set_attributes]
55
+ directly_set_attributes(passed_keys) unless passed_keys.nil?
56
+ super(passed_keys)
57
+ cast_keys # defined in CouchRest::Mixins::Properties
58
+ unless self['_id'] && self['_rev']
59
+ self['couchrest-type'] = self.class.to_s
60
+ end
61
+ after_initialize if respond_to?(:after_initialize)
62
+ end
63
+
64
+ # Defines an instance and save it directly to the database
65
+ #
66
+ # ==== Returns
67
+ # returns the reloaded document
68
+ def self.create(options)
69
+ instance = new(options)
70
+ instance.create
71
+ instance
72
+ end
73
+
74
+ # Defines an instance and save it directly to the database
75
+ #
76
+ # ==== Returns
77
+ # returns the reloaded document or raises an exception
78
+ def self.create!(options)
79
+ instance = new(options)
80
+ instance.create!
81
+ instance
82
+ end
83
+
84
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
85
+ # on the document whenever saving occurs. CouchRest uses a pretty
86
+ # decent time format by default. See Time#to_json
87
+ def self.timestamps!
88
+ class_eval <<-EOS, __FILE__, __LINE__
89
+ property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false)
90
+ property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false)
91
+
92
+ set_callback :save, :before do |object|
93
+ object['updated_at'] = Time.now
94
+ object['created_at'] = object['updated_at'] if object.new?
95
+ end
96
+ EOS
97
+ end
98
+
99
+ # Name a method that will be called before the document is first saved,
100
+ # which returns a string to be used for the document's <tt>_id</tt>.
101
+ # Because CouchDB enforces a constraint that each id must be unique,
102
+ # this can be used to enforce eg: uniq usernames. Note that this id
103
+ # must be globally unique across all document types which share a
104
+ # database, so if you'd like to scope uniqueness to this class, you
105
+ # should use the class name as part of the unique id.
106
+ def self.unique_id method = nil, &block
107
+ if method
108
+ define_method :set_unique_id do
109
+ self['_id'] ||= self.send(method)
110
+ end
111
+ elsif block
112
+ define_method :set_unique_id do
113
+ uniqid = block.call(self)
114
+ raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
115
+ self['_id'] ||= uniqid
116
+ end
117
+ end
118
+ end
119
+
120
+ # Temp solution to make the view_by methods available
121
+ def self.method_missing(m, *args, &block)
122
+ if has_view?(m)
123
+ query = args.shift || {}
124
+ view(m, query, *args, &block)
125
+ else
126
+ super
127
+ end
128
+ end
129
+
130
+ ### instance methods
131
+
132
+ # Returns the Class properties
133
+ #
134
+ # ==== Returns
135
+ # Array:: the list of properties for the instance
136
+ def properties
137
+ self.class.properties
138
+ end
139
+
140
+ # Gets a reference to the actual document in the DB
141
+ # Calls up to the next document if there is one,
142
+ # Otherwise we're at the top and we return self
143
+ def base_doc
144
+ return self if base_doc?
145
+ @casted_by.base_doc
146
+ end
147
+
148
+ # Checks if we're the top document
149
+ def base_doc?
150
+ !@casted_by
151
+ end
152
+
153
+ # Takes a hash as argument, and applies the values by using writer methods
154
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
155
+ # missing. In case of error, no attributes are changed.
156
+ def update_attributes_without_saving(hash)
157
+ # remove attributes that cannot be updated, silently ignoring them
158
+ # which matches Rails behavior when, for instance, setting created_at.
159
+ # make a copy, we don't want to change arguments
160
+ attrs = hash.dup
161
+ %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
162
+ check_properties_exist(attrs)
163
+ set_attributes(attrs)
164
+ end
165
+ alias :attributes= :update_attributes_without_saving
166
+
167
+ # Takes a hash as argument, and applies the values by using writer methods
168
+ # for each key. Raises a NoMethodError if the corresponding methods are
169
+ # missing. In case of error, no attributes are changed.
170
+ def update_attributes(hash)
171
+ update_attributes_without_saving hash
172
+ save
173
+ end
174
+
175
+ # for compatibility with old-school frameworks
176
+ alias :new_record? :new?
177
+ alias :new_document? :new?
178
+
179
+ # Trigger the callbacks (before, after, around)
180
+ # and create the document
181
+ # It's important to have a create callback since you can't check if a document
182
+ # was new after you saved it
183
+ #
184
+ # When creating a document, both the create and the save callbacks will be triggered.
185
+ def create(bulk = false)
186
+ caught = catch(:halt) do
187
+ _run_create_callbacks do
188
+ _run_save_callbacks do
189
+ create_without_callbacks(bulk)
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ # unlike save, create returns the newly created document
196
+ def create_without_callbacks(bulk =false)
197
+ raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
198
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
199
+ result = database.save_doc(self, bulk)
200
+ (result["ok"] == true) ? self : false
201
+ end
202
+
203
+ # Creates the document in the db. Raises an exception
204
+ # if the document is not created properly.
205
+ def create!
206
+ raise "#{self.inspect} failed to save" unless self.create
207
+ end
208
+
209
+ # Trigger the callbacks (before, after, around)
210
+ # only if the document isn't new
211
+ def update(bulk = false)
212
+ caught = catch(:halt) do
213
+ if self.new?
214
+ save(bulk)
215
+ else
216
+ _run_update_callbacks do
217
+ _run_save_callbacks do
218
+ save_without_callbacks(bulk)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ # Trigger the callbacks (before, after, around)
226
+ # and save the document
227
+ def save(bulk = false)
228
+ caught = catch(:halt) do
229
+ if self.new?
230
+ _run_save_callbacks do
231
+ save_without_callbacks(bulk)
232
+ end
233
+ else
234
+ update(bulk)
235
+ end
236
+ end
237
+ end
238
+
239
+ # Overridden to set the unique ID.
240
+ # Returns a boolean value
241
+ def save_without_callbacks(bulk = false)
242
+ raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
243
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
244
+ result = database.save_doc(self, bulk)
245
+ mark_as_saved
246
+ result["ok"] == true
247
+ end
248
+
249
+ # Saves the document to the db using save. Raises an exception
250
+ # if the document is not saved properly.
251
+ def save!
252
+ raise "#{self.inspect} failed to save" unless self.save
253
+ true
254
+ end
255
+
256
+ # Deletes the document from the database. Runs the :destroy callbacks.
257
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
258
+ # document to be saved to a new <tt>_id</tt>.
259
+ def destroy(bulk=false)
260
+ caught = catch(:halt) do
261
+ _run_destroy_callbacks do
262
+ result = database.delete_doc(self, bulk)
263
+ if result['ok']
264
+ self.delete('_rev')
265
+ self.delete('_id')
266
+ end
267
+ result['ok']
268
+ end
269
+ end
270
+ end
271
+
272
+ protected
273
+
274
+ # Set document_saved flag on all casted models to true
275
+ def mark_as_saved
276
+ self.each do |key, prop|
277
+ if prop.is_a?(Array)
278
+ prop.each do |item|
279
+ if item.respond_to?(:document_saved)
280
+ item.send(:document_saved=, true)
281
+ end
282
+ end
283
+ elsif prop.respond_to?(:document_saved)
284
+ prop.send(:document_saved=, true)
285
+ end
286
+ end
287
+ end
288
+
289
+ private
290
+
291
+ def check_properties_exist(attrs)
292
+ attrs.each do |attribute_name, attribute_value|
293
+ raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
294
+ end
295
+ end
296
+
297
+ def directly_set_attributes(hash)
298
+ hash.each do |attribute_name, attribute_value|
299
+ if self.respond_to?("#{attribute_name}=")
300
+ self.send("#{attribute_name}=", hash.delete(attribute_name))
301
+ end
302
+ end
303
+ end
304
+
305
+ def set_attributes(hash)
306
+ attrs = remove_protected_attributes(hash)
307
+ directly_set_attributes(attrs)
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,50 @@
1
+ module CouchRest
2
+
3
+ # Basic attribute support for adding getter/setter + validation
4
+ class Property
5
+ attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
6
+
7
+ # attribute to define
8
+ def initialize(name, type = nil, options = {})
9
+ @name = name.to_s
10
+ parse_type(type)
11
+ parse_options(options)
12
+ self
13
+ end
14
+
15
+ private
16
+
17
+ def parse_type(type)
18
+ if type.nil?
19
+ @type = String
20
+ elsif type.is_a?(Array) && type.empty?
21
+ @type = [Object]
22
+ else
23
+ base_type = type.is_a?(Array) ? type.first : type
24
+ if base_type.is_a?(String)
25
+ if base_type.downcase == 'boolean'
26
+ base_type = TrueClass
27
+ else
28
+ begin
29
+ base_type = ::CouchRest.constantize(base_type)
30
+ rescue # leave base type as a string and convert in more/typecast
31
+ end
32
+ end
33
+ end
34
+ @type = type.is_a?(Array) ? [base_type] : base_type
35
+ end
36
+ end
37
+
38
+ def parse_options(options)
39
+ return if options.empty?
40
+ @validation_format = options.delete(:format) if options[:format]
41
+ @read_only = options.delete(:read_only) if options[:read_only]
42
+ @alias = options.delete(:alias) if options[:alias]
43
+ @default = options.delete(:default) unless options[:default].nil?
44
+ @casted = options[:casted] ? true : false
45
+ @init_method = options[:init_method] ? options.delete(:init_method) : 'new'
46
+ @options = options
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,175 @@
1
+ require 'time'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require File.join(File.dirname(__FILE__), '..', 'more', 'property')
5
+
6
+ class Time
7
+ # returns a local time value much faster than Time.parse
8
+ def self.mktime_with_offset(string)
9
+ string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
10
+ # $1 = year
11
+ # $2 = month
12
+ # $3 = day
13
+ # $4 = hours
14
+ # $5 = minutes
15
+ # $6 = seconds
16
+ # $7 = time zone direction
17
+ # $8 = tz difference
18
+ # utc time with wrong TZ info:
19
+ time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
20
+ tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
21
+ time + tz_difference + zone_offset(time.zone)
22
+ end
23
+ end
24
+
25
+ module CouchRest
26
+ module More
27
+ module Typecast
28
+
29
+ def typecast_value(value, klass, init_method)
30
+ return nil if value.nil?
31
+ klass = ::CouchRest.constantize(klass) unless klass.is_a?(Class)
32
+ if value.instance_of?(klass) || klass == Object
33
+ value
34
+ elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
35
+ send('typecast_to_'+klass.to_s.downcase, value)
36
+ else
37
+ # Allow the init_method to be defined as a Proc for advanced conversion
38
+ init_method.is_a?(Proc) ? init_method.call(value) : klass.send(init_method, value)
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ # Typecast a value to an Integer
45
+ def typecast_to_integer(value)
46
+ typecast_to_numeric(value, :to_i)
47
+ end
48
+
49
+ # Typecast a value to a String
50
+ def typecast_to_string(value)
51
+ value.to_s
52
+ end
53
+
54
+ # Typecast a value to a true or false
55
+ def typecast_to_trueclass(value)
56
+ if value.kind_of?(Integer)
57
+ return true if value == 1
58
+ return false if value == 0
59
+ elsif value.respond_to?(:to_s)
60
+ return true if %w[ true 1 t ].include?(value.to_s.downcase)
61
+ return false if %w[ false 0 f ].include?(value.to_s.downcase)
62
+ end
63
+ value
64
+ end
65
+
66
+ # Typecast a value to a BigDecimal
67
+ def typecast_to_bigdecimal(value)
68
+ if value.kind_of?(Integer)
69
+ value.to_s.to_d
70
+ else
71
+ typecast_to_numeric(value, :to_d)
72
+ end
73
+ end
74
+
75
+ # Typecast a value to a Float
76
+ def typecast_to_float(value)
77
+ typecast_to_numeric(value, :to_f)
78
+ end
79
+
80
+ # Match numeric string
81
+ def typecast_to_numeric(value, method)
82
+ if value.respond_to?(:to_str)
83
+ if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
84
+ $1.send(method)
85
+ else
86
+ value
87
+ end
88
+ elsif value.respond_to?(method)
89
+ value.send(method)
90
+ else
91
+ value
92
+ end
93
+ end
94
+
95
+ # Typecasts an arbitrary value to a DateTime.
96
+ # Handles both Hashes and DateTime instances.
97
+ # This is slow!! Use Time instead.
98
+ def typecast_to_datetime(value)
99
+ if value.is_a?(Hash)
100
+ typecast_hash_to_datetime(value)
101
+ else
102
+ DateTime.parse(value.to_s)
103
+ end
104
+ rescue ArgumentError
105
+ value
106
+ end
107
+
108
+ # Typecasts an arbitrary value to a Date
109
+ # Handles both Hashes and Date instances.
110
+ def typecast_to_date(value)
111
+ if value.is_a?(Hash)
112
+ typecast_hash_to_date(value)
113
+ elsif value.is_a?(Time) # sometimes people think date is time!
114
+ value.to_date
115
+ elsif value.to_s =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})/
116
+ # Faster than parsing the date
117
+ Date.new($1.to_i, $2.to_i, $3.to_i)
118
+ else
119
+ Date.parse(value)
120
+ end
121
+ rescue ArgumentError
122
+ value
123
+ end
124
+
125
+ # Typecasts an arbitrary value to a Time
126
+ # Handles both Hashes and Time instances.
127
+ def typecast_to_time(value)
128
+ if value.is_a?(Hash)
129
+ typecast_hash_to_time(value)
130
+ else
131
+ Time.mktime_with_offset(value.to_s)
132
+ end
133
+ rescue ArgumentError
134
+ value
135
+ rescue TypeError
136
+ value
137
+ end
138
+
139
+ # Creates a DateTime instance from a Hash with keys :year, :month, :day,
140
+ # :hour, :min, :sec
141
+ def typecast_hash_to_datetime(value)
142
+ DateTime.new(*extract_time(value))
143
+ end
144
+
145
+ # Creates a Date instance from a Hash with keys :year, :month, :day
146
+ def typecast_hash_to_date(value)
147
+ Date.new(*extract_time(value)[0, 3])
148
+ end
149
+
150
+ # Creates a Time instance from a Hash with keys :year, :month, :day,
151
+ # :hour, :min, :sec
152
+ def typecast_hash_to_time(value)
153
+ Time.local(*extract_time(value))
154
+ end
155
+
156
+ # Extracts the given args from the hash. If a value does not exist, it
157
+ # uses the value of Time.now.
158
+ def extract_time(value)
159
+ now = Time.now
160
+ [:year, :month, :day, :hour, :min, :sec].map do |segment|
161
+ typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
162
+ end
163
+ end
164
+
165
+ # Typecast a value to a Class
166
+ def typecast_to_class(value)
167
+ ::CouchRest.constantize(value.to_s)
168
+ rescue NameError
169
+ value
170
+ end
171
+
172
+ end
173
+ end
174
+ end
175
+
@@ -0,0 +1,42 @@
1
+ # blank? methods for several different class types
2
+ class Object
3
+ # Returns true if the object is nil or empty (if applicable)
4
+ def blank?
5
+ nil? || (respond_to?(:empty?) && empty?)
6
+ end
7
+ end # class Object
8
+
9
+ class Numeric
10
+ # Numerics can't be blank
11
+ def blank?
12
+ false
13
+ end
14
+ end # class Numeric
15
+
16
+ class NilClass
17
+ # Nils are always blank
18
+ def blank?
19
+ true
20
+ end
21
+ end # class NilClass
22
+
23
+ class TrueClass
24
+ # True is not blank.
25
+ def blank?
26
+ false
27
+ end
28
+ end # class TrueClass
29
+
30
+ class FalseClass
31
+ # False is always blank.
32
+ def blank?
33
+ true
34
+ end
35
+ end # class FalseClass
36
+
37
+ class String
38
+ # Strips out whitespace then tests if the string is empty.
39
+ def blank?
40
+ strip.empty?
41
+ end
42
+ end # class String