couchrest_model 2.0.3 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/VERSION +1 -1
- data/couchrest_model.gemspec +1 -3
- data/history.md +9 -0
- data/lib/couchrest/model/associations.rb +16 -8
- data/lib/couchrest/model/core_extensions/time_parsing.rb +9 -2
- data/lib/couchrest/model/design.rb +11 -106
- data/lib/couchrest/model/designs/design_mapper.rb +5 -0
- data/lib/couchrest/model/designs/migrations.rb +131 -0
- data/lib/couchrest/model/property.rb +1 -1
- data/lib/couchrest/model/proxyable.rb +6 -1
- data/lib/couchrest/model/utils/migrate.rb +11 -6
- data/lib/couchrest_model.rb +15 -16
- data/spec/unit/assocations_spec.rb +5 -0
- data/spec/unit/core_extensions/time_parsing.rb +15 -0
- data/spec/unit/design_spec.rb +11 -78
- data/spec/unit/designs/design_mapper_spec.rb +14 -0
- data/spec/unit/designs/migrations_spec.rb +104 -0
- data/spec/unit/property_spec.rb +9 -0
- data/spec/unit/proxyable_spec.rb +5 -0
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc12bf9934bec928c3317599950ad2ac7b57e623
|
4
|
+
data.tar.gz: 4244221435ea4d8dbd33a6c184ed97926ba5cf14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 337c15afdd8264dfd16341d50ac85a25f8651946f0b1994135e02ac118f63dbde6c7688c4a21e44e21eb639d85aa0f892d979b5465f3d9c516fd9141a9433756
|
7
|
+
data.tar.gz: 9200d9be82615a00e475f05947ccbcef682ab5867a23810368146db85064a63ac46f8f6453efd661a64c16f805376060d65dddcc58cae74ec7892637aeebcfe8
|
data/README.md
CHANGED
@@ -87,6 +87,12 @@ require 'rails/test_unit/railtie'
|
|
87
87
|
|
88
88
|
You'll then need to make sure any references to `config.active_record` are removed from your environment files.
|
89
89
|
|
90
|
+
or alternatively below command do the same work
|
91
|
+
```ruby
|
92
|
+
rails new <application-name> --skip-active-record
|
93
|
+
```
|
94
|
+
Now in the gem file just add [couchrest_model] and you are good to go.
|
95
|
+
|
90
96
|
## Generators
|
91
97
|
|
92
98
|
### Configuration
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0.
|
1
|
+
2.0.4
|
data/couchrest_model.gemspec
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
1
|
Gem::Specification.new do |s|
|
4
2
|
s.name = %q{couchrest_model}
|
5
3
|
s.version = `cat VERSION`.strip
|
@@ -24,7 +22,7 @@ Gem::Specification.new do |s|
|
|
24
22
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
23
|
s.require_paths = ["lib"]
|
26
24
|
|
27
|
-
s.add_dependency(%q<couchrest>, "
|
25
|
+
s.add_dependency(%q<couchrest>, "~> 1.2.1")
|
28
26
|
s.add_dependency(%q<mime-types>, ">= 1.16")
|
29
27
|
s.add_dependency(%q<activemodel>, ">= 4.0", ">= 3.0")
|
30
28
|
s.add_dependency(%q<tzinfo>, ">= 0.3.22")
|
data/history.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# CouchRest Model Change History
|
2
2
|
|
3
|
+
## 2.0.4 - 2015-06-26
|
4
|
+
|
5
|
+
* Casting Array properties using anything that inherits from Hash. (@samlown)
|
6
|
+
* Adding `.proxy_method_names` call to help migrations with multiple proxied models. (@samlown)
|
7
|
+
* Fixing `collection_of` dirty tracking when setting with objects. (@samlown)
|
8
|
+
* Added support for design doc `#view_lib` method for [CommonJS modules in views](http://wiki.apache.org/couchdb/CommonJS_Modules). (@samlown)
|
9
|
+
* Updating CouchRest dependency to ~> 1.2.1 (@samlown)
|
10
|
+
* Migrations use design doc info requests as opposed to Stream (@samlown)
|
11
|
+
|
3
12
|
## 2.0.3 - 2014-07-04
|
4
13
|
|
5
14
|
* Added find_by_view! method support for raising DocumentNotFound error when searching.
|
@@ -183,49 +183,57 @@ module CouchRest
|
|
183
183
|
def initialize(array, property, parent)
|
184
184
|
(array ||= []).compact!
|
185
185
|
super(array, property, parent)
|
186
|
-
|
186
|
+
self.casted_by_attribute = [] # replace the original array!
|
187
187
|
array.compact.each do |obj|
|
188
188
|
check_obj(obj)
|
189
|
-
|
189
|
+
casted_by_attribute << obj.id
|
190
190
|
end
|
191
191
|
end
|
192
192
|
|
193
193
|
def << obj
|
194
194
|
check_obj(obj)
|
195
|
-
|
195
|
+
casted_by_attribute << obj.id
|
196
196
|
super(obj)
|
197
197
|
end
|
198
198
|
|
199
199
|
def push(obj)
|
200
200
|
check_obj(obj)
|
201
|
-
|
201
|
+
casted_by_attribute.push obj.id
|
202
202
|
super(obj)
|
203
203
|
end
|
204
204
|
|
205
205
|
def unshift(obj)
|
206
206
|
check_obj(obj)
|
207
|
-
|
207
|
+
casted_by_attribute.unshift obj.id
|
208
208
|
super(obj)
|
209
209
|
end
|
210
210
|
|
211
211
|
def []= index, obj
|
212
212
|
check_obj(obj)
|
213
|
-
|
213
|
+
casted_by_attribute[index] = obj.id
|
214
214
|
super(index, obj)
|
215
215
|
end
|
216
216
|
|
217
217
|
def pop
|
218
|
-
|
218
|
+
casted_by_attribute.pop
|
219
219
|
super
|
220
220
|
end
|
221
221
|
|
222
222
|
def shift
|
223
|
-
|
223
|
+
casted_by_attribute.shift
|
224
224
|
super
|
225
225
|
end
|
226
226
|
|
227
227
|
protected
|
228
228
|
|
229
|
+
def casted_by_attribute=(value)
|
230
|
+
casted_by.write_attribute(casted_by_property, value)
|
231
|
+
end
|
232
|
+
|
233
|
+
def casted_by_attribute
|
234
|
+
casted_by.read_attribute(casted_by_property)
|
235
|
+
end
|
236
|
+
|
229
237
|
def check_obj(obj)
|
230
238
|
raise "Object cannot be added to #{casted_by.class.to_s}##{casted_by_property.to_s} collection unless saved" if obj.new?
|
231
239
|
end
|
@@ -10,6 +10,13 @@ module CouchRest
|
|
10
10
|
# Times, unless provided with a time zone, are assumed to be in
|
11
11
|
# UTC.
|
12
12
|
#
|
13
|
+
# Uses String#to_r on seconds portion to avoid rounding errors. Eg:
|
14
|
+
# Time.parse_iso8601("2014-12-11T16:54:54.549Z").as_json
|
15
|
+
# => "2014-12-11T16:54:54.548Z"
|
16
|
+
#
|
17
|
+
# See: https://bugs.ruby-lang.org/issues/7829
|
18
|
+
#
|
19
|
+
|
13
20
|
def parse_iso8601(string)
|
14
21
|
if (string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2}(\.\d+)?)(Z| ?([\+|\s|\-])?(\d{2}):?(\d{2}))?/)
|
15
22
|
# $1 = year
|
@@ -24,9 +31,9 @@ module CouchRest
|
|
24
31
|
# $11 = tz difference minutes
|
25
32
|
|
26
33
|
if $8 == 'Z' || $8.to_s.empty?
|
27
|
-
utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.
|
34
|
+
utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_r)
|
28
35
|
else
|
29
|
-
new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.
|
36
|
+
new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_r, "#{$9 == '-' ? '-' : '+'}#{$10}:#{$11}")
|
30
37
|
end
|
31
38
|
else
|
32
39
|
parse(string)
|
@@ -3,6 +3,7 @@ module CouchRest
|
|
3
3
|
module Model
|
4
4
|
|
5
5
|
class Design < ::CouchRest::Design
|
6
|
+
include ::CouchRest::Model::Designs::Migrations
|
6
7
|
|
7
8
|
# The model Class that this design belongs to and method name
|
8
9
|
attr_accessor :model, :method_name
|
@@ -56,108 +57,6 @@ module CouchRest
|
|
56
57
|
self
|
57
58
|
end
|
58
59
|
|
59
|
-
# Migrate the design document preventing downtime on a production
|
60
|
-
# system. Typically this will be used when auto updates are disabled.
|
61
|
-
#
|
62
|
-
# Steps taken are:
|
63
|
-
#
|
64
|
-
# 1. Compare the checksum with the current version
|
65
|
-
# 2. If different, create a new design doc with timestamp
|
66
|
-
# 3. Wait until the view returns a result
|
67
|
-
# 4. Copy over the original design doc
|
68
|
-
#
|
69
|
-
# If a block is provided, it will be called with the result of the migration:
|
70
|
-
#
|
71
|
-
# * :no_change - Nothing performed as there are no changes.
|
72
|
-
# * :created - Add a new design doc as non existed
|
73
|
-
# * :migrated - Migrated the existing design doc.
|
74
|
-
#
|
75
|
-
# This can be used for progressivly printing the results of the migration.
|
76
|
-
#
|
77
|
-
# After completion, either a "cleanup" Proc object will be provided to finalize
|
78
|
-
# the process and copy the document into place, or simply nil if no cleanup is
|
79
|
-
# required. For example:
|
80
|
-
#
|
81
|
-
# print "Synchronising Cat model designs: "
|
82
|
-
# callback = Cat.design_doc.migrate do |res|
|
83
|
-
# puts res.to_s
|
84
|
-
# end
|
85
|
-
# if callback
|
86
|
-
# puts "Cleaning up."
|
87
|
-
# callback.call
|
88
|
-
# end
|
89
|
-
#
|
90
|
-
def migrate(db = nil, &block)
|
91
|
-
db ||= database
|
92
|
-
doc = load_from_database(db)
|
93
|
-
cleanup = nil
|
94
|
-
id = self['_id']
|
95
|
-
|
96
|
-
if !doc
|
97
|
-
# no need to migrate, just save it
|
98
|
-
new_doc = to_hash.dup
|
99
|
-
db.save_doc(new_doc)
|
100
|
-
|
101
|
-
result = :created
|
102
|
-
elsif doc['couchrest-hash'] != checksum
|
103
|
-
id += "_migration"
|
104
|
-
|
105
|
-
# Delete current migration if there is one
|
106
|
-
old_migration = load_from_database(db, id)
|
107
|
-
db.delete_doc(old_migration) if old_migration
|
108
|
-
|
109
|
-
# Save new design doc
|
110
|
-
new_doc = doc.merge(to_hash)
|
111
|
-
new_doc['_id'] = id
|
112
|
-
new_doc.delete('_rev')
|
113
|
-
db.save_doc(new_doc)
|
114
|
-
|
115
|
-
# Proc definition to copy the migration doc over the original
|
116
|
-
cleanup = Proc.new do
|
117
|
-
db.copy_doc(new_doc, doc)
|
118
|
-
db.delete_doc(new_doc)
|
119
|
-
self
|
120
|
-
end
|
121
|
-
|
122
|
-
result = :migrated
|
123
|
-
else
|
124
|
-
# Already up to date
|
125
|
-
result = :no_change
|
126
|
-
end
|
127
|
-
|
128
|
-
if new_doc && !new_doc['views'].empty?
|
129
|
-
# Create a view query and send
|
130
|
-
name = new_doc['views'].keys.first
|
131
|
-
view = new_doc['views'][name]
|
132
|
-
params = {:limit => 1}
|
133
|
-
params[:reduce] = false if view['reduce']
|
134
|
-
db.view("#{id}/_view/#{name}", params) do |res|
|
135
|
-
# Block to use streamer!
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# Provide the result in block
|
140
|
-
yield result if block_given?
|
141
|
-
|
142
|
-
cleanup
|
143
|
-
end
|
144
|
-
|
145
|
-
# Perform a single migration and inmediatly request a cleanup operation:
|
146
|
-
#
|
147
|
-
# print "Synchronising Cat model designs: "
|
148
|
-
# Cat.design_doc.migrate! do |res|
|
149
|
-
# puts res.to_s
|
150
|
-
# end
|
151
|
-
#
|
152
|
-
def migrate!(db = nil, &block)
|
153
|
-
callback = migrate(db, &block)
|
154
|
-
if callback.is_a?(Proc)
|
155
|
-
callback.call
|
156
|
-
else
|
157
|
-
callback
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
60
|
def checksum
|
162
61
|
sum = self['couchrest-hash']
|
163
62
|
if sum && (@_original_hash == to_hash)
|
@@ -209,6 +108,13 @@ module CouchRest
|
|
209
108
|
filters[name.to_s] = function
|
210
109
|
end
|
211
110
|
|
111
|
+
######## VIEW LIBS #########
|
112
|
+
|
113
|
+
def create_view_lib(name, function)
|
114
|
+
filters = (self['views']['lib'] ||= {})
|
115
|
+
filters[name.to_s] = function
|
116
|
+
end
|
117
|
+
|
212
118
|
protected
|
213
119
|
|
214
120
|
def load_from_database(db = database, id = nil)
|
@@ -221,7 +127,7 @@ module CouchRest
|
|
221
127
|
# Calculate and update the checksum of the Design document.
|
222
128
|
# Used for ensuring the latest version has been sent to the database.
|
223
129
|
#
|
224
|
-
# This will generate
|
130
|
+
# This will generate a flatterned, ordered array of all the elements of the
|
225
131
|
# design document, convert to string then generate an MD5 Hash. This should
|
226
132
|
# result in a consisitent Hash accross all platforms.
|
227
133
|
#
|
@@ -233,14 +139,13 @@ module CouchRest
|
|
233
139
|
base.delete('_id')
|
234
140
|
base.delete('_rev')
|
235
141
|
base.delete('couchrest-hash')
|
236
|
-
result = nil
|
237
142
|
flatten =
|
238
143
|
lambda {|r|
|
239
144
|
(recurse = lambda {|v|
|
240
145
|
if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
|
241
|
-
v.to_a.map{|
|
146
|
+
v.to_a.map{|p| recurse.call(p)}.flatten
|
242
147
|
elsif v.is_a?(Array)
|
243
|
-
v.flatten.map{|
|
148
|
+
v.flatten.map{|p| recurse.call(p)}
|
244
149
|
else
|
245
150
|
v.to_s
|
246
151
|
end
|
@@ -49,6 +49,11 @@ module CouchRest
|
|
49
49
|
design_doc.create_filter(name, function)
|
50
50
|
end
|
51
51
|
|
52
|
+
# Define a new view re-usable lib for shared functions.
|
53
|
+
def view_lib(name, function)
|
54
|
+
design_doc.create_view_lib(name, function)
|
55
|
+
end
|
56
|
+
|
52
57
|
# Convenience wrapper to access model's type key option.
|
53
58
|
def model_type_key
|
54
59
|
model.model_type_key
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module Designs
|
4
|
+
|
5
|
+
# Design Document Migrations Support
|
6
|
+
#
|
7
|
+
# A series of methods used inside design documents in order to perform migrations.
|
8
|
+
#
|
9
|
+
module Migrations
|
10
|
+
|
11
|
+
# Migrate the design document preventing downtime on a production
|
12
|
+
# system. Typically this will be used when auto updates are disabled.
|
13
|
+
#
|
14
|
+
# Steps taken are:
|
15
|
+
#
|
16
|
+
# 1. Compare the checksum with the current version
|
17
|
+
# 2. If different, create a new design doc with timestamp
|
18
|
+
# 3. Wait until the view returns a result
|
19
|
+
# 4. Copy over the original design doc
|
20
|
+
#
|
21
|
+
# If a block is provided, it will be called with the result of the migration:
|
22
|
+
#
|
23
|
+
# * :no_change - Nothing performed as there are no changes.
|
24
|
+
# * :created - Add a new design doc as non existed
|
25
|
+
# * :migrated - Migrated the existing design doc.
|
26
|
+
#
|
27
|
+
# This can be used for progressivly printing the results of the migration.
|
28
|
+
#
|
29
|
+
# After completion, either a "cleanup" Proc object will be provided to finalize
|
30
|
+
# the process and copy the document into place, or simply nil if no cleanup is
|
31
|
+
# required. For example:
|
32
|
+
#
|
33
|
+
# print "Synchronising Cat model designs: "
|
34
|
+
# callback = Cat.design_doc.migrate do |res|
|
35
|
+
# puts res.to_s
|
36
|
+
# end
|
37
|
+
# if callback
|
38
|
+
# puts "Cleaning up."
|
39
|
+
# callback.call
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
def migrate(db = nil, &block)
|
43
|
+
db ||= database
|
44
|
+
doc = load_from_database(db)
|
45
|
+
cleanup = nil
|
46
|
+
id = self['_id']
|
47
|
+
|
48
|
+
if !doc
|
49
|
+
# no need to migrate, just save it
|
50
|
+
new_doc = to_hash.dup
|
51
|
+
db.save_doc(new_doc)
|
52
|
+
|
53
|
+
result = :created
|
54
|
+
elsif doc['couchrest-hash'] != checksum
|
55
|
+
id += "_migration"
|
56
|
+
|
57
|
+
# Delete current migration if there is one
|
58
|
+
old_migration = load_from_database(db, id)
|
59
|
+
db.delete_doc(old_migration) if old_migration
|
60
|
+
|
61
|
+
# Save new design doc
|
62
|
+
new_doc = doc.merge(to_hash)
|
63
|
+
new_doc['_id'] = id
|
64
|
+
new_doc.delete('_rev')
|
65
|
+
db.save_doc(new_doc)
|
66
|
+
|
67
|
+
# Proc definition to copy the migration doc over the original
|
68
|
+
cleanup = Proc.new do
|
69
|
+
db.copy_doc(new_doc, doc)
|
70
|
+
db.delete_doc(new_doc)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
result = :migrated
|
75
|
+
else
|
76
|
+
# Already up to date
|
77
|
+
result = :no_change
|
78
|
+
end
|
79
|
+
|
80
|
+
wait_for_view_update_completion(db, new_doc)
|
81
|
+
|
82
|
+
yield result if block_given?
|
83
|
+
|
84
|
+
cleanup
|
85
|
+
end
|
86
|
+
|
87
|
+
# Perform a single migration and inmediatly request a cleanup operation:
|
88
|
+
#
|
89
|
+
# print "Synchronising Cat model designs: "
|
90
|
+
# Cat.design_doc.migrate! do |res|
|
91
|
+
# puts res.to_s
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
def migrate!(db = nil, &block)
|
95
|
+
callback = migrate(db, &block)
|
96
|
+
if callback.is_a?(Proc)
|
97
|
+
callback.call
|
98
|
+
else
|
99
|
+
callback
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def wait_for_view_update_completion(db, attrs)
|
106
|
+
if attrs && !attrs['views'].empty?
|
107
|
+
# Prepare a design doc we can use
|
108
|
+
doc = CouchRest::Design.new(attrs)
|
109
|
+
doc.database = db
|
110
|
+
|
111
|
+
# Request view, to trigger a *background* view update
|
112
|
+
doc.view(doc['views'].keys.first, :limit => 1, :stale => "update_after")
|
113
|
+
|
114
|
+
# Poll the view update process
|
115
|
+
while true
|
116
|
+
sleep 1
|
117
|
+
info = doc.info
|
118
|
+
if !info || !info['view_index']
|
119
|
+
raise "Migration error, unable to load design doc info: #{db.root}/#{doc.id}"
|
120
|
+
end
|
121
|
+
break if !info['view_index']['updater_running']
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -29,7 +29,7 @@ module CouchRest::Model
|
|
29
29
|
if array
|
30
30
|
if value.nil?
|
31
31
|
value = []
|
32
|
-
elsif
|
32
|
+
elsif value.is_a?(Hash)
|
33
33
|
# Assume provided as a params hash where key is index
|
34
34
|
value = parameter_hash_to_array(value)
|
35
35
|
elsif !value.is_a?(Array)
|
@@ -17,7 +17,8 @@ module CouchRest
|
|
17
17
|
def proxy_for(assoc_name, options = {})
|
18
18
|
db_method = options[:database_method] || "proxy_database"
|
19
19
|
options[:class_name] ||= assoc_name.to_s.singularize.camelize
|
20
|
-
|
20
|
+
proxy_method_names << assoc_name.to_sym unless proxy_method_names.include?(assoc_name.to_sym)
|
21
|
+
proxied_model_names << options[:class_name] unless proxied_model_names.include?(options[:class_name])
|
21
22
|
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
22
23
|
def #{assoc_name}
|
23
24
|
@#{assoc_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
|
@@ -48,6 +49,10 @@ module CouchRest
|
|
48
49
|
@proxy_database_method
|
49
50
|
end
|
50
51
|
|
52
|
+
def proxy_method_names
|
53
|
+
@proxy_method_names ||= []
|
54
|
+
end
|
55
|
+
|
51
56
|
def proxied_model_names
|
52
57
|
@proxied_model_names ||= []
|
53
58
|
end
|
@@ -59,7 +59,7 @@ module CouchRest
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def find_proxying_models
|
62
|
-
CouchRest::Model::Base.subclasses.reject{|m| m.
|
62
|
+
CouchRest::Model::Base.subclasses.reject{|m| m.proxy_method_names.empty?}
|
63
63
|
end
|
64
64
|
|
65
65
|
def migrate_each_model(models, db = nil)
|
@@ -75,17 +75,22 @@ module CouchRest
|
|
75
75
|
def migrate_each_proxying_model(models)
|
76
76
|
callbacks = [ ]
|
77
77
|
models.each do |model|
|
78
|
-
|
79
|
-
|
80
|
-
puts "Finding proxied models for #{model}
|
81
|
-
|
78
|
+
methods = model.proxy_method_names
|
79
|
+
methods.each do |method|
|
80
|
+
puts "Finding proxied models for #{model}##{method}"
|
81
|
+
model.all.each do |obj|
|
82
|
+
proxy = obj.send(method)
|
83
|
+
callbacks += migrate_each_model([proxy.model], proxy.database)
|
84
|
+
end
|
82
85
|
end
|
83
86
|
end
|
84
87
|
callbacks
|
85
88
|
end
|
86
89
|
|
87
90
|
def migrate_design(model, design, db = nil)
|
88
|
-
print "Migrating #{model.to_s}##{design.method_name}
|
91
|
+
print "Migrating #{model.to_s}##{design.method_name}"
|
92
|
+
print " on #{db.name}" if db
|
93
|
+
print "... "
|
89
94
|
callback = design.migrate(db) do |result|
|
90
95
|
puts "#{result.to_s.gsub(/_/, ' ')}"
|
91
96
|
end
|
data/lib/couchrest_model.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_model"
|
2
2
|
require "active_model/callbacks"
|
3
3
|
require "active_model/conversion"
|
4
4
|
require "active_model/errors"
|
@@ -9,22 +9,22 @@ require "active_model/validator"
|
|
9
9
|
require "active_model/validations"
|
10
10
|
require "active_model/dirty"
|
11
11
|
|
12
|
-
require
|
13
|
-
require
|
12
|
+
require "active_support/core_ext"
|
13
|
+
require "active_support/json"
|
14
14
|
|
15
|
-
require
|
15
|
+
require "mime/types"
|
16
16
|
require "enumerator"
|
17
17
|
require "time"
|
18
|
-
require
|
18
|
+
require "digest/md5"
|
19
19
|
|
20
|
-
require
|
21
|
-
require
|
20
|
+
require "bigdecimal" # used in typecast
|
21
|
+
require "bigdecimal/util" # used in typecast
|
22
22
|
|
23
|
-
require
|
23
|
+
require "couchrest"
|
24
24
|
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
25
|
+
require "couchrest/model"
|
26
|
+
require "couchrest/model/errors"
|
27
|
+
require "couchrest/model/translation"
|
28
28
|
require "couchrest/model/persistence"
|
29
29
|
require "couchrest/model/typecast"
|
30
30
|
require "couchrest/model/casted_by"
|
@@ -42,10 +42,11 @@ require "couchrest/model/proxyable"
|
|
42
42
|
require "couchrest/model/associations"
|
43
43
|
require "couchrest/model/configuration"
|
44
44
|
require "couchrest/model/connection"
|
45
|
-
require "couchrest/model/
|
46
|
-
require "couchrest/model/designs"
|
45
|
+
require "couchrest/model/designs/migrations"
|
47
46
|
require "couchrest/model/designs/design_mapper"
|
48
47
|
require "couchrest/model/designs/view"
|
48
|
+
require "couchrest/model/design"
|
49
|
+
require "couchrest/model/designs"
|
49
50
|
|
50
51
|
# Monkey patches applied to couchrest
|
51
52
|
require "couchrest/model/support/couchrest_database"
|
@@ -62,6 +63,4 @@ require "couchrest/model/base"
|
|
62
63
|
require "couchrest/model/utils/migrate.rb"
|
63
64
|
|
64
65
|
# Add rails support *after* everything has loaded
|
65
|
-
if defined?(Rails)
|
66
|
-
require "couchrest/railtie"
|
67
|
-
end
|
66
|
+
require "couchrest/railtie" if defined?(Rails)
|
@@ -126,6 +126,7 @@ describe "Assocations" do
|
|
126
126
|
@invoice.entry_ids = @entries.collect{|i| i.id}
|
127
127
|
@invoice.entries.length.should eql(3)
|
128
128
|
@invoice.entries.first.should eql(@entries.first)
|
129
|
+
@invoice.changed?.should be_true
|
129
130
|
end
|
130
131
|
|
131
132
|
it "should ignore blank ids when set directly" do
|
@@ -171,6 +172,10 @@ describe "Assocations" do
|
|
171
172
|
|
172
173
|
# Account for dirty tracking
|
173
174
|
describe "dirty tracking" do
|
175
|
+
it "should register changes on replacement" do
|
176
|
+
@invoice.entries = @entries
|
177
|
+
@invoice.changed?.should be_true
|
178
|
+
end
|
174
179
|
it "should register changes on push" do
|
175
180
|
@invoice.changed?.should be_false
|
176
181
|
@invoice.entries << @entries[0]
|
@@ -99,6 +99,21 @@ describe "Time Parsing core extension" do
|
|
99
99
|
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
100
100
|
end
|
101
101
|
|
102
|
+
it "should parse JSON time with second fractions" do
|
103
|
+
txt = "2014-12-11T16:53:54.000Z"
|
104
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2014, 12, 11, 16, 53, Rational(54000, 1000)))
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should avoid rounding errors parsing JSON time with second fractions" do
|
108
|
+
txt = "2014-12-11T16:53:54.548Z"
|
109
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2014, 12, 11, 16, 53, Rational(54548,1000)))
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
it "avoids seconds rounding error" do
|
115
|
+
time_string = "2014-12-11T16:54:54.549Z"
|
116
|
+
Time.parse_iso8601(time_string).as_json.should eql(time_string)
|
102
117
|
end
|
103
118
|
|
104
119
|
describe "resorting back to normal parse" do
|
data/spec/unit/design_spec.rb
CHANGED
@@ -163,84 +163,6 @@ describe CouchRest::Model::Design do
|
|
163
163
|
end
|
164
164
|
|
165
165
|
|
166
|
-
describe "#migrate" do
|
167
|
-
# WARNING! ORDER IS IMPORTANT!
|
168
|
-
|
169
|
-
describe "with limited changes" do
|
170
|
-
|
171
|
-
class DesignSampleModelMigrate < DesignSampleModelBase
|
172
|
-
end
|
173
|
-
|
174
|
-
before :all do
|
175
|
-
reset_test_db!
|
176
|
-
@mod = DesignSampleModelMigrate
|
177
|
-
@doc = @mod.design_doc
|
178
|
-
@db = @mod.database
|
179
|
-
end
|
180
|
-
|
181
|
-
it "should create new design if non exists" do
|
182
|
-
@db.should_receive(:view).with("#{@doc['_id']}/_view/#{@doc['views'].keys.first}", {:limit => 1, :reduce => false})
|
183
|
-
callback = @doc.migrate do |res|
|
184
|
-
res.should eql(:created)
|
185
|
-
end
|
186
|
-
doc = @db.get(@doc['_id'])
|
187
|
-
doc['views']['all'].should eql(@doc['views']['all'])
|
188
|
-
callback.should be_nil
|
189
|
-
end
|
190
|
-
|
191
|
-
it "should not change anything if design is up to date" do
|
192
|
-
@doc.sync
|
193
|
-
@db.should_not_receive(:view)
|
194
|
-
callback = @doc.migrate do |res|
|
195
|
-
res.should eql(:no_change)
|
196
|
-
end
|
197
|
-
callback.should be_nil
|
198
|
-
end
|
199
|
-
|
200
|
-
end
|
201
|
-
|
202
|
-
describe "migrating a document if there are changes" do
|
203
|
-
|
204
|
-
class DesignSampleModelMigrate2 < DesignSampleModelBase
|
205
|
-
end
|
206
|
-
|
207
|
-
before :all do
|
208
|
-
reset_test_db!
|
209
|
-
@mod = DesignSampleModelMigrate2
|
210
|
-
@doc = @mod.design_doc
|
211
|
-
@db = @mod.database
|
212
|
-
@doc.sync!
|
213
|
-
@doc.create_view(:by_name_and_surname)
|
214
|
-
@doc_id = @doc['_id'] + '_migration'
|
215
|
-
end
|
216
|
-
|
217
|
-
it "should save new migration design doc" do
|
218
|
-
@db.should_receive(:view).with("#{@doc_id}/_view/by_name", {:limit => 1, :reduce => false})
|
219
|
-
@callback = @doc.migrate do |res|
|
220
|
-
res.should eql(:migrated)
|
221
|
-
end
|
222
|
-
@callback.should_not be_nil
|
223
|
-
|
224
|
-
# should not have updated original view until cleanup
|
225
|
-
doc = @db.get(@doc['_id'])
|
226
|
-
doc['views'].should_not have_key('by_name_and_surname')
|
227
|
-
|
228
|
-
# Should have created the migration
|
229
|
-
new_doc = @db.get(@doc_id)
|
230
|
-
new_doc.should_not be_nil
|
231
|
-
|
232
|
-
# should be possible to perform cleanup
|
233
|
-
@callback.call
|
234
|
-
lambda { new_doc = @db.get(@doc_id) }.should raise_error RestClient::ResourceNotFound
|
235
|
-
|
236
|
-
doc = @db.get(@doc['_id'])
|
237
|
-
doc['views'].should have_key('by_name_and_surname')
|
238
|
-
end
|
239
|
-
|
240
|
-
end
|
241
|
-
|
242
|
-
end
|
243
|
-
|
244
166
|
|
245
167
|
describe "#checksum" do
|
246
168
|
|
@@ -346,6 +268,17 @@ describe CouchRest::Model::Design do
|
|
346
268
|
end
|
347
269
|
end
|
348
270
|
|
271
|
+
describe "#create_view_lib" do
|
272
|
+
before :each do
|
273
|
+
@doc = DesignSampleModel.design_doc
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should add simple view lib" do
|
277
|
+
@doc.create_view_lib('test', 'foobar')
|
278
|
+
@doc['views']['lib']['test'].should eql('foobar')
|
279
|
+
@doc['views']['lib'] = nil # cleanup
|
280
|
+
end
|
281
|
+
end
|
349
282
|
end
|
350
283
|
|
351
284
|
|
@@ -120,5 +120,19 @@ describe CouchRest::Model::Designs::DesignMapper do
|
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
|
+
describe "#view_lib" do
|
124
|
+
before :each do
|
125
|
+
@object = @klass.new(DesignModel)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should add the #view_lib function to the design doc" do
|
129
|
+
val = "exports.bar = 42;"
|
130
|
+
@object.view_lib(:foo, val)
|
131
|
+
DesignModel.design_doc['views']['lib'].should_not be_empty
|
132
|
+
DesignModel.design_doc['views']['lib'].should_not be_blank
|
133
|
+
DesignModel.design_doc['views']['lib']['foo'].should eql(val)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
123
137
|
end
|
124
138
|
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe CouchRest::Model::Designs::Migrations do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
reset_test_db!
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "base methods" do
|
11
|
+
|
12
|
+
describe "#migrate" do
|
13
|
+
# WARNING! ORDER IS IMPORTANT!
|
14
|
+
|
15
|
+
describe "with limited changes" do
|
16
|
+
|
17
|
+
class MigrationModelBase < CouchRest::Model::Base
|
18
|
+
use_database DB
|
19
|
+
property :name
|
20
|
+
property :surname
|
21
|
+
design do
|
22
|
+
view :by_name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class DesignSampleModelMigrate < MigrationModelBase
|
27
|
+
end
|
28
|
+
|
29
|
+
before :all do
|
30
|
+
reset_test_db!
|
31
|
+
@mod = DesignSampleModelMigrate
|
32
|
+
@doc = @mod.design_doc
|
33
|
+
@db = @mod.database
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create new design if non exists" do
|
37
|
+
@db.should_receive(:view).with("#{@doc.name}/#{@doc['views'].keys.first}", {
|
38
|
+
:limit => 1, :stale => 'update_after', :reduce => false
|
39
|
+
})
|
40
|
+
callback = @doc.migrate do |res|
|
41
|
+
res.should eql(:created)
|
42
|
+
end
|
43
|
+
doc = @db.get(@doc['_id'])
|
44
|
+
doc['views']['all'].should eql(@doc['views']['all'])
|
45
|
+
callback.should be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not change anything if design is up to date" do
|
49
|
+
@doc.sync
|
50
|
+
@db.should_not_receive(:view)
|
51
|
+
callback = @doc.migrate do |res|
|
52
|
+
res.should eql(:no_change)
|
53
|
+
end
|
54
|
+
callback.should be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "migrating a document if there are changes" do
|
60
|
+
|
61
|
+
class DesignSampleModelMigrate2 < MigrationModelBase
|
62
|
+
end
|
63
|
+
|
64
|
+
before :all do
|
65
|
+
reset_test_db!
|
66
|
+
@mod = DesignSampleModelMigrate2
|
67
|
+
@doc = @mod.design_doc
|
68
|
+
@db = @mod.database
|
69
|
+
@doc.sync!
|
70
|
+
@doc.create_view(:by_name_and_surname)
|
71
|
+
@doc_id = @doc['_id'] + '_migration'
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should save new migration design doc" do
|
75
|
+
@db.should_receive(:view).with("#{@doc.name}_migration/by_name", {
|
76
|
+
:limit => 1, :reduce => false, :stale => 'update_after'
|
77
|
+
})
|
78
|
+
@callback = @doc.migrate do |res|
|
79
|
+
res.should eql(:migrated)
|
80
|
+
end
|
81
|
+
@callback.should_not be_nil
|
82
|
+
|
83
|
+
# should not have updated original view until cleanup
|
84
|
+
doc = @db.get(@doc['_id'])
|
85
|
+
doc['views'].should_not have_key('by_name_and_surname')
|
86
|
+
|
87
|
+
# Should have created the migration
|
88
|
+
new_doc = @db.get(@doc_id)
|
89
|
+
new_doc.should_not be_nil
|
90
|
+
|
91
|
+
# should be possible to perform cleanup
|
92
|
+
@callback.call
|
93
|
+
lambda { new_doc = @db.get(@doc_id) }.should raise_error RestClient::ResourceNotFound
|
94
|
+
|
95
|
+
doc = @db.get(@doc['_id'])
|
96
|
+
doc['views'].should have_key('by_name_and_surname')
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
data/spec/unit/property_spec.rb
CHANGED
@@ -328,6 +328,15 @@ describe "properties of array of casted models" do
|
|
328
328
|
@course.questions.last.class.should eql(Question)
|
329
329
|
end
|
330
330
|
|
331
|
+
it "should allow attribute to be set from Hash subclass with ordered keys" do
|
332
|
+
ourhash = Class.new(HashWithIndifferentAccess)
|
333
|
+
hash = ourhash.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
|
334
|
+
@course.questions = hash
|
335
|
+
@course.questions.length.should eql(2)
|
336
|
+
@course.questions.last.q.should eql('Test2')
|
337
|
+
@course.questions.last.class.should eql(Question)
|
338
|
+
end
|
339
|
+
|
331
340
|
it "should raise an error if attempting to set single value for array type" do
|
332
341
|
lambda {
|
333
342
|
@course.questions = Question.new(:q => 'test1')
|
data/spec/unit/proxyable_spec.rb
CHANGED
@@ -78,6 +78,11 @@ describe CouchRest::Model::Proxyable do
|
|
78
78
|
@class.proxied_model_names.should eql(['Cat'])
|
79
79
|
end
|
80
80
|
|
81
|
+
it "should add method names to proxied method name array" do
|
82
|
+
@class.proxy_for(:cats)
|
83
|
+
@class.proxy_method_names.should eql([:cats])
|
84
|
+
end
|
85
|
+
|
81
86
|
it "should create a new method" do
|
82
87
|
DummyProxyable.stub!(:method_defined?).and_return(true)
|
83
88
|
DummyProxyable.proxy_for(:cats)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: couchrest_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Chris Anderson
|
@@ -12,22 +12,22 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2015-06-26 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: couchrest
|
19
19
|
requirement: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- - "
|
21
|
+
- - "~>"
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
23
|
+
version: 1.2.1
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
|
-
- - "
|
28
|
+
- - "~>"
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version:
|
30
|
+
version: 1.2.1
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: mime-types
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,6 +184,7 @@ files:
|
|
184
184
|
- lib/couchrest/model/design.rb
|
185
185
|
- lib/couchrest/model/designs.rb
|
186
186
|
- lib/couchrest/model/designs/design_mapper.rb
|
187
|
+
- lib/couchrest/model/designs/migrations.rb
|
187
188
|
- lib/couchrest/model/designs/view.rb
|
188
189
|
- lib/couchrest/model/dirty.rb
|
189
190
|
- lib/couchrest/model/document_queries.rb
|
@@ -253,6 +254,7 @@ files:
|
|
253
254
|
- spec/unit/core_extensions/time_parsing.rb
|
254
255
|
- spec/unit/design_spec.rb
|
255
256
|
- spec/unit/designs/design_mapper_spec.rb
|
257
|
+
- spec/unit/designs/migrations_spec.rb
|
256
258
|
- spec/unit/designs/view_spec.rb
|
257
259
|
- spec/unit/designs_spec.rb
|
258
260
|
- spec/unit/dirty_spec.rb
|
@@ -288,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
288
290
|
version: 1.3.1
|
289
291
|
requirements: []
|
290
292
|
rubyforge_project:
|
291
|
-
rubygems_version: 2.
|
293
|
+
rubygems_version: 2.4.6
|
292
294
|
signing_key:
|
293
295
|
specification_version: 4
|
294
296
|
summary: Extends the CouchRest Document for advanced modelling.
|
@@ -334,6 +336,7 @@ test_files:
|
|
334
336
|
- spec/unit/core_extensions/time_parsing.rb
|
335
337
|
- spec/unit/design_spec.rb
|
336
338
|
- spec/unit/designs/design_mapper_spec.rb
|
339
|
+
- spec/unit/designs/migrations_spec.rb
|
337
340
|
- spec/unit/designs/view_spec.rb
|
338
341
|
- spec/unit/designs_spec.rb
|
339
342
|
- spec/unit/dirty_spec.rb
|