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.
@@ -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