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
data/test/test_helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'rr'
|
5
|
+
require 'couchrest'
|
6
|
+
require File.dirname(__FILE__) + '/../lib/couch_tomato.rb'
|
7
|
+
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
10
|
+
|
11
|
+
unless Test::Unit::TestCase.include?(RR::Adapters::TestUnit)
|
12
|
+
class Test::Unit::TestCase
|
13
|
+
include RR::Adapters::TestUnit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def unload_const (klass)
|
19
|
+
if Object.send :const_defined?, klass
|
20
|
+
Object.send :remove_const, klass
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_const(super_klass=nil)
|
25
|
+
super_klass ? Class.new(super_klass) : Class.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# TestDb = create_const(CouchTomato::Database)
|
29
|
+
|
30
|
+
# class Test::Unit::TestCase
|
31
|
+
# include RR::Adapters::TestUnit unless include?(RR::Adapters::TestUnit)
|
32
|
+
# end
|
33
|
+
|
34
|
+
# 3.times {puts}
|
35
|
+
|
36
|
+
# class TestDb > CouchTomato::Database
|
37
|
+
# name 'couch_tomato_test'
|
38
|
+
# end
|
39
|
+
|
40
|
+
# CouchTomato::Config.database_name = 'couch_tomato_test'
|
41
|
+
# CouchTomato::Config.database_server = 'http://127.0.0.1:5984/'
|
42
|
+
|
43
|
+
|
44
|
+
# class Comment
|
45
|
+
# include CouchTomato::Persistence
|
46
|
+
#
|
47
|
+
# validates_presence_of :title
|
48
|
+
#
|
49
|
+
# property :title
|
50
|
+
# belongs_to :commenter
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# def recreate_db
|
54
|
+
# CouchTomato.couchrest_database.delete! rescue nil
|
55
|
+
# CouchTomato.couchrest_database.server.create_db CouchTomato::Config.database_name
|
56
|
+
# end
|
57
|
+
# recreate_db
|
58
|
+
#
|
59
|
+
# Spec::Matchers.define :string_matching do |regex|
|
60
|
+
# match do |string|
|
61
|
+
# string =~ regex
|
62
|
+
# end
|
63
|
+
# end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/couch_tomato.rb'
|
3
|
+
|
4
|
+
module Foo
|
5
|
+
class Bar
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DatabaseTes < Test::Unit::TestCase
|
10
|
+
context "A Database class" do
|
11
|
+
setup do
|
12
|
+
unload_const('TestDb')
|
13
|
+
::TestDb = create_const(CouchTomato::Database)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "have a database name" do
|
17
|
+
TestDb.class_eval do
|
18
|
+
name 'hello-world'
|
19
|
+
end
|
20
|
+
assert_equal TestDb.database_name, 'hello-world'
|
21
|
+
end # have db name
|
22
|
+
|
23
|
+
should "assign the localhost as server if none is given" do
|
24
|
+
@db = Object.new
|
25
|
+
stub(TestDb).database_name{'hello-world'}
|
26
|
+
stub(TestDb).database{@db}
|
27
|
+
stub(@db).info{1}
|
28
|
+
|
29
|
+
TestDb.class_eval do
|
30
|
+
server
|
31
|
+
end
|
32
|
+
assert_equal TestDb.database_server, 'http://127.0.0.1:5984/'
|
33
|
+
end #instatiate couchrest
|
34
|
+
|
35
|
+
should "raise an exception if the database doesn't exist" do
|
36
|
+
assert_raise RestClient::RequestFailed do
|
37
|
+
TestDb.class_eval do
|
38
|
+
name 'DoesNotExist'
|
39
|
+
server # 'http://127.0.0.1:5984/does-not-exist'
|
40
|
+
end
|
41
|
+
TestDb.database
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with a correctly configured database and server" do
|
46
|
+
setup do
|
47
|
+
stub(TestDb).database_name{'hello-world'}
|
48
|
+
stub(TestDb).database_server{'http://127.0.0.1:5984/'}
|
49
|
+
stub(TestDb.database).info{1}
|
50
|
+
end
|
51
|
+
|
52
|
+
should "raise an exception if no nemonic is given for the view" do
|
53
|
+
assert_raise ArgumentError do
|
54
|
+
TestDb.class_eval do
|
55
|
+
view
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end # raise an exception
|
59
|
+
|
60
|
+
should "use nemonic name for view name if none is given" do
|
61
|
+
TestDb.class_eval do
|
62
|
+
view :hello
|
63
|
+
end
|
64
|
+
assert_equal TestDb.views[:hello][:view_name], 'hello'
|
65
|
+
end #should have data from view
|
66
|
+
|
67
|
+
should "use database name for design doc name if none is given" do
|
68
|
+
TestDb.class_eval do
|
69
|
+
view :hello
|
70
|
+
end
|
71
|
+
assert_equal TestDb.views[:hello][:design_doc].to_s, 'hello-world'
|
72
|
+
end #should have data from view
|
73
|
+
|
74
|
+
context "if given a document to save" do
|
75
|
+
setup do
|
76
|
+
@document = Object.new
|
77
|
+
stub(@document).dirty?{1}
|
78
|
+
end
|
79
|
+
|
80
|
+
should "call create_doc if the document is new" do
|
81
|
+
stub(@document).new?{1}
|
82
|
+
stub(TestDb).create_document {true}
|
83
|
+
dont_allow(TestDb).update_document(@document)
|
84
|
+
assert_equal TestDb.save_document(@document), true
|
85
|
+
end
|
86
|
+
|
87
|
+
should "call update_doc if the document is not new" do
|
88
|
+
stub(@document).new?{nil}
|
89
|
+
stub(TestDb).update_document {true}
|
90
|
+
dont_allow(TestDb).create_document(@document)
|
91
|
+
assert_equal TestDb.save_document(@document), true
|
92
|
+
end
|
93
|
+
|
94
|
+
context ", " do
|
95
|
+
setup do
|
96
|
+
stub(@document).valid?{1}
|
97
|
+
stub(@document).to_hash{1}
|
98
|
+
|
99
|
+
stub(TestDb.database).save_doc{{'rev' => '1', 'id' => '123', }}
|
100
|
+
stub(@document)._rev=('1')
|
101
|
+
|
102
|
+
mock(@document).run_callbacks(:before_validation_on_save)
|
103
|
+
mock(@document).run_callbacks(:before_save)
|
104
|
+
mock(@document).run_callbacks(:after_save)
|
105
|
+
end
|
106
|
+
|
107
|
+
should ", if the document is new, call the required callbacks before saving it" do
|
108
|
+
stub(@document).new?{1}
|
109
|
+
|
110
|
+
stub(@document).database=(TestDb)
|
111
|
+
stub(@document)._id=('123')
|
112
|
+
|
113
|
+
mock(@document).run_callbacks(:before_validation_on_create)
|
114
|
+
mock(@document).run_callbacks(:before_create)
|
115
|
+
mock(@document).run_callbacks(:after_create)
|
116
|
+
|
117
|
+
assert_equal TestDb.save_document(@document), true
|
118
|
+
end
|
119
|
+
|
120
|
+
should ", if the document is not new, call the required callbacks before saving it" do
|
121
|
+
stub(@document).new?{nil}
|
122
|
+
|
123
|
+
mock(@document).run_callbacks(:before_validation_on_update)
|
124
|
+
mock(@document).run_callbacks(:before_update)
|
125
|
+
mock(@document).run_callbacks(:after_update)
|
126
|
+
|
127
|
+
assert_equal TestDb.save_document(@document), true
|
128
|
+
end
|
129
|
+
end # context ,
|
130
|
+
end #context if given a doc to save
|
131
|
+
|
132
|
+
context "if loading a document" do
|
133
|
+
setup do
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
should "raise an exception if no id is given" do
|
138
|
+
assert_raise ArgumentError do
|
139
|
+
TestDb.load_doc
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
should "raise an exception if a nil id is given" do
|
144
|
+
assert_raise RuntimeError do
|
145
|
+
TestDb.load_doc nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
should "return nil if no document matching the given id is found" do
|
150
|
+
stub(TestDb.database).get('123456789'){raise RestClient::ResourceNotFound}
|
151
|
+
document = TestDb.load_doc '123456789'
|
152
|
+
assert_equal document, nil
|
153
|
+
end
|
154
|
+
|
155
|
+
should "return a hash if :model => :raw is given as an option" do
|
156
|
+
stub(TestDb.database).get('123456789'){{:key => "value", :key2 => "value2", }}
|
157
|
+
document = TestDb.load_doc '123456789', :model => :raw
|
158
|
+
assert_equal document, {:key => "value", :key2 => "value2", }
|
159
|
+
end
|
160
|
+
|
161
|
+
should "return a hash if the document does not specify a class" do
|
162
|
+
stub(TestDb.database).get('123456789'){{:key => "value", :key2 => "value2", }}
|
163
|
+
document = TestDb.load_doc '123456789'
|
164
|
+
assert_equal document, {:key => "value", :key2 => "value2", }
|
165
|
+
end
|
166
|
+
|
167
|
+
should "return the right class of object if one is specified in the document" do
|
168
|
+
stub(TestDb.database).get('123456789'){{'ruby_class' => 'Object', :key2 => "value2", }}
|
169
|
+
document = TestDb.load_doc '123456789'
|
170
|
+
# document = TestDb.load_doc '123456789', :model => Object
|
171
|
+
|
172
|
+
assert_equal document.class, Object
|
173
|
+
end
|
174
|
+
|
175
|
+
should "find classes namespeced within other classes" do
|
176
|
+
stub(TestDb.database).get('123456789'){{'ruby_class' => 'Foo::Bar', :key2 => "value2", }}
|
177
|
+
document = TestDb.load_doc '123456789'
|
178
|
+
|
179
|
+
assert_equal document.class, Foo::Bar
|
180
|
+
end
|
181
|
+
end #context loading document
|
182
|
+
|
183
|
+
context "if querying a view" do
|
184
|
+
setup do
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
should "raise an exception if the view is not found in the DB class" do
|
189
|
+
# self.query_view(name, options.merge(self.views[name][:couch_options]))
|
190
|
+
assert_raise RuntimeError do
|
191
|
+
TestDb.query_view!(:query_name)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
should "raise an exception if the view is not found in the database" do
|
196
|
+
stub(TestDb).query_view(:query_name,{}){raise RestClient::ResourceNotFound}
|
197
|
+
stub(TestDb).views{{:query_name => "query_name"}}
|
198
|
+
|
199
|
+
assert_raise RestClient::ResourceNotFound do
|
200
|
+
TestDb.query_view!(:query_name)
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
context "the results" do
|
206
|
+
setup do
|
207
|
+
@fields = {:"key" => "value", :"key2" => "value2"}
|
208
|
+
@fields2 = {:"key3" => "value3", :"key4" => "value4"}
|
209
|
+
|
210
|
+
@row1 = {"id" => "123456789", "value" => @fields, "key" => "7654321"}
|
211
|
+
@row2 = {"id" => "987654321", "value" => @fields2, "key" => "1234567"}
|
212
|
+
end
|
213
|
+
|
214
|
+
should "return an array of hashes if the documents do not cotain Class info" do
|
215
|
+
stub(TestDb).query_view(:query_name,{}){{"rows" => [@row1, @row2], "offset" => 0, "total_rows" => 2 }}
|
216
|
+
stub(TestDb).views{{:query_name => {:anything => "query_name"}}}
|
217
|
+
|
218
|
+
assert_equal TestDb.query_view!(:query_name), [@fields, @fields2]
|
219
|
+
end
|
220
|
+
|
221
|
+
should "find classes namespeced within other classes"
|
222
|
+
|
223
|
+
should "return an empty array if results is empty" do
|
224
|
+
stub(TestDb).views{{:query_name => {:anything => "query_name"}}}
|
225
|
+
|
226
|
+
empty_results = {"rows" => [], "offset" => 0, "total_rows" => 2 }
|
227
|
+
assert_equal TestDb.process_results(:query_name, empty_results), []
|
228
|
+
end
|
229
|
+
|
230
|
+
should "return an array of hashes if the documents cotain Class info but the user specified :model=> :raw" do
|
231
|
+
@fields.merge!({"ruby_class" => "Object"})
|
232
|
+
@fields2.merge!({"ruby_class" => "Object"})
|
233
|
+
|
234
|
+
stub(TestDb).query_view(:query_name,{:model => :raw}){{"rows" => [@row1, @row2], "offset" => 0, "total_rows" => 2 }}
|
235
|
+
stub(TestDb).views{{:query_name => {:anything => "query_name"}}}
|
236
|
+
|
237
|
+
assert_equal TestDb.query_view!(:query_name, :model => :raw ), [@fields, @fields2]
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
should "return an array of Objects if the view definition specified :model=> Object" do
|
242
|
+
@fields.merge!({"ruby_class" => "Object"})
|
243
|
+
@fields2.merge!({"ruby_class" => "Object"})
|
244
|
+
|
245
|
+
stub(TestDb).query_view(:query_name,{}){{"rows" => [@row1, @row2], "offset" => 0, "total_rows" => 2 }}
|
246
|
+
stub(TestDb).views{{:query_name => {:model => Object}}}
|
247
|
+
stub(Object).json_create(@fields, {"id"=>"123456789", "key"=>"7654321"}){Object.new}
|
248
|
+
stub(Object).json_create(@fields2, {"id"=>"987654321", "key"=>"1234567"}){Object.new}
|
249
|
+
|
250
|
+
|
251
|
+
assert_equal TestDb.query_view!(:query_name, {})[0].class, Object
|
252
|
+
assert_equal TestDb.query_view!(:query_name, {})[1].class, Object
|
253
|
+
end
|
254
|
+
|
255
|
+
should "return an array of Objects if the view definition did not specify :model=> Object but the docs contain 'ruby_class' info" do
|
256
|
+
@fields.merge!({"ruby_class" => "Object"})
|
257
|
+
@fields2.merge!({"ruby_class" => "Object"})
|
258
|
+
|
259
|
+
stub(TestDb).query_view(:query_name,{}){{"rows" => [@row1, @row2], "offset" => 0, "total_rows" => 2 }}
|
260
|
+
stub(TestDb).views{{:query_name => {:anything => "query_name"}}}
|
261
|
+
stub(Object).json_create(@fields, {"id"=>"123456789", "key"=>"7654321"}){Object.new}
|
262
|
+
stub(Object).json_create(@fields2, {"id"=>"987654321", "key"=>"1234567"}){Object.new}
|
263
|
+
|
264
|
+
assert_equal TestDb.query_view!(:query_name, {})[0].class, Object
|
265
|
+
assert_equal TestDb.query_view!(:query_name, {})[1].class, Object
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
should "return an mixed array of Objects and hashes if the view definition did not specify :model=> Object but some docs contain 'ruby_class' info" do
|
270
|
+
@fields.merge!({"ruby_class" => "Object"})
|
271
|
+
|
272
|
+
stub(TestDb).query_view(:query_name,{}){{"rows" => [@row1, @row2], "offset" => 0, "total_rows" => 2 }}
|
273
|
+
stub(TestDb).views{{:query_name => {:anything => "query_name"}}}
|
274
|
+
stub(Object).json_create(@fields, {"id"=>"123456789", "key"=>"7654321"}){Object.new}
|
275
|
+
stub(Hash).json_create(@fields2, {"id"=>"987654321", "key"=>"1234567"}){Object.new}
|
276
|
+
|
277
|
+
assert_equal TestDb.query_view!(:query_name, {})[0].class, Object
|
278
|
+
assert_equal TestDb.query_view!(:query_name, {})[1].class, Hash
|
279
|
+
|
280
|
+
end
|
281
|
+
end # context the results
|
282
|
+
end # context querying a view
|
283
|
+
end # context with db and serv
|
284
|
+
end #context a Db
|
285
|
+
end # class
|