couch_tomato 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE.txt +19 -0
- data/README.md +96 -0
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +10 -0
- data/lib/core_ext/duplicable.rb +43 -0
- data/lib/core_ext/extract_options.rb +14 -0
- data/lib/core_ext/inheritable_attributes.rb +222 -0
- data/lib/core_ext/object.rb +5 -0
- data/lib/core_ext/string.rb +19 -0
- data/lib/core_ext/symbol.rb +15 -0
- data/lib/core_ext/time.rb +12 -0
- data/lib/couch_tomato/database.rb +279 -0
- data/lib/couch_tomato/js_view_source.rb +182 -0
- data/lib/couch_tomato/migration.rb +52 -0
- data/lib/couch_tomato/migrator.rb +235 -0
- data/lib/couch_tomato/persistence/base.rb +62 -0
- data/lib/couch_tomato/persistence/belongs_to_property.rb +58 -0
- data/lib/couch_tomato/persistence/callbacks.rb +60 -0
- data/lib/couch_tomato/persistence/dirty_attributes.rb +27 -0
- data/lib/couch_tomato/persistence/json.rb +48 -0
- data/lib/couch_tomato/persistence/magic_timestamps.rb +15 -0
- data/lib/couch_tomato/persistence/properties.rb +58 -0
- data/lib/couch_tomato/persistence/simple_property.rb +97 -0
- data/lib/couch_tomato/persistence/validation.rb +18 -0
- data/lib/couch_tomato/persistence.rb +85 -0
- data/lib/couch_tomato/replicator.rb +50 -0
- data/lib/couch_tomato.rb +46 -0
- data/lib/tasks/couch_tomato.rake +128 -0
- data/rails/init.rb +7 -0
- data/spec/callbacks_spec.rb +271 -0
- data/spec/comment.rb +8 -0
- data/spec/create_spec.rb +22 -0
- data/spec/custom_view_spec.rb +134 -0
- data/spec/destroy_spec.rb +29 -0
- data/spec/fixtures/address.rb +9 -0
- data/spec/fixtures/person.rb +6 -0
- data/spec/property_spec.rb +103 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/unit/attributes_spec.rb +26 -0
- data/spec/unit/callbacks_spec.rb +33 -0
- data/spec/unit/create_spec.rb +58 -0
- data/spec/unit/customs_views_spec.rb +15 -0
- data/spec/unit/database_spec.rb +38 -0
- data/spec/unit/dirty_attributes_spec.rb +113 -0
- data/spec/unit/string_spec.rb +13 -0
- data/spec/unit/view_query_spec.rb +9 -0
- data/spec/update_spec.rb +40 -0
- data/test/test_helper.rb +63 -0
- data/test/unit/database_test.rb +285 -0
- data/test/unit/js_view_test.rb +362 -0
- data/test/unit/property_test.rb +193 -0
- metadata +133 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/couch_tomato.rb'
|
3
|
+
|
4
|
+
class JsViewTes < Test::Unit::TestCase
|
5
|
+
context "A Javascript View class" do
|
6
|
+
setup do
|
7
|
+
unload_const('TestView')
|
8
|
+
::TestView = create_const(CouchTomato::JsViewSource)
|
9
|
+
def gen_str(size=10)
|
10
|
+
(0...size).map{ ('a'..'z').to_a[rand(26)] }.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def gen_num(size=10)
|
14
|
+
(0...size).map{ (0..9).to_a[rand(10)] }.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def gen_alphanum(size=10)
|
18
|
+
(0...size).map{ ((0..9).to_a + ('a'..'z').to_a)[rand(36)] }.join
|
19
|
+
end
|
20
|
+
|
21
|
+
def gen_path(size=4)
|
22
|
+
"/" + Array.new(size).collect{ gen_str }.join("/")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
should "infer a single database name from the file system" do
|
27
|
+
path = gen_path
|
28
|
+
database = gen_str
|
29
|
+
stub(TestView).path { path }
|
30
|
+
stub(Dir).[](TestView.path + "/**") { ["#{path}/#{database}"] }
|
31
|
+
assert_equal TestView.fs_database_names, [database]
|
32
|
+
end
|
33
|
+
|
34
|
+
should "get a single design doc from couch server" do
|
35
|
+
name = gen_str
|
36
|
+
mock(db = Object.new).get(anything, anything) {{
|
37
|
+
"rows" => [{
|
38
|
+
"doc" => {
|
39
|
+
"_id"=>"_design/#{name}"
|
40
|
+
}
|
41
|
+
}]}
|
42
|
+
}
|
43
|
+
|
44
|
+
expected = { name.to_sym => { "_id"=>"_design/#{name}" } }
|
45
|
+
assert_equal TestView.db_design_docs(db), expected
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with views in the local filesystem" do
|
49
|
+
setup do
|
50
|
+
@path = gen_path
|
51
|
+
|
52
|
+
class VirtualFile
|
53
|
+
def initialize(content)
|
54
|
+
@content = content
|
55
|
+
end
|
56
|
+
|
57
|
+
def read
|
58
|
+
@content
|
59
|
+
end
|
60
|
+
|
61
|
+
def close
|
62
|
+
@content = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
should "raise an exception when an invalid javascript file path is provided for a local view" do
|
68
|
+
assert_raise Errno::ENOENT do
|
69
|
+
TestView.fs_view({}, "#{@path}/#{gen_str}-map.js")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
should "raise an exception when an empty hash is provided as a container for the local javascript" do
|
74
|
+
mock(TestView).open(anything) { VirtualFile.new("") }
|
75
|
+
assert_raise RuntimeError do
|
76
|
+
TestView.fs_view({}, "#{@path}/#{gen_str}-map.js")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
should "raise an exception if a passed javascript does not contain a map or reduce tag" do
|
81
|
+
mock(TestView).open(anything) { VirtualFile.new("") }
|
82
|
+
assert_raise RuntimeError do
|
83
|
+
TestView.fs_view({}, "#{@path}/#{gen_str}.js")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
should "create a proper hash containing the information from a single javascript (view) file" do
|
88
|
+
content = gen_str
|
89
|
+
view_name = gen_str
|
90
|
+
view_type = "map"
|
91
|
+
file_path = "#{@path}/#{view_name}-#{view_type}.js"
|
92
|
+
sha = gen_alphanum(40)
|
93
|
+
|
94
|
+
mock(TestView).open(anything) { VirtualFile.new(content) }
|
95
|
+
mock(Digest::SHA1).hexdigest(anything) { sha }
|
96
|
+
|
97
|
+
expected = { "views" => {
|
98
|
+
view_name => {
|
99
|
+
view_type => content,
|
100
|
+
"sha1-#{view_type}" => sha
|
101
|
+
}
|
102
|
+
}}
|
103
|
+
assert_equal TestView.fs_view({'views' => {}}, file_path), expected
|
104
|
+
end
|
105
|
+
|
106
|
+
should "properly accrue the views for a given design document/database(without any design docs)" do
|
107
|
+
design_name = gen_str
|
108
|
+
views = {
|
109
|
+
gen_str => {:map => { :content => gen_str, :sha1 => gen_alphanum(40) },
|
110
|
+
:reduce => { :content => gen_str, :sha1 => gen_alphanum(40) }},
|
111
|
+
gen_str => {:map => { :content => gen_str, :sha1 => gen_alphanum(40) },
|
112
|
+
:reduce => { :content => gen_str, :sha1 => gen_alphanum(40) }}
|
113
|
+
}
|
114
|
+
|
115
|
+
accrued_hash = {'views' => {}}
|
116
|
+
views.each do |view_name, view_types|
|
117
|
+
view_types.each do |type, view_data|
|
118
|
+
stub(TestView).open { VirtualFile.new(view_data[:content]) }
|
119
|
+
path = "#{@path}/#{design_name}/#{view_name}-#{type.to_s}.js"
|
120
|
+
stub(Digest::SHA1).hexdigest { view_data[:sha1] }
|
121
|
+
TestView.fs_view(accrued_hash, path)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
expected = {'views' => {}}
|
126
|
+
views.each do |view_name, view_types|
|
127
|
+
expected['views'][view_name] ||= {}
|
128
|
+
view_types.each do |type, view_data|
|
129
|
+
expected['views'][view_name][type.to_s] = view_data[:content]
|
130
|
+
expected['views'][view_name]["sha1-#{type.to_s}"] = view_data[:sha1]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
assert_equal accrued_hash, expected
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "with design documents in the local filesystem that need to be aggregated" do
|
138
|
+
setup do
|
139
|
+
@path = gen_path
|
140
|
+
@db_name = gen_str
|
141
|
+
stub(TestView).path { @path + "/" + @db_name }
|
142
|
+
|
143
|
+
def assign_path(views, design_doc=nil)
|
144
|
+
views.map do |view|
|
145
|
+
[@path, @db_name, design_doc, view].compact.join("/")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
should "get a hash of a design doc under a specific database with no design folders in the filesystem; no reduce functions" do
|
151
|
+
view = gen_str
|
152
|
+
view_components = ["#{view}-map.js"]
|
153
|
+
|
154
|
+
stub(Dir).[](TestView.path + "/**") { assign_path(view_components) }
|
155
|
+
fs_view_return = {
|
156
|
+
"views"=> {
|
157
|
+
"#{view}"=> {}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
stub(TestView).fs_view { fs_view_return }
|
162
|
+
expected = { @db_name.to_sym => fs_view_return }
|
163
|
+
assert_equal TestView.fs_design_docs(@db_name), expected
|
164
|
+
end
|
165
|
+
|
166
|
+
should "get a hash of a design doc under a specific database with no design folders in the filesystem; one reduce function" do
|
167
|
+
view = gen_str
|
168
|
+
view_components = ["#{view}-map.js", "#{view}-reduce.js"]
|
169
|
+
|
170
|
+
stub(Dir).[](TestView.path + "/**") { assign_path(view_components) }
|
171
|
+
fs_view_return = {
|
172
|
+
"views"=> {
|
173
|
+
"#{view}"=> { "reduce"=>gen_str, "map"=>gen_str }
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
stub(TestView).fs_view { fs_view_return }
|
178
|
+
expected = { @db_name.to_sym => fs_view_return }
|
179
|
+
assert_equal TestView.fs_design_docs(@db_name), expected
|
180
|
+
end
|
181
|
+
|
182
|
+
should "raise an exception when a reduce view is given without a corresponding map view under a specific database with no design folders" do
|
183
|
+
view = gen_str
|
184
|
+
view_components = ["#{view}-reduce.js"]
|
185
|
+
|
186
|
+
stub(Dir).[](TestView.path + "/**") { assign_path(view_components) }
|
187
|
+
fs_view_return = {
|
188
|
+
"views"=> {
|
189
|
+
"#{view}"=> { "reduce"=>gen_str }
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
stub(TestView).fs_view { fs_view_return }
|
194
|
+
assert_raise RuntimeError do
|
195
|
+
TestView.fs_design_docs(@db_name)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
should "get a hash of design docs under a specific database with multiple design folders" do
|
200
|
+
design_names = [gen_str, gen_str]
|
201
|
+
views = design_names.inject({}) do |doc_views, doc_name|
|
202
|
+
view = gen_str
|
203
|
+
doc_views.merge!(doc_name => ["#{view}-map.js", "#{view}-reduce.js"])
|
204
|
+
end
|
205
|
+
|
206
|
+
mock(Dir).[](TestView.path + "/**") { assign_path(design_names) }
|
207
|
+
mock(Dir).[]("#{TestView.path}/#{design_names[0]}/*.js") {
|
208
|
+
assign_path(views[design_names[0]], design_names[0]) }
|
209
|
+
mock(Dir).[]("#{TestView.path}/#{design_names[1]}/*.js") {
|
210
|
+
assign_path(views[design_names[1]], design_names[1]) }
|
211
|
+
|
212
|
+
expected = {
|
213
|
+
design_names[0].to_sym => {
|
214
|
+
"views"=> {
|
215
|
+
views[design_names[0]].first.split("-").first =>
|
216
|
+
{ "reduce"=>gen_str, "map"=>gen_str }
|
217
|
+
}
|
218
|
+
},
|
219
|
+
design_names[1].to_sym => {
|
220
|
+
"views"=> {
|
221
|
+
views[design_names[1]].first.split("-").first =>
|
222
|
+
{ "reduce"=>gen_str, "map"=>gen_str }
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
stub(TestView).fs_view(anything, /#{design_names[0]}/) { expected[design_names[0].to_sym] }
|
228
|
+
stub(TestView).fs_view(anything, /#{design_names[1]}/) { expected[design_names[1].to_sym] }
|
229
|
+
|
230
|
+
assert_equal TestView.fs_design_docs(@db_name), expected
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "with documents in either the filesystem, the database, or both" do
|
235
|
+
setup do
|
236
|
+
@path = gen_path
|
237
|
+
@db_name = gen_str
|
238
|
+
|
239
|
+
def gen_view_file(type)
|
240
|
+
{
|
241
|
+
type => gen_str,
|
242
|
+
"sha1-#{type}" => gen_alphanum(40)
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
def gen_view(name, elements)
|
247
|
+
view = {name => {}}
|
248
|
+
elements.each do |element|
|
249
|
+
view[name].merge!(gen_view_file(element.to_s))
|
250
|
+
end
|
251
|
+
view
|
252
|
+
end
|
253
|
+
|
254
|
+
def gen_design_doc(id, views)
|
255
|
+
design_doc = {
|
256
|
+
"_id" => "_design/#{id}",
|
257
|
+
"views" => {}
|
258
|
+
}
|
259
|
+
|
260
|
+
views.each do |view, types|
|
261
|
+
design_doc["views"].merge!(gen_view(view, types))
|
262
|
+
end
|
263
|
+
design_doc
|
264
|
+
end
|
265
|
+
|
266
|
+
def gen_db_hash(seed=rand(5))
|
267
|
+
design_docs = {}
|
268
|
+
types = [:map, :reduce]
|
269
|
+
(0..seed).each do
|
270
|
+
views = {}
|
271
|
+
(0..rand(seed)).each do
|
272
|
+
views.merge!(gen_str => types[0, rand(types.length) + 1])
|
273
|
+
end
|
274
|
+
design_name = gen_str
|
275
|
+
design_docs.merge!(design_name.to_sym => gen_design_doc(design_name, views))
|
276
|
+
end
|
277
|
+
design_docs
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
should "delete a document from the database when the same document is found on the filesystem with no views" do
|
282
|
+
databases = [ gen_str ]
|
283
|
+
mock(TestView).fs_database_names { databases }
|
284
|
+
db = Object.new
|
285
|
+
stub(TestView).database! { db }
|
286
|
+
fs_db = gen_db_hash(1)
|
287
|
+
remote_db = fs_db.clone
|
288
|
+
remote_db.values.each { |doc| doc.merge!("_rev" => gen_num) }
|
289
|
+
fs_db[fs_db.keys.first]["views"] = {}
|
290
|
+
|
291
|
+
mock(TestView).fs_design_docs(anything) { fs_db }
|
292
|
+
mock(TestView).db_design_docs(anything) { remote_db }
|
293
|
+
|
294
|
+
mock(db).delete_doc(anything) {}
|
295
|
+
stub(db).save_doc {}
|
296
|
+
|
297
|
+
TestView.push(true)
|
298
|
+
end
|
299
|
+
|
300
|
+
should "create an empty remote db if an empty db folder is found in the file system" do
|
301
|
+
db = Object.new
|
302
|
+
mock(TestView).fs_database_names { [gen_str] }
|
303
|
+
mock(TestView).database!(anything) { db }
|
304
|
+
|
305
|
+
fs_single_doc_name = gen_str
|
306
|
+
fs_db = {
|
307
|
+
fs_single_doc_name.to_sym => {
|
308
|
+
"_id" => "_design/#{fs_single_doc_name}",
|
309
|
+
"views" => {}}
|
310
|
+
}
|
311
|
+
remote_db = gen_db_hash(2)
|
312
|
+
remote_db.values.each { |doc| doc.merge!("_rev" => gen_num) }
|
313
|
+
mock(TestView).fs_design_docs(anything) { fs_db }
|
314
|
+
mock(TestView).db_design_docs(anything) { remote_db }
|
315
|
+
dont_allow(db).delete_doc(anything) {}
|
316
|
+
dont_allow(db).save_doc(anything) {}
|
317
|
+
|
318
|
+
TestView.push(true)
|
319
|
+
end
|
320
|
+
|
321
|
+
should "not update documents on the server if both file system and server documents are identical" do
|
322
|
+
db = Object.new
|
323
|
+
mock(TestView).fs_database_names { [gen_str] }
|
324
|
+
mock(TestView).database!(anything) { db }
|
325
|
+
|
326
|
+
fs_db = gen_db_hash(1)
|
327
|
+
remote_db = fs_db.clone
|
328
|
+
remote_db.values.each { |doc| doc.merge!("_rev" => gen_num) }
|
329
|
+
|
330
|
+
mock(TestView).fs_design_docs(anything) { fs_db }
|
331
|
+
mock(TestView).db_design_docs(anything) { remote_db }
|
332
|
+
dont_allow(db).delete_doc(anything) {}
|
333
|
+
dont_allow(db).save_doc(anything) {}
|
334
|
+
|
335
|
+
TestView.push(true)
|
336
|
+
end
|
337
|
+
|
338
|
+
should "properly added all new documents seen on the file system to the remote system." do
|
339
|
+
db = Object.new
|
340
|
+
mock(TestView).fs_database_names { [gen_str] }
|
341
|
+
mock(TestView).database!(anything) { db }
|
342
|
+
|
343
|
+
fs_db = gen_db_hash(2)
|
344
|
+
remote_db = gen_db_hash(2)
|
345
|
+
remote_db.values.each { |doc| doc.merge!("_rev" => gen_num) }
|
346
|
+
|
347
|
+
mock(TestView).fs_design_docs(anything) { fs_db }
|
348
|
+
mock(TestView).db_design_docs(anything) { remote_db }
|
349
|
+
dont_allow(db).delete_doc(anything) {}
|
350
|
+
mock(db).save_doc(anything).times(fs_db.length) {}
|
351
|
+
|
352
|
+
TestView.push(true)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
should "properly read fs view, create a remote database\
|
357
|
+
if necessary, and copy all the local views to the remote site (database)" do
|
358
|
+
stub(TestView).path { "#{File.dirname(__FILE__)}/../../test/integration" }
|
359
|
+
#TODO: Complete this integration test.
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/couch_tomato.rb'
|
3
|
+
|
4
|
+
class PropertyTes < Test::Unit::TestCase
|
5
|
+
context "A Model that will persist data using CouchTomato" do
|
6
|
+
setup do
|
7
|
+
unload_const('Comment')
|
8
|
+
::Comment = create_const
|
9
|
+
Comment.class_eval do
|
10
|
+
include CouchTomato::Persistence
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "raise an exception if native JSON data types are assigned to the properties" do
|
15
|
+
[Float, String, Integer, Array, Hash, Fixnum].each do |klass|
|
16
|
+
assert_raise RuntimeError do
|
17
|
+
Comment.class_eval do
|
18
|
+
property :title, :type => klass
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
should "raise an exception if an empty array is used as a property's type" do
|
25
|
+
assert_raise RuntimeError do
|
26
|
+
Comment.class_eval do
|
27
|
+
property :title, :type => []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
should "return the names of its properties using property_names" do
|
33
|
+
Comment.class_eval do
|
34
|
+
property :title
|
35
|
+
property :commenter
|
36
|
+
property :email
|
37
|
+
end
|
38
|
+
assert_equal Comment.property_names, [:created_at, :updated_at, :title, :commenter, :email]
|
39
|
+
end
|
40
|
+
|
41
|
+
should "respond to property name attribute accessor methods" do
|
42
|
+
Comment.class_eval do
|
43
|
+
property :title
|
44
|
+
end
|
45
|
+
|
46
|
+
@comment = Comment.new
|
47
|
+
|
48
|
+
assert @comment.respond_to?(:title)
|
49
|
+
assert @comment.respond_to?(:title=)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "mark as invalid a property which requires to have a value and has none" do
|
53
|
+
Comment.class_eval do
|
54
|
+
property :title
|
55
|
+
validates_presence_of :title
|
56
|
+
end
|
57
|
+
|
58
|
+
@comment = Comment.new
|
59
|
+
|
60
|
+
assert_equal @comment.valid?, false
|
61
|
+
|
62
|
+
@comment.title = "Title"
|
63
|
+
|
64
|
+
assert_equal @comment.valid?, true
|
65
|
+
end
|
66
|
+
|
67
|
+
should "call the validate callbacks when required" do
|
68
|
+
Comment.class_eval do
|
69
|
+
property :title
|
70
|
+
validates_presence_of :title
|
71
|
+
end
|
72
|
+
|
73
|
+
@comment = Comment.new :title => "My Title"
|
74
|
+
|
75
|
+
# stub(@comment).valid?{true}
|
76
|
+
mock(@comment).run_callbacks(:before_validation)
|
77
|
+
|
78
|
+
@comment.valid?
|
79
|
+
end
|
80
|
+
|
81
|
+
should "return true if a property is set" do
|
82
|
+
Comment.class_eval do
|
83
|
+
property :title
|
84
|
+
end
|
85
|
+
|
86
|
+
@comment = Comment.new
|
87
|
+
|
88
|
+
assert_equal @comment.title?, false
|
89
|
+
|
90
|
+
@comment.title = "Title"
|
91
|
+
|
92
|
+
assert_equal @comment.title?, true
|
93
|
+
end
|
94
|
+
|
95
|
+
context "and has callback methods defined" do
|
96
|
+
setup do
|
97
|
+
Comment.class_eval do
|
98
|
+
property :title
|
99
|
+
|
100
|
+
before_create :do_before
|
101
|
+
after_create :do_after
|
102
|
+
|
103
|
+
attr_reader :count_var
|
104
|
+
|
105
|
+
def do_before
|
106
|
+
@count_var = 0
|
107
|
+
@count_var +=3
|
108
|
+
end
|
109
|
+
|
110
|
+
def do_after
|
111
|
+
@count_var +=2
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
unload_const('TestDb')
|
117
|
+
::TestDb = create_const(CouchTomato::Database)
|
118
|
+
TestDb.couchrest_db = Object.new
|
119
|
+
|
120
|
+
@document = Comment.new :title => "My Title"
|
121
|
+
|
122
|
+
stub(@document).valid?{true}
|
123
|
+
|
124
|
+
stub(TestDb.database).save_doc{{'rev' => '1', 'id' => '123'}}
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
should "support single callbacks" do
|
129
|
+
stub(@document).new?{true}
|
130
|
+
stub(@document).dirty?{true}
|
131
|
+
|
132
|
+
TestDb.save_doc @document
|
133
|
+
|
134
|
+
assert_equal @document.count_var, 5
|
135
|
+
end
|
136
|
+
|
137
|
+
should "support multiple callbacks" do
|
138
|
+
stub(@document).new?{false}
|
139
|
+
stub(@document).dirty?{true}
|
140
|
+
|
141
|
+
Comment.class_eval do
|
142
|
+
before_update :do_before, :do_after
|
143
|
+
after_update :do_after
|
144
|
+
end
|
145
|
+
|
146
|
+
TestDb.save_doc @document
|
147
|
+
|
148
|
+
assert_equal @document.count_var, 7
|
149
|
+
end
|
150
|
+
end # context has callbacks defined
|
151
|
+
|
152
|
+
context "and wants to delete (destroy) a particular document" do
|
153
|
+
setup do
|
154
|
+
unload_const('TestDb')
|
155
|
+
::TestDb = create_const(CouchTomato::Database)
|
156
|
+
TestDb.couchrest_db = Object.new
|
157
|
+
|
158
|
+
@document = Comment.new
|
159
|
+
@document._deleted = false
|
160
|
+
@document._id = '123'
|
161
|
+
@document._rev = '456'
|
162
|
+
stub(@document).to_hash{1}
|
163
|
+
stub(TestDb.couchrest_db).delete_doc(@document.to_hash) {1}
|
164
|
+
mock(@document).run_callbacks(:before_destroy)
|
165
|
+
mock(@document).run_callbacks(:after_destroy)
|
166
|
+
end
|
167
|
+
|
168
|
+
should "call the destroy related callbacks" do
|
169
|
+
TestDb.destroy_doc(@document)
|
170
|
+
end
|
171
|
+
|
172
|
+
should "mark the document as deleted" do
|
173
|
+
TestDb.destroy_doc(@document)
|
174
|
+
|
175
|
+
assert_equal true, @document._deleted
|
176
|
+
end
|
177
|
+
|
178
|
+
should "make the document id nil" do
|
179
|
+
TestDb.destroy_doc(@document)
|
180
|
+
|
181
|
+
assert_equal nil, @document.id
|
182
|
+
end
|
183
|
+
|
184
|
+
should "make the document revision nil" do
|
185
|
+
TestDb.destroy_doc(@document)
|
186
|
+
|
187
|
+
assert_equal nil, @document._rev
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end #context a model that will persist data
|
192
|
+
|
193
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: couch_tomato
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Plastic Trophy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-18 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: validatable
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: couchrest
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0.24"
|
44
|
+
version:
|
45
|
+
description: Ruby persistence layer for CouchDB, inspired by and forked from Couch Potato
|
46
|
+
email: dev@plastictrophy.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.md
|
53
|
+
files:
|
54
|
+
- MIT-LICENSE.txt
|
55
|
+
- README.md
|
56
|
+
- init.rb
|
57
|
+
- lib/core_ext/date.rb
|
58
|
+
- lib/core_ext/duplicable.rb
|
59
|
+
- lib/core_ext/extract_options.rb
|
60
|
+
- lib/core_ext/inheritable_attributes.rb
|
61
|
+
- lib/core_ext/object.rb
|
62
|
+
- lib/core_ext/string.rb
|
63
|
+
- lib/core_ext/symbol.rb
|
64
|
+
- lib/core_ext/time.rb
|
65
|
+
- lib/couch_tomato.rb
|
66
|
+
- lib/couch_tomato/database.rb
|
67
|
+
- lib/couch_tomato/js_view_source.rb
|
68
|
+
- lib/couch_tomato/migration.rb
|
69
|
+
- lib/couch_tomato/migrator.rb
|
70
|
+
- lib/couch_tomato/persistence.rb
|
71
|
+
- lib/couch_tomato/persistence/base.rb
|
72
|
+
- lib/couch_tomato/persistence/belongs_to_property.rb
|
73
|
+
- lib/couch_tomato/persistence/callbacks.rb
|
74
|
+
- lib/couch_tomato/persistence/dirty_attributes.rb
|
75
|
+
- lib/couch_tomato/persistence/json.rb
|
76
|
+
- lib/couch_tomato/persistence/magic_timestamps.rb
|
77
|
+
- lib/couch_tomato/persistence/properties.rb
|
78
|
+
- lib/couch_tomato/persistence/simple_property.rb
|
79
|
+
- lib/couch_tomato/persistence/validation.rb
|
80
|
+
- lib/couch_tomato/replicator.rb
|
81
|
+
- lib/tasks/couch_tomato.rake
|
82
|
+
- rails/init.rb
|
83
|
+
has_rdoc: true
|
84
|
+
homepage: http://github.com/plastictrophy/couch_tomato
|
85
|
+
licenses: []
|
86
|
+
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options:
|
89
|
+
- --charset=UTF-8
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
version:
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.3.5
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Ruby persistence layer for CouchDB, inspired by and forked from Couch Potato
|
111
|
+
test_files:
|
112
|
+
- spec/callbacks_spec.rb
|
113
|
+
- spec/comment.rb
|
114
|
+
- spec/create_spec.rb
|
115
|
+
- spec/custom_view_spec.rb
|
116
|
+
- spec/destroy_spec.rb
|
117
|
+
- spec/fixtures/address.rb
|
118
|
+
- spec/fixtures/person.rb
|
119
|
+
- spec/property_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
- spec/unit/attributes_spec.rb
|
122
|
+
- spec/unit/callbacks_spec.rb
|
123
|
+
- spec/unit/create_spec.rb
|
124
|
+
- spec/unit/customs_views_spec.rb
|
125
|
+
- spec/unit/database_spec.rb
|
126
|
+
- spec/unit/dirty_attributes_spec.rb
|
127
|
+
- spec/unit/string_spec.rb
|
128
|
+
- spec/unit/view_query_spec.rb
|
129
|
+
- spec/update_spec.rb
|
130
|
+
- test/test_helper.rb
|
131
|
+
- test/unit/database_test.rb
|
132
|
+
- test/unit/js_view_test.rb
|
133
|
+
- test/unit/property_test.rb
|