couchrest_model 2.0.3 → 2.0.4
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.
- 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
|