couchrest_model 1.1.0.beta → 1.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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