couchrest_model 1.0.0.beta8 → 1.0.0

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 (44) hide show
  1. data/.gitignore +9 -0
  2. data/{spec/spec.opts → .rspec} +0 -1
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +77 -0
  5. data/README.md +144 -57
  6. data/Rakefile +12 -43
  7. data/VERSION +1 -0
  8. data/couchrest_model.gemspec +35 -0
  9. data/history.txt +23 -1
  10. data/init.rb +1 -0
  11. data/lib/couchrest/model/associations.rb +17 -1
  12. data/lib/couchrest/model/base.rb +5 -5
  13. data/lib/couchrest/model/casted_model.rb +2 -2
  14. data/lib/couchrest/model/class_proxy.rb +6 -0
  15. data/lib/couchrest/model/collection.rb +3 -0
  16. data/lib/couchrest/model/configuration.rb +51 -0
  17. data/lib/couchrest/model/design_doc.rb +2 -5
  18. data/lib/couchrest/model/document_queries.rb +20 -3
  19. data/lib/couchrest/model/persistence.rb +15 -1
  20. data/lib/couchrest/model/properties.rb +97 -23
  21. data/lib/couchrest/model/property.rb +5 -4
  22. data/lib/couchrest/model/property_protection.rb +71 -0
  23. data/lib/couchrest/model/typecast.rb +12 -7
  24. data/lib/couchrest/model/view.rb +190 -0
  25. data/lib/couchrest/model/views.rb +3 -3
  26. data/lib/couchrest/model.rb +1 -1
  27. data/lib/couchrest/railtie.rb +3 -2
  28. data/lib/couchrest_model.rb +7 -14
  29. data/lib/rails/generators/couchrest_model/model/model_generator.rb +2 -1
  30. data/spec/.gitignore +1 -0
  31. data/spec/couchrest/base_spec.rb +3 -3
  32. data/spec/couchrest/casted_model_spec.rb +63 -49
  33. data/spec/couchrest/class_proxy_spec.rb +6 -0
  34. data/spec/couchrest/configuration_spec.rb +78 -0
  35. data/spec/couchrest/persistence_spec.rb +10 -4
  36. data/spec/couchrest/{attribute_protection_spec.rb → property_protection_spec.rb} +29 -2
  37. data/spec/couchrest/property_spec.rb +61 -0
  38. data/spec/couchrest/subclass_spec.rb +2 -2
  39. data/spec/couchrest/view_spec.rb +6 -0
  40. data/spec/fixtures/more/article.rb +1 -1
  41. data/spec/spec_helper.rb +4 -3
  42. metadata +96 -32
  43. data/lib/couchrest/model/attribute_protection.rb +0 -74
  44. data/lib/couchrest/model/attributes.rb +0 -75
@@ -4,6 +4,7 @@ module CouchRest
4
4
 
5
5
  extend ActiveModel::Naming
6
6
 
7
+ include CouchRest::Model::Configuration
7
8
  include CouchRest::Model::Persistence
8
9
  include CouchRest::Model::Callbacks
9
10
  include CouchRest::Model::DocumentQueries
@@ -12,8 +13,7 @@ module CouchRest
12
13
  include CouchRest::Model::ExtendedAttachments
13
14
  include CouchRest::Model::ClassProxy
14
15
  include CouchRest::Model::Collection
15
- include CouchRest::Model::AttributeProtection
16
- include CouchRest::Model::Attributes
16
+ include CouchRest::Model::PropertyProtection
17
17
  include CouchRest::Model::Associations
18
18
  include CouchRest::Model::Validations
19
19
 
@@ -37,7 +37,7 @@ module CouchRest
37
37
 
38
38
  # Accessors
39
39
  attr_accessor :casted_by
40
-
40
+
41
41
 
42
42
  # Instantiate a new CouchRest::Model::Base by preparing all properties
43
43
  # using the provided document hash.
@@ -47,10 +47,10 @@ module CouchRest
47
47
  # * :directly_set_attributes: true when data comes directly from database
48
48
  #
49
49
  def initialize(doc = {}, options = {})
50
- prepare_all_attributes(doc, options)
50
+ doc = prepare_all_attributes(doc, options)
51
51
  super(doc)
52
52
  unless self['_id'] && self['_rev']
53
- self['couchrest-type'] = self.class.to_s
53
+ self[self.model_type_key] = self.class.to_s
54
54
  end
55
55
  after_initialize if respond_to?(:after_initialize)
56
56
  end
@@ -4,10 +4,10 @@ module CouchRest::Model
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- include CouchRest::Model::AttributeProtection
8
- include CouchRest::Model::Attributes
7
+ include CouchRest::Model::Configuration
9
8
  include CouchRest::Model::Callbacks
10
9
  include CouchRest::Model::Properties
10
+ include CouchRest::Model::PropertyProtection
11
11
  include CouchRest::Model::Associations
12
12
  include CouchRest::Model::Validations
13
13
  attr_accessor :casted_by
@@ -75,6 +75,12 @@ module CouchRest
75
75
  doc
76
76
  end
77
77
 
78
+ def last(opts = {})
79
+ doc = @klass.last({:database => @database}.merge(opts))
80
+ doc.database = @database if doc && doc.respond_to?(:database)
81
+ doc
82
+ end
83
+
78
84
  def get(id)
79
85
  doc = @klass.get(id, @database)
80
86
  doc.database = @database if doc && doc.respond_to?(:database)
@@ -82,6 +82,7 @@ module CouchRest
82
82
  design_doc['views'][view_name.to_s] &&
83
83
  design_doc['views'][view_name.to_s]["couchrest-defaults"]) || {}
84
84
  view_options = default_view_options.merge(options)
85
+ view_options.delete(:database)
85
86
 
86
87
  [design_doc, view_name, view_options]
87
88
  end
@@ -94,6 +95,8 @@ module CouchRest
94
95
  raise ArgumentError, 'search_name is required' if search_name.nil?
95
96
 
96
97
  search_options = options.clone
98
+ search_options.delete(:database)
99
+
97
100
  [design_doc, search_name, search_options]
98
101
  end
99
102
 
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+
3
+ # CouchRest Model Configuration support, stolen from Carrierwave by jnicklas
4
+ # http://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/uploader/configuration.rb
5
+
6
+ module Model
7
+ module Configuration
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ add_config :model_type_key
12
+ add_config :mass_assign_any_attribute
13
+
14
+ configure do |config|
15
+ config.model_type_key = 'couchrest-type' # 'model'?
16
+ config.mass_assign_any_attribute = false
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ def add_config(name)
23
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
24
+ def self.#{name}(value=nil)
25
+ @#{name} = value if value
26
+ return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
27
+ name = superclass.#{name}
28
+ return nil if name.nil? && !instance_variable_defined?("@#{name}")
29
+ @#{name} = name && !name.is_a?(Module) && !name.is_a?(Symbol) && !name.is_a?(Numeric) && !name.is_a?(TrueClass) && !name.is_a?(FalseClass) ? name.dup : name
30
+ end
31
+
32
+ def self.#{name}=(value)
33
+ @#{name} = value
34
+ end
35
+
36
+ def #{name}
37
+ self.class.#{name}
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ def configure
43
+ yield self
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
51
+
@@ -2,10 +2,7 @@
2
2
  module CouchRest
3
3
  module Model
4
4
  module DesignDoc
5
-
6
- def self.included(base)
7
- base.extend(ClassMethods)
8
- end
5
+ extend ActiveSupport::Concern
9
6
 
10
7
  module ClassMethods
11
8
 
@@ -34,7 +31,7 @@ module CouchRest
34
31
  "views" => {
35
32
  'all' => {
36
33
  'map' => "function(doc) {
37
- if (doc['couchrest-type'] == '#{self.to_s}') {
34
+ if (doc['#{self.model_type_key}'] == '#{self.to_s}') {
38
35
  emit(doc['_id'],1);
39
36
  }
40
37
  }"
@@ -8,21 +8,21 @@ module CouchRest
8
8
 
9
9
  module ClassMethods
10
10
 
11
- # Load all documents that have the "couchrest-type" field equal to the
11
+ # Load all documents that have the model_type_key's field equal to the
12
12
  # name of the current class. Take the standard set of
13
13
  # CouchRest::Database#view options.
14
14
  def all(opts = {}, &block)
15
15
  view(:all, opts, &block)
16
16
  end
17
17
 
18
- # Returns the number of documents that have the "couchrest-type" field
18
+ # Returns the number of documents that have the model_type_key's field
19
19
  # equal to the name of the current class. Takes the standard set of
20
20
  # CouchRest::Database#view options
21
21
  def count(opts = {}, &block)
22
22
  all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
23
23
  end
24
24
 
25
- # Load the first document that have the "couchrest-type" field equal to
25
+ # Load the first document that have the model_type_key's field equal to
26
26
  # the name of the current class.
27
27
  #
28
28
  # ==== Returns
@@ -38,6 +38,22 @@ module CouchRest
38
38
  first_instance.empty? ? nil : first_instance.first
39
39
  end
40
40
 
41
+ # Load the last document that have the model_type_key's field equal to
42
+ # the name of the current class.
43
+ # It's similar to method first, just adds :descending => true
44
+ #
45
+ # ==== Returns
46
+ # Object:: The last object instance available
47
+ # or
48
+ # Nil:: if no instances available
49
+ #
50
+ # ==== Parameters
51
+ # opts<Hash>::
52
+ # View options, see <tt>CouchRest::Database#view</tt> options for more info.
53
+ def last(opts = {})
54
+ first(opts.merge!(:descending => true))
55
+ end
56
+
41
57
  # Load a document from the database by id
42
58
  # No exceptions will be raised if the document isn't found
43
59
  #
@@ -70,6 +86,7 @@ module CouchRest
70
86
  # id<String, Integer>:: Document ID
71
87
  # db<Database>:: optional option to pass a custom database to use
72
88
  def get!(id, db = database)
89
+ raise "Missing or empty document ID" if id.to_s.empty?
73
90
  doc = db.get id
74
91
  create_from_database(doc)
75
92
  end
@@ -62,6 +62,20 @@ module CouchRest
62
62
  end
63
63
  end
64
64
 
65
+ # Update the document's attributes and save. For example:
66
+ #
67
+ # doc.update_attributes :name => "Fred"
68
+ #
69
+ # Is the equivilent of doing the following:
70
+ #
71
+ # doc.attributes = { :name => "Fred" }
72
+ # doc.save
73
+ #
74
+ def update_attributes(hash)
75
+ update_attributes_without_saving hash
76
+ save
77
+ end
78
+
65
79
  protected
66
80
 
67
81
  def perform_validations(options = {})
@@ -83,7 +97,7 @@ module CouchRest
83
97
  # ==== Returns
84
98
  # a document instance
85
99
  def create_from_database(doc = {})
86
- base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
100
+ base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize
87
101
  base.new(doc, :directly_set_attributes => true)
88
102
  end
89
103
 
@@ -2,16 +2,12 @@
2
2
  module CouchRest
3
3
  module Model
4
4
  module Properties
5
-
6
- class IncludeError < StandardError; end
7
-
8
- def self.included(base)
9
- base.class_eval <<-EOS, __FILE__, __LINE__ + 1
10
- extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
11
- self.properties ||= []
12
- EOS
13
- base.extend(ClassMethods)
14
- raise CouchRest::Mixins::Properties::IncludeError, "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (base.new.respond_to?(:[]) && base.new.respond_to?(:[]=))
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
9
+ self.properties ||= []
10
+ raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=))
15
11
  end
16
12
 
17
13
  # Returns the Class properties
@@ -22,16 +18,45 @@ module CouchRest
22
18
  self.class.properties
23
19
  end
24
20
 
21
+ # Returns the Class properties with their values
22
+ #
23
+ # ==== Returns
24
+ # Array:: the list of properties with their values
25
+ def properties_with_values
26
+ props = {}
27
+ properties.each { |property| props[property.name] = read_attribute(property.name) }
28
+ props
29
+ end
30
+
31
+ # Read the casted value of an attribute defined with a property.
32
+ #
33
+ # ==== Returns
34
+ # Object:: the casted attibutes value.
25
35
  def read_attribute(property)
26
- self[property.to_s]
36
+ self[find_property!(property).to_s]
27
37
  end
28
38
 
39
+ # Store a casted value in the current instance of an attribute defined
40
+ # with a property.
29
41
  def write_attribute(property, value)
30
- prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
31
- raise "Missing property definition for #{property.to_s}" unless prop
32
- self[prop.to_s] = prop.cast(self, value)
42
+ prop = find_property!(property)
43
+ self[prop.to_s] = prop.is_a?(String) ? value : prop.cast(self, value)
44
+ end
45
+
46
+ # Takes a hash as argument, and applies the values by using writer methods
47
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
48
+ # missing. In case of error, no attributes are changed.
49
+ def update_attributes_without_saving(hash)
50
+ # Remove any protected and update all the rest. Any attributes
51
+ # which do not have a property will simply be ignored.
52
+ attrs = remove_protected_attributes(hash)
53
+ directly_set_attributes(attrs)
33
54
  end
55
+ alias :attributes= :update_attributes_without_saving
34
56
 
57
+
58
+ private
59
+ # The following methods should be accessable by the Model::Base Class, but not by anything else!
35
60
  def apply_all_property_defaults
36
61
  return if self.respond_to?(:new?) && (new? == false)
37
62
  # TODO: cache the default object
@@ -39,9 +64,57 @@ module CouchRest
39
64
  write_attribute(property, property.default_value)
40
65
  end
41
66
  end
42
-
67
+
68
+ def prepare_all_attributes(doc = {}, options = {})
69
+ apply_all_property_defaults
70
+ if options[:directly_set_attributes]
71
+ directly_set_read_only_attributes(doc)
72
+ else
73
+ doc = remove_protected_attributes(doc)
74
+ end
75
+ directly_set_attributes(doc) unless doc.nil?
76
+ end
77
+
78
+ def find_property!(property)
79
+ prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
80
+ raise ArgumentError, "Missing property definition for #{property.to_s}" if prop.nil?
81
+ prop
82
+ end
83
+
84
+ # Set all the attributes and return a hash with the attributes
85
+ # that have not been accepted.
86
+ def directly_set_attributes(hash)
87
+ hash.reject do |attribute_name, attribute_value|
88
+ if self.respond_to?("#{attribute_name}=")
89
+ self.send("#{attribute_name}=", attribute_value)
90
+ true
91
+ elsif mass_assign_any_attribute # config option
92
+ self[attribute_name] = attribute_value
93
+ true
94
+ else
95
+ false
96
+ end
97
+ end
98
+ end
99
+
100
+ def directly_set_read_only_attributes(hash)
101
+ property_list = self.properties.map{|p| p.name}
102
+ hash.each do |attribute_name, attribute_value|
103
+ next if self.respond_to?("#{attribute_name}=")
104
+ if property_list.include?(attribute_name)
105
+ write_attribute(attribute_name, hash.delete(attribute_name))
106
+ end
107
+ end
108
+ end
109
+
110
+ def set_attributes(hash)
111
+ attrs = remove_protected_attributes(hash)
112
+ directly_set_attributes(attrs)
113
+ end
114
+
115
+
43
116
  module ClassMethods
44
-
117
+
45
118
  def property(name, *options, &block)
46
119
  opts = { }
47
120
  type = options.shift
@@ -64,16 +137,16 @@ module CouchRest
64
137
  class_eval <<-EOS, __FILE__, __LINE__
65
138
  property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
66
139
  property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
67
-
140
+
68
141
  set_callback :save, :before do |object|
69
142
  write_attribute('updated_at', Time.now)
70
143
  write_attribute('created_at', Time.now) if object.new?
71
144
  end
72
145
  EOS
73
146
  end
74
-
147
+
75
148
  protected
76
-
149
+
77
150
  # This is not a thread safe operation, if you have to set new properties at runtime
78
151
  # make sure a mutex is used.
79
152
  def define_property(name, options={}, &block)
@@ -87,7 +160,7 @@ module CouchRest
87
160
  type = [type] # inject as an array
88
161
  end
89
162
  property = Property.new(name, type, options)
90
- create_property_getter(property)
163
+ create_property_getter(property)
91
164
  create_property_setter(property) unless property.read_only == true
92
165
  if property.type_class.respond_to?(:validates_casted_model)
93
166
  validates_casted_model property.name
@@ -95,7 +168,7 @@ module CouchRest
95
168
  properties << property
96
169
  property
97
170
  end
98
-
171
+
99
172
  # defines the getter for the property (and optional aliases)
100
173
  def create_property_getter(property)
101
174
  # meth = property.name
@@ -136,9 +209,10 @@ module CouchRest
136
209
  EOS
137
210
  end
138
211
  end
139
-
212
+
140
213
  end # module ClassMethods
141
-
214
+
142
215
  end
143
216
  end
144
217
  end
218
+
@@ -33,7 +33,7 @@ module CouchRest::Model
33
33
  data.keys.sort.each do |k|
34
34
  value << data[k]
35
35
  end
36
- elsif value.class != Array
36
+ elsif !value.is_a?(Array)
37
37
  raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
38
38
  end
39
39
  arr = value.collect { |data| cast_value(parent, data) }
@@ -58,7 +58,8 @@ module CouchRest::Model
58
58
  if default.class == Proc
59
59
  default.call
60
60
  else
61
- Marshal.load(Marshal.dump(default))
61
+ # Marshal.load(Marshal.dump(default)) # Removed as there are no failing tests and caused mutex errors
62
+ default
62
63
  end
63
64
  end
64
65
 
@@ -72,12 +73,12 @@ module CouchRest::Model
72
73
  def parse_type(type)
73
74
  if type.nil?
74
75
  @casted = false
75
- @type = nil
76
+ @type = nil
76
77
  @type_class = nil
77
78
  else
78
79
  base = type.is_a?(Array) ? type.first : type
79
80
  base = Object if base.nil?
80
- raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
81
+ raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
81
82
  @type_class = base
82
83
  @type = type
83
84
  end
@@ -0,0 +1,71 @@
1
+ module CouchRest
2
+ module Model
3
+ module PropertyProtection
4
+ extend ActiveSupport::Concern
5
+
6
+ # Property protection from mass assignment to CouchRest::Model properties
7
+ #
8
+ # Protected methods will be removed from
9
+ # * new
10
+ # * update_attributes
11
+ # * upate_attributes_without_saving
12
+ # * attributes=
13
+ #
14
+ # There are two modes of protection
15
+ # 1) Declare accessible poperties, and assume all unspecified properties are protected
16
+ # property :name, :accessible => true
17
+ # property :admin # this will be automatically protected
18
+ #
19
+ # 2) Declare protected properties, and assume all unspecified properties are accessible
20
+ # property :name # this will not be protected
21
+ # property :admin, :protected => true
22
+ #
23
+ # 3) Mix and match, and assume all unspecified properties are protected.
24
+ # property :name, :accessible => true
25
+ # property :admin, :protected => true # ignored
26
+ # property :phone # this will be automatically protected
27
+ #
28
+ # Note: the timestamps! method protectes the created_at and updated_at properties
29
+
30
+
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ end
34
+
35
+ module ClassMethods
36
+ def accessible_properties
37
+ props = properties.select { |prop| prop.options[:accessible] }
38
+ if props.empty?
39
+ props = properties.select { |prop| !prop.options[:protected] }
40
+ end
41
+ props
42
+ end
43
+
44
+ def protected_properties
45
+ accessibles = accessible_properties
46
+ properties.reject { |prop| accessibles.include?(prop) }
47
+ end
48
+ end
49
+
50
+ def accessible_properties
51
+ self.class.accessible_properties
52
+ end
53
+
54
+ def protected_properties
55
+ self.class.protected_properties
56
+ end
57
+
58
+ # Return a new copy of the attributes hash with protected attributes
59
+ # removed.
60
+ def remove_protected_attributes(attributes)
61
+ protected_names = protected_properties.map { |prop| prop.name }
62
+ return attributes if protected_names.empty? or attributes.nil?
63
+
64
+ attributes.reject do |property_name, property_value|
65
+ protected_names.include?(property_name.to_s)
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -1,19 +1,23 @@
1
1
  class Time
2
2
  # returns a local time value much faster than Time.parse
3
3
  def self.mktime_with_offset(string)
4
- string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
4
+ string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})(([\+|\s|\-])*(\d{2}):?(\d{2}))?/
5
5
  # $1 = year
6
6
  # $2 = month
7
7
  # $3 = day
8
8
  # $4 = hours
9
9
  # $5 = minutes
10
10
  # $6 = seconds
11
- # $7 = time zone direction
12
- # $8 = tz difference
11
+ # $8 = time zone direction
12
+ # $9 = tz difference
13
13
  # utc time with wrong TZ info:
14
- time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
15
- tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
16
- time + tz_difference + zone_offset(time.zone)
14
+ time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6)
15
+ if ($7)
16
+ tz_difference = ("#{$8 == '-' ? '+' : '-'}#{$9}".to_i * 3600)
17
+ time + tz_difference + zone_offset(time.zone)
18
+ else
19
+ time
20
+ end
17
21
  end
18
22
  end
19
23
 
@@ -75,7 +79,7 @@ module CouchRest
75
79
  # Match numeric string
76
80
  def typecast_to_numeric(value, method)
77
81
  if value.respond_to?(:to_str)
78
- if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
82
+ if value.gsub(/,/, '.').gsub(/\.(?!\d*\Z)/, '').to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
79
83
  $1.send(method)
80
84
  else
81
85
  value
@@ -128,6 +132,7 @@ module CouchRest
128
132
  rescue ArgumentError
129
133
  value
130
134
  rescue TypeError
135
+ # After failures, resort to normal time parse
131
136
  value
132
137
  end
133
138