couchrest_model 1.1.0.beta → 1.1.0.beta2

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.
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- couchrest_model (1.1.0.beta)
4
+ couchrest_model (1.1.0.beta2)
5
5
  activemodel (~> 3.0.0)
6
- couchrest (~> 1.0.2)
6
+ couchrest (= 1.1.0.pre2)
7
7
  mime-types (~> 1.15)
8
8
  railties (~> 3.0.0)
9
9
  tzinfo (~> 0.3.22)
@@ -12,9 +12,9 @@ GEM
12
12
  remote: http://rubygems.org/
13
13
  specs:
14
14
  abstract (1.0.0)
15
- actionpack (3.0.4)
16
- activemodel (= 3.0.4)
17
- activesupport (= 3.0.4)
15
+ actionpack (3.0.5)
16
+ activemodel (= 3.0.5)
17
+ activesupport (= 3.0.5)
18
18
  builder (~> 2.1.2)
19
19
  erubis (~> 2.6.6)
20
20
  i18n (~> 0.4)
@@ -22,13 +22,13 @@ GEM
22
22
  rack-mount (~> 0.6.13)
23
23
  rack-test (~> 0.5.7)
24
24
  tzinfo (~> 0.3.23)
25
- activemodel (3.0.4)
26
- activesupport (= 3.0.4)
25
+ activemodel (3.0.5)
26
+ activesupport (= 3.0.5)
27
27
  builder (~> 2.1.2)
28
28
  i18n (~> 0.4)
29
- activesupport (3.0.4)
29
+ activesupport (3.0.5)
30
30
  builder (2.1.2)
31
- couchrest (1.0.2)
31
+ couchrest (1.1.0.pre2)
32
32
  json (~> 1.5.1)
33
33
  mime-types (~> 1.15)
34
34
  rest-client (~> 1.6.1)
@@ -39,13 +39,13 @@ GEM
39
39
  json (1.5.1)
40
40
  mime-types (1.16)
41
41
  rack (1.2.1)
42
- rack-mount (0.6.13)
42
+ rack-mount (0.6.14)
43
43
  rack (>= 1.0.0)
44
44
  rack-test (0.5.7)
45
45
  rack (>= 1.0)
46
- railties (3.0.4)
47
- actionpack (= 3.0.4)
48
- activesupport (= 3.0.4)
46
+ railties (3.0.5)
47
+ actionpack (= 3.0.5)
48
+ activesupport (= 3.0.5)
49
49
  rake (>= 0.8.7)
50
50
  thor (~> 0.14.4)
51
51
  rake (0.8.7)
@@ -60,14 +60,14 @@ GEM
60
60
  diff-lcs (~> 1.1.2)
61
61
  rspec-mocks (2.3.0)
62
62
  thor (0.14.6)
63
- tzinfo (0.3.24)
63
+ tzinfo (0.3.26)
64
64
 
65
65
  PLATFORMS
66
66
  ruby
67
67
 
68
68
  DEPENDENCIES
69
69
  activemodel (~> 3.0.0)
70
- couchrest (~> 1.0.2)
70
+ couchrest (= 1.1.0.pre2)
71
71
  couchrest_model!
72
72
  mime-types (~> 1.15)
73
73
  rack-test (>= 0.5.7)
data/README.md CHANGED
@@ -499,6 +499,48 @@ A really interesting use of `:proxy` and `:view` together could be where you'd l
499
499
 
500
500
  Pretty cool!
501
501
 
502
+ ## Proxy Support
503
+
504
+ CouchDB makes it really easy to create databases on the fly, so easy in fact that it is perfectly
505
+ feasable to have one database per user or per company or per whatever makes sense to split into
506
+ its own individual database. CouchRest Model now makes it really easy to support this scenario
507
+ using the proxy methods. Here's a quick example:
508
+
509
+ # Define a master company class, its children should be in their own DB
510
+ class Company < CouchRest::Model::Base
511
+ use_database COUCHDB_DATABASE
512
+ property :name
513
+ property :slug
514
+
515
+ proxy_for :invoices
516
+
517
+ def proxy_database
518
+ @proxy_database ||= COUCHDB_SERVER.database!("project_#{slug}")
519
+ end
520
+ end
521
+
522
+ # Invoices belong to a company
523
+ class Invoice < CouchRest::Model::Base
524
+ property :date
525
+ property :total
526
+
527
+ proxied_by :company
528
+
529
+ design do
530
+ view :by_date
531
+ end
532
+ end
533
+
534
+ By setting up our models like this, the invoices should be accessed via a company object:
535
+
536
+ company = Company.first
537
+ company.invoices.new # build a new invoice
538
+ company.invoices.by_date.first # find company's first invoice by date
539
+
540
+ Internally, all requests for invoices are passed through a model proxy. Aside from the
541
+ basic methods and views, it also ensures that some of the more complex queries are supported
542
+ such as validating for uniqueness and associations.
543
+
502
544
 
503
545
  ## Configuration
504
546
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0.beta
1
+ 1.1.0.beta2
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
24
  s.require_paths = ["lib"]
25
25
 
26
- s.add_dependency(%q<couchrest>, "~> 1.0.2")
26
+ s.add_dependency(%q<couchrest>, "1.1.0.pre2")
27
27
  s.add_dependency(%q<mime-types>, "~> 1.15")
28
28
  s.add_dependency(%q<activemodel>, "~> 3.0.0")
29
29
  s.add_dependency(%q<tzinfo>, "~> 0.3.22")
data/history.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 1.1.0.beta2
2
+
3
+ * Minor enhancements:
4
+ * Time handling improved in accordance with CouchRest 1.1.0. Always set to UTC.
5
+ * Refinements to associations and uniqueness validation for proxy (based on issue found by Gleb Kanterov)
6
+ * Added :allow_nil and :allow_blank options when creating a new view
7
+ * Unique Validation now supports scopes!
8
+ * Added support for #keys with list on Design View.
9
+
1
10
  == 1.1.0.beta
2
11
 
3
12
  * Epic enhancements:
@@ -11,30 +11,37 @@ module CouchRest
11
11
  module ClassMethods
12
12
 
13
13
  # Define an association that this object belongs to.
14
- #
14
+ #
15
+ # An attribute will be created matching the name of the attribute
16
+ # with '_id' on the end, or the foreign key (:foreign_key) provided.
17
+ #
18
+ # Searching for the assocated object is performed using a string
19
+ # (:proxy) to be evaulated in the context of the owner. Typically
20
+ # this will be set to the class name (:class_name), or determined
21
+ # automatically if the owner belongs to a proxy object.
22
+ #
23
+ # If the association owner is proxied by another model, than an attempt will
24
+ # be made to automatically determine the correct place to request
25
+ # the documents. Typically, this is a method with the pluralized name of the
26
+ # association inside owner's owner, or proxy.
27
+ #
28
+ # For example, imagine a company acts as a proxy for invoices and clients.
29
+ # If an invoice belongs to a client, the invoice will need to access the
30
+ # list of clients via the proxy. So a request to search for the associated
31
+ # client from an invoice would look like:
32
+ #
33
+ # self.company.clients
34
+ #
35
+ # If the name of the collection proxy is not the pluralized assocation name,
36
+ # it can be set with the :proxy_name option.
37
+ #
15
38
  def belongs_to(attrib, *options)
16
- opts = {
17
- :foreign_key => attrib.to_s + '_id',
18
- :class_name => attrib.to_s.camelcase,
19
- :proxy => nil
20
- }
21
- case options.first
22
- when Hash
23
- opts.merge!(options.first)
24
- end
39
+ opts = merge_belongs_to_association_options(attrib, options.first)
25
40
 
26
- begin
27
- opts[:class] = opts[:class_name].constantize
28
- rescue NameError
29
- raise NameError, "Unable to convert class name into Constant for #{self.name}##{attrib}"
30
- end
41
+ property(opts[:foreign_key], opts)
31
42
 
32
- prop = property(opts[:foreign_key], opts)
33
-
34
- create_belongs_to_getter(attrib, prop, opts)
35
- create_belongs_to_setter(attrib, prop, opts)
36
-
37
- prop
43
+ create_belongs_to_getter(attrib, opts)
44
+ create_belongs_to_setter(attrib, opts)
38
45
  end
39
46
 
40
47
  # Provide access to a collection of objects where the associated
@@ -77,44 +84,50 @@ module CouchRest
77
84
  # frequently! Use with prudence.
78
85
  #
79
86
  def collection_of(attrib, *options)
80
- opts = {
81
- :foreign_key => attrib.to_s.singularize + '_ids',
82
- :class_name => attrib.to_s.singularize.camelcase,
83
- :proxy => nil
84
- }
85
- case options.first
86
- when Hash
87
- opts.merge!(options.first)
88
- end
89
- begin
90
- opts[:class] = opts[:class_name].constantize
91
- rescue
92
- raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
93
- end
87
+ opts = merge_belongs_to_association_options(attrib, options.first)
88
+ opts[:foreign_key] = opts[:foreign_key].pluralize
94
89
  opts[:readonly] = true
95
90
 
96
- prop = property(opts[:foreign_key], [], opts)
91
+ property(opts[:foreign_key], [], opts)
97
92
 
98
- create_collection_of_property_setter(attrib, prop, opts)
99
- create_collection_of_getter(attrib, prop, opts)
100
- create_collection_of_setter(attrib, prop, opts)
101
-
102
- prop
93
+ create_collection_of_property_setter(attrib, opts)
94
+ create_collection_of_getter(attrib, opts)
95
+ create_collection_of_setter(attrib, opts)
103
96
  end
104
97
 
105
98
 
106
99
  private
107
100
 
108
- def create_belongs_to_getter(attrib, property, options)
109
- base = options[:proxy] || options[:class_name]
101
+ def merge_belongs_to_association_options(attrib, options = nil)
102
+ opts = {
103
+ :foreign_key => attrib.to_s.singularize + '_id',
104
+ :class_name => attrib.to_s.singularize.camelcase,
105
+ :proxy_name => attrib.to_s.pluralize
106
+ }
107
+ opts.merge!(options) if options.is_a?(Hash)
108
+
109
+ # Generate a string for the proxy method call
110
+ # Assumes that the proxy_owner_method from "proxyable" is available.
111
+ if opts[:proxy].to_s.empty?
112
+ opts[:proxy] = if proxy_owner_method
113
+ "self.#{proxy_owner_method}.#{opts[:proxy_name]}"
114
+ else
115
+ opts[:class_name]
116
+ end
117
+ end
118
+
119
+ opts
120
+ end
121
+
122
+ def create_belongs_to_getter(attrib, options)
110
123
  class_eval <<-EOS, __FILE__, __LINE__ + 1
111
124
  def #{attrib}
112
- @#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : (model_proxy || #{base}).get(self.#{options[:foreign_key]})
125
+ @#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : #{options[:proxy]}.get(self.#{options[:foreign_key]})
113
126
  end
114
127
  EOS
115
128
  end
116
129
 
117
- def create_belongs_to_setter(attrib, property, options)
130
+ def create_belongs_to_setter(attrib, options)
118
131
  class_eval <<-EOS, __FILE__, __LINE__ + 1
119
132
  def #{attrib}=(value)
120
133
  self.#{options[:foreign_key]} = value.nil? ? nil : value.id
@@ -125,7 +138,7 @@ module CouchRest
125
138
 
126
139
  ### collection_of support methods
127
140
 
128
- def create_collection_of_property_setter(attrib, property, options)
141
+ def create_collection_of_property_setter(attrib, options)
129
142
  # ensure CollectionOfProxy is nil, ready to be reloaded on request
130
143
  class_eval <<-EOS, __FILE__, __LINE__ + 1
131
144
  def #{options[:foreign_key]}=(value)
@@ -135,18 +148,17 @@ module CouchRest
135
148
  EOS
136
149
  end
137
150
 
138
- def create_collection_of_getter(attrib, property, options)
139
- base = options[:proxy] || options[:class_name]
151
+ def create_collection_of_getter(attrib, options)
140
152
  class_eval <<-EOS, __FILE__, __LINE__ + 1
141
153
  def #{attrib}(reload = false)
142
154
  return @#{attrib} unless @#{attrib}.nil? or reload
143
- ary = self.#{options[:foreign_key]}.collect{|i| (model_proxy || #{base}).get(i)}
155
+ ary = self.#{options[:foreign_key]}.collect{|i| #{options[:proxy]}.get(i)}
144
156
  @#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
145
157
  end
146
158
  EOS
147
159
  end
148
160
 
149
- def create_collection_of_setter(attrib, property, options)
161
+ def create_collection_of_setter(attrib, options)
150
162
  class_eval <<-EOS, __FILE__, __LINE__ + 1
151
163
  def #{attrib}=(value)
152
164
  @#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
@@ -53,7 +53,7 @@ module CouchRest::Model
53
53
  end
54
54
  alias :to_key :id
55
55
  alias :to_param :id
56
-
56
+
57
57
  # Sets the attributes from a hash
58
58
  def update_attributes_without_saving(hash)
59
59
  hash.each do |k, v|
@@ -0,0 +1,66 @@
1
+ module CouchRest
2
+ module Model
3
+ module CoreExtensions
4
+ module TimeParsing
5
+
6
+ if RUBY_VERSION < "1.9.0"
7
+ # Overrwrite Ruby's standard new method to provide compatible support
8
+ # of 1.9.2's Time.new method.
9
+ #
10
+ # Only supports syntax like:
11
+ #
12
+ # Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
13
+ # # or
14
+ # Time.new(2011, 4, 1, 18, 50, 32)
15
+ #
16
+ def new(*args)
17
+ return super() if (args.empty?)
18
+ zone = args.delete_at(6)
19
+ time = mktime(*args)
20
+ if zone =~ /([\+|\-]?)(\d{2}):?(\d{2})/
21
+ tz_difference = ("#{$1 == '-' ? '+' : '-'}#{$2}".to_i * 3600) + ($3.to_i * 60)
22
+ time + tz_difference + zone_offset(time.zone)
23
+ else
24
+ time
25
+ end
26
+ end
27
+ end
28
+
29
+ # Attemtps to parse a time string in ISO8601 format.
30
+ # If no match is found, the standard time parse will be used.
31
+ #
32
+ # Times, unless provided with a time zone, are assumed to be in
33
+ # UTC.
34
+ #
35
+ def parse_iso8601(string)
36
+ if (string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})(Z| ?([\+|\s|\-])?(\d{2}):?(\d{2}))?/)
37
+ # $1 = year
38
+ # $2 = month
39
+ # $3 = day
40
+ # $4 = hours
41
+ # $5 = minutes
42
+ # $6 = seconds
43
+ # $7 = UTC or Timezone
44
+ # $8 = time zone direction
45
+ # $9 = tz difference hours
46
+ # $10 = tz difference minutes
47
+
48
+ if (!$7.to_s.empty? && $7 != 'Z')
49
+ new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, "#{$8 == '-' ? '-' : '+'}#{$9}:#{$10}")
50
+ else
51
+ utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
52
+ end
53
+ else
54
+ parse(string)
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Time.class_eval do
64
+ extend CouchRest::Model::CoreExtensions::TimeParsing
65
+ end
66
+
@@ -25,7 +25,7 @@ module CouchRest
25
25
  self.owner = parent
26
26
  self.name = name.to_s
27
27
  # Default options:
28
- self.query = { :reduce => false }
28
+ self.query = { }
29
29
  elsif parent.is_a?(self.class)
30
30
  self.model = (new_query.delete(:proxy) || parent.model)
31
31
  self.owner = parent.owner
@@ -139,12 +139,6 @@ module CouchRest
139
139
  execute['total_rows']
140
140
  end
141
141
 
142
- # Convenience wrapper around the rows result set. This will provide
143
- # and array of keys.
144
- def keys
145
- rows.map{|r| r.key}
146
- end
147
-
148
142
  # Convenience wrapper to provide all the values from the route
149
143
  # set without having to go through +rows+.
150
144
  def values
@@ -181,7 +175,7 @@ module CouchRest
181
175
  #
182
176
  # Cannot be used when the +#startkey+ or +#endkey+ have been set.
183
177
  def key(value)
184
- raise "View#key cannot be used when startkey or endkey have been set" unless query[:startkey].nil? && query[:endkey].nil?
178
+ raise "View#key cannot be used when startkey or endkey have been set" unless query[:keys].nil? && query[:startkey].nil? && query[:endkey].nil?
185
179
  update_query(:key => value)
186
180
  end
187
181
 
@@ -193,7 +187,7 @@ module CouchRest
193
187
  #
194
188
  # Cannot be used if the key has been set.
195
189
  def startkey(value)
196
- raise "View#startkey cannot be used when key has been set" unless query[:key].nil?
190
+ raise "View#startkey cannot be used when key has been set" unless query[:key].nil? && query[:keys].nil?
197
191
  update_query(:startkey => value)
198
192
  end
199
193
 
@@ -210,7 +204,7 @@ module CouchRest
210
204
  # See the +#startkey+ method for more details and the +#inclusive_end+
211
205
  # option.
212
206
  def endkey(value)
213
- raise "View#endkey cannot be used when key has been set" unless query[:key].nil?
207
+ raise "View#endkey cannot be used when key has been set" unless query[:key].nil? && query[:keys].nil?
214
208
  update_query(:endkey => value)
215
209
  end
216
210
 
@@ -221,6 +215,22 @@ module CouchRest
221
215
  update_query(:endkey_docid => value.is_a?(String) ? value : value.id)
222
216
  end
223
217
 
218
+ # Keys is a special CouchDB option that will cause the view request to be POSTed
219
+ # including an array of keys. Only documents with the matching keys will be
220
+ # returned. This is much faster than sending multiple requests for a set
221
+ # non-consecutive documents.
222
+ #
223
+ # If no values are provided, this method will act as a wrapper around
224
+ # the rows result set, providing an array of keys.
225
+ def keys(*keys)
226
+ if keys.empty?
227
+ rows.map{|r| r.key}
228
+ else
229
+ raise "View#keys cannot by used when key or startkey/endkey have been set" unless query[:key].nil? && query[:startkey].nil? && query[:endkey].nil?
230
+ update_query(:keys => keys.first)
231
+ end
232
+ end
233
+
224
234
 
225
235
  # The results should be provided in descending order.
226
236
  #
@@ -341,8 +351,6 @@ module CouchRest
341
351
  (offset_value / limit_value) + 1
342
352
  end
343
353
 
344
-
345
-
346
354
  protected
347
355
 
348
356
  def include_docs!
@@ -371,7 +379,7 @@ module CouchRest
371
379
  def use_database
372
380
  query[:database] || model.database
373
381
  end
374
-
382
+
375
383
  def execute
376
384
  return self.result if result
377
385
  raise "Database must be defined in model or view!" if use_database.nil?
@@ -412,6 +420,16 @@ module CouchRest
412
420
  # The view name is the same, but three keys would be used in the
413
421
  # subsecuent index.
414
422
  #
423
+ # By default, a check is made on each of the view's keys to ensure they
424
+ # do not contain a nil value ('null' in javascript). This is probably what
425
+ # you want in most cases but sometimes in can be useful to create an
426
+ # index where nil is permited. Set the <tt>:allow_nil</tt> option to true to
427
+ # remove this check.
428
+ #
429
+ # Conversely, keys are not checked to see if they are empty or blank. If you'd
430
+ # like to enable this, set the <tt>:allow_blank</tt> option to false. The default
431
+ # is true, empty strings are permited in the indexes.
432
+ #
415
433
  def create(model, name, opts = {})
416
434
 
417
435
  unless opts[:map]
@@ -421,12 +439,14 @@ module CouchRest
421
439
 
422
440
  raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
423
441
 
442
+ opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
424
443
  opts[:guards] ||= []
425
444
  opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
426
445
 
427
446
  keys = opts[:by].map{|o| "doc['#{o}']"}
428
447
  emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
429
- opts[:guards] += keys.map{|k| "(#{k} != null)"}
448
+ opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
449
+ opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
430
450
  opts[:map] = <<-EOF
431
451
  function(doc) {
432
452
  if (#{opts[:guards].join(' && ')}) {
@@ -131,8 +131,10 @@ module CouchRest
131
131
  end
132
132
 
133
133
  # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
134
- # on the document whenever saving occurs. CouchRest uses a pretty
135
- # decent time format by default. See Time#to_json
134
+ # on the document whenever saving occurs.
135
+ #
136
+ # These properties are casted as Time objects, so they should always
137
+ # be set to UTC.
136
138
  def timestamps!
137
139
  class_eval <<-EOS, __FILE__, __LINE__
138
140
  property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
@@ -4,31 +4,36 @@ module CouchRest
4
4
  module Proxyable
5
5
  extend ActiveSupport::Concern
6
6
 
7
- included do
8
- attr_accessor :model_proxy
9
- end
10
-
11
7
  module ClassMethods
12
8
 
13
9
  # Define a collection that will use the base model for the database connection
14
10
  # details.
15
- def proxy_for(model_name, options = {})
11
+ def proxy_for(assoc_name, options = {})
16
12
  db_method = options[:database_method] || "proxy_database"
17
- options[:class_name] ||= model_name.to_s.singularize.camelize
13
+ options[:class_name] ||= assoc_name.to_s.singularize.camelize
18
14
  class_eval <<-EOS, __FILE__, __LINE__ + 1
19
- def #{model_name}
15
+ def #{assoc_name}
20
16
  unless respond_to?('#{db_method}')
21
17
  raise "Missing ##{db_method} method for proxy"
22
18
  end
23
- @#{model_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
19
+ @#{assoc_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
24
20
  end
25
21
  EOS
26
22
  end
27
23
 
28
24
  def proxied_by(model_name, options = {})
29
- raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name)
25
+ raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name) || !proxy_owner_method.nil?
26
+ self.proxy_owner_method = model_name
27
+ attr_accessor :model_proxy
30
28
  attr_accessor model_name
31
29
  end
30
+
31
+ # Define an a class variable accessor ready to be inherited and unique
32
+ # for each Class using the base.
33
+ # Perhaps there is a shorter way of writing this.
34
+ def proxy_owner_method=(name); @proxy_owner_method = name; end
35
+ def proxy_owner_method; @proxy_owner_method; end
36
+
32
37
  end
33
38
 
34
39
  class ModelProxy
@@ -132,10 +137,10 @@ module CouchRest
132
137
  # Update the document's proxy details, specifically, the fields that
133
138
  # link back to the original document.
134
139
  def proxy_update(doc)
135
- if doc
136
- doc.database = @database if doc.respond_to?(:database=)
137
- doc.model_proxy = self if doc.respond_to?(:model_proxy=)
138
- doc.send("#{owner_name}=", owner) if doc.respond_to?("#{owner_name}=")
140
+ if doc && doc.is_a?(model)
141
+ doc.database = @database
142
+ doc.model_proxy = self
143
+ doc.send("#{owner_name}=", owner)
139
144
  end
140
145
  doc
141
146
  end
@@ -1,26 +1,3 @@
1
- class Time
2
- # returns a local time value much faster than Time.parse
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}))?/
5
- # $1 = year
6
- # $2 = month
7
- # $3 = day
8
- # $4 = hours
9
- # $5 = minutes
10
- # $6 = seconds
11
- # $8 = time zone direction
12
- # $9 = tz difference
13
- # utc time with wrong TZ info:
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
21
- end
22
- end
23
-
24
1
  module CouchRest
25
2
  module Model
26
3
  module Typecast
@@ -29,7 +6,11 @@ module CouchRest
29
6
  return nil if value.nil?
30
7
  klass = property.type_class
31
8
  if value.instance_of?(klass) || klass == Object
32
- value
9
+ if klass == Time && !value.utc?
10
+ value.utc # Ensure Time is always in UTC
11
+ else
12
+ value
13
+ end
33
14
  elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
34
15
  send('typecast_to_'+klass.to_s.downcase, value)
35
16
  else
@@ -127,12 +108,11 @@ module CouchRest
127
108
  if value.is_a?(Hash)
128
109
  typecast_hash_to_time(value)
129
110
  else
130
- Time.mktime_with_offset(value.to_s)
111
+ Time.parse_iso8601(value.to_s)
131
112
  end
132
113
  rescue ArgumentError
133
114
  value
134
115
  rescue TypeError
135
- # After failures, resort to normal time parse
136
116
  value
137
117
  end
138
118
 
@@ -150,13 +130,13 @@ module CouchRest
150
130
  # Creates a Time instance from a Hash with keys :year, :month, :day,
151
131
  # :hour, :min, :sec
152
132
  def typecast_hash_to_time(value)
153
- Time.local(*extract_time(value))
133
+ Time.utc(*extract_time(value))
154
134
  end
155
135
 
156
136
  # Extracts the given args from the hash. If a value does not exist, it
157
137
  # uses the value of Time.now.
158
138
  def extract_time(value)
159
- now = Time.now
139
+ now = Time.now
160
140
  [:year, :month, :day, :hour, :min, :sec].map do |segment|
161
141
  typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
162
142
  end