yssk22-couch_resource 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/LICENSE +2 -0
- data/MIT_LICENSE +8 -0
- data/README.rdoc +7 -0
- data/lib/couch_resource.rb +25 -0
- data/lib/couch_resource/base.rb +705 -0
- data/lib/couch_resource/callbacks.rb +103 -0
- data/lib/couch_resource/connection.rb +194 -0
- data/lib/couch_resource/error.rb +3 -0
- data/lib/couch_resource/struct.rb +340 -0
- data/lib/couch_resource/validations.rb +520 -0
- data/lib/couch_resource/view.rb +228 -0
- data/test/test_base.rb +384 -0
- data/test/test_callbacks.rb +115 -0
- data/test/test_connection.rb +39 -0
- data/test/test_struct.rb +332 -0
- data/test/test_validations.rb +186 -0
- metadata +70 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
#
|
7
|
+
# == Synchronization of design documents
|
8
|
+
#
|
9
|
+
# Synchronization mechanism of design documents is controlled by <tt>check_design_revision_every_time</tt>.
|
10
|
+
# The value is true, the finder methods of views always check the revision of the design document.
|
11
|
+
# When false, the finder methods only check at first time.
|
12
|
+
#
|
13
|
+
module CouchResource
|
14
|
+
module View
|
15
|
+
def self.included(base)
|
16
|
+
base.send(:extend, ClassMethods)
|
17
|
+
# base.send(:include, InstanceMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Returns the design path specified by the <tt>design</tt> and <tt>view</tt> name.
|
22
|
+
def design_path(design, query_options=nil)
|
23
|
+
design_fullname = get_design_fullname(design)
|
24
|
+
# document_path("_design%2F#{design_fullname}", query_options)
|
25
|
+
document_path("_design/#{design_fullname}", query_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns the view path specified by the <tt>design</tt> and <tt>view</tt> name.
|
29
|
+
def view_path(design, view, query_options=nil)
|
30
|
+
design_fullname = get_design_fullname(design)
|
31
|
+
view_options = {}
|
32
|
+
if query_options
|
33
|
+
# convert query args to json value
|
34
|
+
[:key, :startkey, :endkey].each do |arg|
|
35
|
+
view_options[arg] = query_options[arg].to_json if query_options.has_key?(arg)
|
36
|
+
end
|
37
|
+
|
38
|
+
# do not care
|
39
|
+
[:include_docs, :update, :descending, :group, :startkey_docid, :endkey_docid, :count, :skip, :group_level].each do |arg|
|
40
|
+
view_options[arg] = query_options[arg] if query_options.has_key?(arg)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
document_path(File.join("_view", design_fullname, view.to_s), view_options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the full name of design document.
|
47
|
+
def get_design_fullname(design)
|
48
|
+
"#{self.to_s}_#{design}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the view definition hash which contains :map key and :reduce key (optional).
|
52
|
+
def get_view_definition(design, view)
|
53
|
+
design_documents = read_inheritable_attribute(:design_documents) || {}
|
54
|
+
design_doc = design_documents[design.to_s]
|
55
|
+
return nil unless design_doc
|
56
|
+
return design_doc[:views][view]
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Define view on the server access method for this class.
|
61
|
+
#
|
62
|
+
# for example, the following code defines a design_document at _design/SomeDocument_my_design ::
|
63
|
+
#
|
64
|
+
# class SomeDocument
|
65
|
+
# view :my_design, :sample_view => {
|
66
|
+
# :map => include_js("path/to/map.js")
|
67
|
+
# :reduce => include_js("path/to/reduce.js")
|
68
|
+
# }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# The design document include one view whose name is 'sample_view'.
|
72
|
+
# And following 4 methods will be available in SomeDocument class.
|
73
|
+
#
|
74
|
+
# SomeDocument.find_my_design_sample_view()
|
75
|
+
# SomeDocument.find_my_design_sample_view_first()
|
76
|
+
# SomeDocument.find_my_design_sample_view_last()
|
77
|
+
# SomeDocument.find_my_design_sample_view_all()
|
78
|
+
#
|
79
|
+
# The design document actually stored on the server at the first time when the above methods are invoked.
|
80
|
+
#
|
81
|
+
def view(design, views)
|
82
|
+
# append prefix to design
|
83
|
+
# Klass_design is a proper design document name
|
84
|
+
design_fullname = get_design_fullname(design)
|
85
|
+
|
86
|
+
design_document = {
|
87
|
+
:_id => "_design/#{design_fullname}",
|
88
|
+
:language => "javascript",
|
89
|
+
:views => views
|
90
|
+
}
|
91
|
+
# Get the design document revision if already exists.
|
92
|
+
logger.debug "Design Doc: get the existance revision."
|
93
|
+
rev = connection.get(design_path(design))[:_rev] rescue nil
|
94
|
+
design_document[:_rev] = rev if rev
|
95
|
+
logger.debug "Design Doc: revision: #{rev || 'not found'}"
|
96
|
+
|
97
|
+
# Update inheritable attribute for design_documents
|
98
|
+
design_documents = read_inheritable_attribute(:design_documents) || {}
|
99
|
+
design_documents[design.to_s] = design_document
|
100
|
+
write_inheritable_attribute(:design_documents, design_documents)
|
101
|
+
|
102
|
+
# define query method for each views
|
103
|
+
# find_{design}_{view}(*args) -- key is one of :all, :first, :last
|
104
|
+
# find_{design}_{view}_all(options)
|
105
|
+
# find_{design}_{view}_first(options)
|
106
|
+
# find_{design}_{view}_last(options)
|
107
|
+
views.each do |viewname, functions|
|
108
|
+
define_view_method(design, viewname)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the string of the javascript file stored in the <tt>path</tt>
|
113
|
+
# The <tt>path</tt> is the relative path, root of which is the directory of the caller.
|
114
|
+
# The <tt>root</tt> can be also specified in the second argument.
|
115
|
+
def include_js(path, root = nil)
|
116
|
+
# set root the current directory of the caller.
|
117
|
+
if root.nil?
|
118
|
+
from = caller.first
|
119
|
+
if from =~ /^(.+?):(\d+)/
|
120
|
+
root = File.dirname($1)
|
121
|
+
else
|
122
|
+
root = RAILS_ROOT
|
123
|
+
end
|
124
|
+
end
|
125
|
+
fullpath = File.join(root, path)
|
126
|
+
ERB.new(File.read(fullpath)).result(binding)
|
127
|
+
end
|
128
|
+
|
129
|
+
def define_view_method(design, view)
|
130
|
+
method = <<-EOS
|
131
|
+
def self.find_#{design}_#{view}(*args)
|
132
|
+
options = args.extract_options!
|
133
|
+
define_design_document("#{design.to_s}")
|
134
|
+
case args.first
|
135
|
+
when :first
|
136
|
+
find_#{design}_#{view}_first(options)
|
137
|
+
when :last
|
138
|
+
find_#{design}_#{view}_last(options)
|
139
|
+
else
|
140
|
+
find_#{design}_#{view}_all(options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
EOS
|
144
|
+
class_eval(method, __FILE__, __LINE__)
|
145
|
+
|
146
|
+
[:all, :first, :last].each do |key|
|
147
|
+
method = <<-EOS
|
148
|
+
def self.find_#{design}_#{view}_#{key}(options = {})
|
149
|
+
define_design_document("#{design.to_s}")
|
150
|
+
find_#{key}("#{design}", :#{view}, options)
|
151
|
+
end
|
152
|
+
EOS
|
153
|
+
class_eval(method, __FILE__, __LINE__)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_design_document_from_server(design)
|
158
|
+
path = design_path(design)
|
159
|
+
logger.debug "CouchResource::Connection#get #{path}"
|
160
|
+
connection.get(path) rescue nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Define design document if it does not exist on the server.
|
164
|
+
def define_design_document(design)
|
165
|
+
path = design_path(design)
|
166
|
+
design_document = read_inheritable_attribute(:design_documents)[design]
|
167
|
+
design_revision_checked = read_inheritable_attribute(:design_revision_checked) || false
|
168
|
+
logger.debug "Design Document Check"
|
169
|
+
logger.debug " check_design_revision_every_time = #{check_design_revision_every_time}"
|
170
|
+
logger.debug " design_revision_checked = #{design_revision_checked}"
|
171
|
+
#if self.check_view_every_access
|
172
|
+
if self.check_design_revision_every_time || !design_revision_checked
|
173
|
+
current_doc = get_design_document_from_server(design)
|
174
|
+
# already exists
|
175
|
+
if current_doc
|
176
|
+
logger.debug "Design document is found and updates are being checked."
|
177
|
+
logger.debug current_doc.to_json
|
178
|
+
if match_views?(design_document[:views], current_doc[:views])
|
179
|
+
logger.debug "Design document(#{path}) is the latest."
|
180
|
+
else
|
181
|
+
logger.debug "Design document(#{path}) is not the latest, should be updated."
|
182
|
+
design_document[:_rev] = current_doc[:_rev]
|
183
|
+
logger.debug "CouchResource::Connection#put #{path}"
|
184
|
+
hash = connection.put(path, design_document.to_json)
|
185
|
+
logger.debug hash.to_json
|
186
|
+
design_document[:_rev] = hash[:rev]
|
187
|
+
end
|
188
|
+
else
|
189
|
+
logger.debug "Design document not found so to put."
|
190
|
+
design_document.delete(:_rev)
|
191
|
+
logger.debug "CouchResource::Connection#put #{path}"
|
192
|
+
logger.debug design_document.to_json
|
193
|
+
hash = connection.put(path, design_document.to_json)
|
194
|
+
logger.debug hash.to_json
|
195
|
+
design_document[:_rev] = hash["rev"]
|
196
|
+
end
|
197
|
+
design_revision_checked = true
|
198
|
+
else
|
199
|
+
if design_document[:_rev].nil?
|
200
|
+
begin
|
201
|
+
hash = connection.put(design_path(design), design_document.to_json)
|
202
|
+
design_document[:_rev] = hash[:rev]
|
203
|
+
rescue CouchResource::PreconditionFailed
|
204
|
+
# through the error.
|
205
|
+
design_document[:_rev] = connection.get(design_path(design))[:_rev]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
def match_views?(views1, views2)
|
213
|
+
# {
|
214
|
+
# :view_name => {:map => ..., :reduce => ...},
|
215
|
+
# ...
|
216
|
+
# }
|
217
|
+
views1.each do |key, mapred1|
|
218
|
+
return false unless views2.has_key?(key)
|
219
|
+
mapred2 = views2[key]
|
220
|
+
redhex = [Digest::MD5.hexdigest(mapred1[:reduce].to_s), Digest::MD5.hexdigest(mapred2[:reduce].to_s)]
|
221
|
+
return false unless Digest::MD5.hexdigest(mapred1[:map].to_s) == Digest::MD5.hexdigest(mapred2[:map].to_s)
|
222
|
+
return false unless Digest::MD5.hexdigest(mapred1[:reduce].to_s) == Digest::MD5.hexdigest(mapred2[:reduce].to_s)
|
223
|
+
end
|
224
|
+
true
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,384 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.join(File.dirname(__FILE__), "../lib/couch_resource")
|
3
|
+
|
4
|
+
TEST_DB_PATH = "couch_resource_base_test"
|
5
|
+
|
6
|
+
CouchResource::Base.logger = Logger.new("/dev/null")
|
7
|
+
CouchResource::Base.check_design_revision_every_time = true
|
8
|
+
# CouchResource::Base.logger = Logger.new(STDOUT)
|
9
|
+
|
10
|
+
class SimpleDocument < CouchResource::Base
|
11
|
+
self.database = "http://localhost:5984/#{TEST_DB_PATH}"
|
12
|
+
string :title
|
13
|
+
string :content
|
14
|
+
|
15
|
+
view :simple_document, {
|
16
|
+
:all_by_id => {
|
17
|
+
:map => "function(doc){ emit(doc._id, doc); }"
|
18
|
+
},
|
19
|
+
:all_by_title => {
|
20
|
+
:map => "function(doc){ emit(doc.title, doc); }"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
class StrictDocument < CouchResource::Base
|
26
|
+
self.database = "http://localhost:5984/#{TEST_DB_PATH}"
|
27
|
+
string :title, :validates => [
|
28
|
+
[:length_of, {:maximum => 20}],
|
29
|
+
[:length_of, {:on => :create, :minimum => 5}],
|
30
|
+
[:length_of, {:on => :update, :minimum => 10}]
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
class MagicAttributesTest < CouchResource::Base
|
35
|
+
self.database = "http://localhost:5984/#{TEST_DB_PATH}"
|
36
|
+
datetime :created_at
|
37
|
+
datetime :created_on
|
38
|
+
datetime :updated_at
|
39
|
+
datetime :updated_on
|
40
|
+
end
|
41
|
+
|
42
|
+
class TestBase < Test::Unit::TestCase
|
43
|
+
def setup
|
44
|
+
res = SimpleDocument.connection.put(SimpleDocument.database.path)
|
45
|
+
unless res[:ok]
|
46
|
+
puts "Failed to create test database. Check couchdb server running."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def teardown
|
51
|
+
res = SimpleDocument.connection.delete(SimpleDocument.database.path)
|
52
|
+
unless res[:ok]
|
53
|
+
puts "Failed to drop test database. Delete manually before you test next time."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def test_new?
|
59
|
+
doc = SimpleDocument.new(:title => "title", :content => "content")
|
60
|
+
assert doc.new?
|
61
|
+
doc = SimpleDocument.new(:_id => "simple_document", :title => "title", :content => "content")
|
62
|
+
assert doc.new?
|
63
|
+
doc = SimpleDocument.new(:_id => "simple_document", :_rev => "1234",
|
64
|
+
:title => "title", :content => "content")
|
65
|
+
assert !doc.new?
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_create_without_id
|
69
|
+
doc = SimpleDocument.new(:title => "title", :content => "content")
|
70
|
+
assert doc.save
|
71
|
+
assert_not_nil doc.id
|
72
|
+
assert_not_nil doc.rev
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_create_with_id
|
76
|
+
doc = SimpleDocument.new(:_id => "simple_document", :title => "title", :content => "content")
|
77
|
+
assert doc.save
|
78
|
+
assert_equal "simple_document", doc.id
|
79
|
+
assert_not_nil doc.rev
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_create_with_validation_failed
|
83
|
+
doc = StrictDocument.new(:_id => "simple_document", :title => "123", :content => "content")
|
84
|
+
assert !doc.save
|
85
|
+
assert_raise(CouchResource::ResourceNotFound) { StrictDocument.find("simple_document")}
|
86
|
+
doc.title = "1" * 5
|
87
|
+
assert doc.save
|
88
|
+
doc = StrictDocument.find("simple_document")
|
89
|
+
assert_not_nil doc
|
90
|
+
assert !doc.save
|
91
|
+
doc.title = "1" * 11
|
92
|
+
assert doc.save
|
93
|
+
doc.title = "1" * 25
|
94
|
+
assert !doc.save
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_update
|
98
|
+
doc1 = SimpleDocument.new(:title => "title", :content => "content")
|
99
|
+
assert doc1.save
|
100
|
+
old_rev = doc1.rev
|
101
|
+
assert_not_nil doc1.id
|
102
|
+
assert_not_nil doc1.rev
|
103
|
+
doc1.content = "updated"
|
104
|
+
doc1.save
|
105
|
+
doc2 = SimpleDocument.find(doc1.id)
|
106
|
+
assert_equal "updated", doc2.content
|
107
|
+
assert_not_equal old_rev, doc2.rev
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_destroy
|
111
|
+
doc1 = SimpleDocument.new(:title => "title", :content => "content")
|
112
|
+
doc1.save
|
113
|
+
old_id = doc1.id
|
114
|
+
old_rev = doc1.rev
|
115
|
+
assert_not_nil doc1.id
|
116
|
+
assert_not_nil doc1.rev
|
117
|
+
doc1.destroy
|
118
|
+
assert_not_nil doc1.id
|
119
|
+
assert_nil doc1.rev
|
120
|
+
assert_raise(CouchResource::ResourceNotFound) { SimpleDocument.find(old_id) }
|
121
|
+
doc1.save
|
122
|
+
doc1 = SimpleDocument.find(old_id)
|
123
|
+
assert_equal old_id, doc1.id
|
124
|
+
assert_not_equal old_rev, doc1.rev
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_revs
|
128
|
+
doc1 = SimpleDocument.new(:title => "title", :content => "content")
|
129
|
+
revs = []
|
130
|
+
5.times do
|
131
|
+
assert doc1.save
|
132
|
+
revs << doc1.rev
|
133
|
+
end
|
134
|
+
revs.reverse!
|
135
|
+
retrieved = doc1.revs
|
136
|
+
assert_equal 5, retrieved.length
|
137
|
+
assert_equal revs, retrieved
|
138
|
+
retrieved = doc1.revs(true)
|
139
|
+
retrieved.each_with_index do |revinfo, i|
|
140
|
+
assert_not_nil revinfo
|
141
|
+
assert_equal revs[i], revinfo[:rev]
|
142
|
+
assert_equal "available", revinfo[:status]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_find_from_ids()
|
147
|
+
doc1 = SimpleDocument.new(:title => "title", :content => "content")
|
148
|
+
doc1.save
|
149
|
+
# find an document
|
150
|
+
doc2 = SimpleDocument.find(doc1.id)
|
151
|
+
assert_equal "title", doc2.title
|
152
|
+
assert_equal "content", doc2.content
|
153
|
+
assert_equal doc1.rev, doc2.rev
|
154
|
+
assert_equal doc1.id, doc2.id
|
155
|
+
# find multiple documents
|
156
|
+
docs = SimpleDocument.find(doc1.id, doc2.id)
|
157
|
+
docs.each do |doc|
|
158
|
+
assert_equal "title", doc.title
|
159
|
+
assert_equal "content", doc2.content
|
160
|
+
assert_equal doc1.rev, doc.rev
|
161
|
+
assert_equal doc1.id, doc.id
|
162
|
+
end
|
163
|
+
# find with :rev option
|
164
|
+
doc2 = SimpleDocument.find(doc1.id, :rev => doc1.rev)
|
165
|
+
doc2 = SimpleDocument.find(doc1.id)
|
166
|
+
assert_equal "title", doc2.title
|
167
|
+
assert_equal "content", doc2.content
|
168
|
+
assert_equal doc1.rev, doc2.rev
|
169
|
+
assert_equal doc1.id, doc2.id
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_find_first
|
173
|
+
register_simple_documents()
|
174
|
+
# without any options
|
175
|
+
doc = SimpleDocument.find_simple_document_all_by_title_first
|
176
|
+
assert_not_nil doc
|
177
|
+
assert_equal "title_0", doc.title
|
178
|
+
assert_equal "content_0", doc.content
|
179
|
+
|
180
|
+
# with descending option (same as SimpleDocument.last without descending option)
|
181
|
+
doc = SimpleDocument.find_simple_document_all_by_title_first(:descending => true)
|
182
|
+
assert_not_nil doc
|
183
|
+
assert_equal "title_9", doc.title
|
184
|
+
assert_equal "content_9", doc.content
|
185
|
+
|
186
|
+
# with offset option
|
187
|
+
doc = SimpleDocument.find_simple_document_all_by_title_first(:skip => 1)
|
188
|
+
assert_not_nil doc
|
189
|
+
assert_equal "title_1", doc.title
|
190
|
+
assert_equal "content_1", doc.content
|
191
|
+
|
192
|
+
# with key options
|
193
|
+
doc = SimpleDocument.find_simple_document_all_by_title_first(:key => "title_2")
|
194
|
+
assert_not_nil doc
|
195
|
+
assert_equal "title_2", doc.title
|
196
|
+
assert_equal "content_2", doc.content
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_find_last
|
200
|
+
register_simple_documents()
|
201
|
+
# without any options
|
202
|
+
doc = SimpleDocument.find_simple_document_all_by_title_last
|
203
|
+
assert_not_nil doc
|
204
|
+
assert_equal "title_9", doc.title
|
205
|
+
assert_equal "content_9", doc.content
|
206
|
+
|
207
|
+
# with descending option (same as SimpleDocument.last without descending option)
|
208
|
+
doc = SimpleDocument.find_simple_document_all_by_title_last(:descending => true)
|
209
|
+
assert_not_nil doc
|
210
|
+
assert_equal "title_0", doc.title
|
211
|
+
assert_equal "content_0", doc.content
|
212
|
+
|
213
|
+
# with key option
|
214
|
+
doc = SimpleDocument.find_simple_document_all_by_title_last(:key => "title_2")
|
215
|
+
assert_not_nil doc
|
216
|
+
assert_equal "title_2", doc.title
|
217
|
+
assert_equal "content_2", doc.content
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_find_all
|
221
|
+
register_simple_documents()
|
222
|
+
# without any options
|
223
|
+
docs = SimpleDocument.find_simple_document_all_by_title
|
224
|
+
assert_not_nil docs
|
225
|
+
(0..9).each do |i|
|
226
|
+
doc = docs[:rows][i]
|
227
|
+
assert_not_nil doc
|
228
|
+
assert_equal "title_#{i}", doc.title
|
229
|
+
assert_equal "content_#{i}", doc.content
|
230
|
+
end
|
231
|
+
|
232
|
+
# with descending option
|
233
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:descending => true)
|
234
|
+
(0..9).each do |i|
|
235
|
+
doc = docs[:rows][i]
|
236
|
+
assert_equal "title_#{9-i}", doc.title
|
237
|
+
assert_equal "content_#{9-i}", doc.content
|
238
|
+
end
|
239
|
+
|
240
|
+
# with key option
|
241
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:key => "title_1")
|
242
|
+
assert_equal 1, docs[:rows].length
|
243
|
+
assert_equal "title_1", docs[:rows].first.title
|
244
|
+
assert_equal "content_1", docs[:rows].first.content
|
245
|
+
# with key option (no matching)
|
246
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:key => "1")
|
247
|
+
assert_equal 0, docs[:rows].length
|
248
|
+
|
249
|
+
# with startkey option
|
250
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:startkey => "title_5")
|
251
|
+
assert_equal 5, docs[:rows].length
|
252
|
+
(5..9).each do |i|
|
253
|
+
doc = docs[:rows][i-5]
|
254
|
+
assert_not_nil doc
|
255
|
+
assert_equal "title_#{i}", doc.title
|
256
|
+
assert_equal "content_#{i}", doc.content
|
257
|
+
end
|
258
|
+
|
259
|
+
# with endkey option
|
260
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:endkey => "title_4")
|
261
|
+
assert_equal 5, docs[:rows].length
|
262
|
+
(0..4).each do |i|
|
263
|
+
doc = docs[:rows][i]
|
264
|
+
assert_not_nil doc
|
265
|
+
assert_equal "title_#{i}", doc.title
|
266
|
+
assert_equal "content_#{i}", doc.content
|
267
|
+
end
|
268
|
+
|
269
|
+
# with startkey and endkey option
|
270
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:startkey => "title_3", :endkey => "title_6")
|
271
|
+
assert_equal 4, docs[:rows].length
|
272
|
+
(3..6).each do |i|
|
273
|
+
doc = docs[:rows][i-3]
|
274
|
+
assert_not_nil doc
|
275
|
+
assert_equal "title_#{i}", doc.title
|
276
|
+
assert_equal "content_#{i}", doc.content
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
def test_paginate
|
282
|
+
register_simple_documents()
|
283
|
+
|
284
|
+
# retrieve all documentes
|
285
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:count => 10)
|
286
|
+
assert_nil docs[:previous]
|
287
|
+
assert_nil docs[:next]
|
288
|
+
assert_equal "title_0", docs[:rows].first.title
|
289
|
+
assert_equal "title_9", docs[:rows].last.title
|
290
|
+
|
291
|
+
# retrive documents per 4 docs.
|
292
|
+
# 0..3
|
293
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:count => 4)
|
294
|
+
assert_equal -1, docs[:previous][:expected_offset]
|
295
|
+
assert docs[:next][:expected_offset] > 0
|
296
|
+
assert_equal "title_0", docs[:rows].first.title
|
297
|
+
assert_equal "title_3", docs[:rows].last.title
|
298
|
+
|
299
|
+
# next
|
300
|
+
# 4..7
|
301
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:next])
|
302
|
+
assert docs[:previous][:expected_offset] > 0
|
303
|
+
assert docs[:next][:expected_offset] > 0
|
304
|
+
assert_equal "title_4", docs[:rows].first.title
|
305
|
+
assert_equal "title_7", docs[:rows].last.title
|
306
|
+
|
307
|
+
# next (it remains only 2 docs);
|
308
|
+
# 8..9
|
309
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:next])
|
310
|
+
assert docs[:previous][:expected_offset] > 0
|
311
|
+
assert_equal -1, docs[:next][:expected_offset]
|
312
|
+
assert_equal "title_8", docs[:rows].first.title
|
313
|
+
assert_equal "title_9", docs[:rows].last.title
|
314
|
+
|
315
|
+
# previous (should return 4 docs before title_8)
|
316
|
+
# 4..7
|
317
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:previous])
|
318
|
+
assert docs[:previous][:expected_offset] > 0
|
319
|
+
assert docs[:next][:expected_offset] > 0
|
320
|
+
assert_equal "title_4", docs[:rows].first.title
|
321
|
+
assert_equal "title_7", docs[:rows].last.title
|
322
|
+
|
323
|
+
# previous (should return 4 docs before title_4)
|
324
|
+
# 0..3
|
325
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:previous])
|
326
|
+
assert docs[:previous][:expected_offset] > 0
|
327
|
+
assert docs[:next][:expected_offset] > 0
|
328
|
+
assert_equal "title_0", docs[:rows].first.title
|
329
|
+
assert_equal "title_3", docs[:rows].last.title
|
330
|
+
|
331
|
+
# previous over reached
|
332
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:previous])
|
333
|
+
assert_equal -1, docs[:previous][:expected_offset]
|
334
|
+
assert_nil docs[:next][:expected_offset]
|
335
|
+
assert_equal 0, docs[:rows].length
|
336
|
+
|
337
|
+
# more complex case
|
338
|
+
# fetch more than half documents at first access and with descending option
|
339
|
+
# retrieve 9 documentes (from 9 to 1)
|
340
|
+
docs = SimpleDocument.find_simple_document_all_by_title(:descending => true, :count => 9)
|
341
|
+
assert_equal -1, docs[:previous][:expected_offset]
|
342
|
+
assert docs[:next][:expected_offset] > 0
|
343
|
+
assert_equal "title_9", docs[:rows].first.title
|
344
|
+
assert_equal "title_1", docs[:rows].last.title
|
345
|
+
|
346
|
+
# next
|
347
|
+
# only 0
|
348
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:next])
|
349
|
+
assert docs[:previous][:expected_offset] > 0
|
350
|
+
assert_equal -1, docs[:next][:expected_offset]
|
351
|
+
assert_equal "title_0", docs[:rows].first.title
|
352
|
+
|
353
|
+
# previous
|
354
|
+
# 9..1
|
355
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:previous])
|
356
|
+
assert docs[:previous][:expected_offset] > 0
|
357
|
+
assert docs[:next][:expected_offset] > 0
|
358
|
+
assert_equal "title_9", docs[:rows].first.title
|
359
|
+
assert_equal "title_1", docs[:rows].last.title
|
360
|
+
|
361
|
+
# previous over reached
|
362
|
+
docs = SimpleDocument.find_simple_document_all_by_title(docs[:previous])
|
363
|
+
assert_equal -1, docs[:previous][:expected_offset]
|
364
|
+
assert_nil docs[:next][:expected_offset]
|
365
|
+
assert_equal 0, docs[:rows].length
|
366
|
+
end
|
367
|
+
|
368
|
+
private
|
369
|
+
def register_simple_documents
|
370
|
+
0.upto(9) do |i|
|
371
|
+
doc = SimpleDocument.new(:title => "title_#{i}", :content => "content_#{i}")
|
372
|
+
doc.save
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def test_magic_attributes
|
377
|
+
obj = MagicAttributesTest
|
378
|
+
obj.save
|
379
|
+
assert_not_nil obj.created_at
|
380
|
+
assert_not_nil obj.created_on
|
381
|
+
assert_not_nil obj.updated_at
|
382
|
+
assert_not_nil obj.updated_on
|
383
|
+
end
|
384
|
+
end
|