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.
Files changed (52) hide show
  1. data/MIT-LICENSE.txt +19 -0
  2. data/README.md +96 -0
  3. data/init.rb +3 -0
  4. data/lib/core_ext/date.rb +10 -0
  5. data/lib/core_ext/duplicable.rb +43 -0
  6. data/lib/core_ext/extract_options.rb +14 -0
  7. data/lib/core_ext/inheritable_attributes.rb +222 -0
  8. data/lib/core_ext/object.rb +5 -0
  9. data/lib/core_ext/string.rb +19 -0
  10. data/lib/core_ext/symbol.rb +15 -0
  11. data/lib/core_ext/time.rb +12 -0
  12. data/lib/couch_tomato/database.rb +279 -0
  13. data/lib/couch_tomato/js_view_source.rb +182 -0
  14. data/lib/couch_tomato/migration.rb +52 -0
  15. data/lib/couch_tomato/migrator.rb +235 -0
  16. data/lib/couch_tomato/persistence/base.rb +62 -0
  17. data/lib/couch_tomato/persistence/belongs_to_property.rb +58 -0
  18. data/lib/couch_tomato/persistence/callbacks.rb +60 -0
  19. data/lib/couch_tomato/persistence/dirty_attributes.rb +27 -0
  20. data/lib/couch_tomato/persistence/json.rb +48 -0
  21. data/lib/couch_tomato/persistence/magic_timestamps.rb +15 -0
  22. data/lib/couch_tomato/persistence/properties.rb +58 -0
  23. data/lib/couch_tomato/persistence/simple_property.rb +97 -0
  24. data/lib/couch_tomato/persistence/validation.rb +18 -0
  25. data/lib/couch_tomato/persistence.rb +85 -0
  26. data/lib/couch_tomato/replicator.rb +50 -0
  27. data/lib/couch_tomato.rb +46 -0
  28. data/lib/tasks/couch_tomato.rake +128 -0
  29. data/rails/init.rb +7 -0
  30. data/spec/callbacks_spec.rb +271 -0
  31. data/spec/comment.rb +8 -0
  32. data/spec/create_spec.rb +22 -0
  33. data/spec/custom_view_spec.rb +134 -0
  34. data/spec/destroy_spec.rb +29 -0
  35. data/spec/fixtures/address.rb +9 -0
  36. data/spec/fixtures/person.rb +6 -0
  37. data/spec/property_spec.rb +103 -0
  38. data/spec/spec_helper.rb +40 -0
  39. data/spec/unit/attributes_spec.rb +26 -0
  40. data/spec/unit/callbacks_spec.rb +33 -0
  41. data/spec/unit/create_spec.rb +58 -0
  42. data/spec/unit/customs_views_spec.rb +15 -0
  43. data/spec/unit/database_spec.rb +38 -0
  44. data/spec/unit/dirty_attributes_spec.rb +113 -0
  45. data/spec/unit/string_spec.rb +13 -0
  46. data/spec/unit/view_query_spec.rb +9 -0
  47. data/spec/update_spec.rb +40 -0
  48. data/test/test_helper.rb +63 -0
  49. data/test/unit/database_test.rb +285 -0
  50. data/test/unit/js_view_test.rb +362 -0
  51. data/test/unit/property_test.rb +193 -0
  52. 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