dolly 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dolly/document.rb +31 -7
- data/lib/dolly/property.rb +3 -0
- data/lib/dolly/request.rb +11 -2
- data/lib/dolly/timestamps.rb +5 -2
- data/lib/dolly/version.rb +1 -1
- data/lib/exceptions/dolly.rb +6 -1
- data/test/collection_test.rb +3 -1
- data/test/document_test.rb +121 -2
- data/test/dummy/log/test.log +10881 -0
- data/test/support/test.txt +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1952fc2428d46ea5090b64c1785e63c2665196e1
|
4
|
+
data.tar.gz: 365cf7c705ebd578fe5116721a94653a28cca9fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c043052831b7bc395ca53d49771026d1450e409a1bd92dcc2efc45377d4ae63d1cf81ec95ebed62717998308cadfa8f4a12085c43f1bd2e77fc66631d500e7da
|
7
|
+
data.tar.gz: 0a90e6732e7125a4f835d320c6f6902bff4327f37afd63794789fc4fb8ec0687e30ef638bc1b98dfac1a9ac38fe3388908c90070158cd918b52d275ab8f07d63
|
data/lib/dolly/document.rb
CHANGED
@@ -10,6 +10,7 @@ module Dolly
|
|
10
10
|
|
11
11
|
attr_accessor :rows, :doc, :key
|
12
12
|
class_attribute :properties
|
13
|
+
cattr_accessor :timestamps
|
13
14
|
|
14
15
|
def initialize options = {}
|
15
16
|
@doc ||= {}
|
@@ -50,11 +51,12 @@ module Dolly
|
|
50
51
|
doc['_rev'] = value
|
51
52
|
end
|
52
53
|
|
53
|
-
def save
|
54
|
+
def save options = {}
|
55
|
+
return false unless options[:validate] == false || valid?
|
54
56
|
self.doc['_id'] = self.id if self.id.present?
|
55
57
|
self.doc['_id'] = self.class.next_id if self.doc['_id'].blank?
|
56
|
-
set_created_at if
|
57
|
-
set_updated_at if
|
58
|
+
set_created_at if timestamps
|
59
|
+
set_updated_at if timestamps
|
58
60
|
response = database.put(id_as_resource, self.doc.to_json)
|
59
61
|
obj = JSON::parse response.parsed_response
|
60
62
|
doc['_rev'] = obj['rev'] if obj['rev']
|
@@ -62,7 +64,7 @@ module Dolly
|
|
62
64
|
end
|
63
65
|
|
64
66
|
def save!
|
65
|
-
|
67
|
+
raise DocumentInvalidError unless valid?
|
66
68
|
save
|
67
69
|
end
|
68
70
|
|
@@ -96,6 +98,18 @@ module Dolly
|
|
96
98
|
CGI::escape id
|
97
99
|
end
|
98
100
|
|
101
|
+
def attach_file! file_name, mime_type, body, opts={}
|
102
|
+
if opts[:inline]
|
103
|
+
attachment_data = { file_name.to_s => { 'content_type' => mime_type,
|
104
|
+
'data' => Base64.encode64(body)} }
|
105
|
+
doc['_attachments'] ||= {}
|
106
|
+
doc['_attachments'].merge! attachment_data
|
107
|
+
save
|
108
|
+
else
|
109
|
+
database.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
99
113
|
def self.create options = {}
|
100
114
|
obj = new options
|
101
115
|
obj.save
|
@@ -124,11 +138,15 @@ module Dolly
|
|
124
138
|
end
|
125
139
|
|
126
140
|
def write_property name, value
|
127
|
-
|
141
|
+
instance_variable_set(:"@#{name}", value)
|
142
|
+
@doc[name.to_s] = value
|
128
143
|
end
|
129
144
|
|
130
145
|
def read_property name
|
131
|
-
|
146
|
+
if instance_variable_get(:"@#{name}").nil?
|
147
|
+
write_property name, (doc[name.to_s] || self.properties[name].value)
|
148
|
+
end
|
149
|
+
instance_variable_get(:"@#{name}")
|
132
150
|
end
|
133
151
|
|
134
152
|
def _properties
|
@@ -146,7 +164,11 @@ module Dolly
|
|
146
164
|
end
|
147
165
|
|
148
166
|
def initialize_default_properties options
|
149
|
-
_properties.reject { |property| options.keys.include? property.name }.each
|
167
|
+
_properties.reject { |property| options.keys.include? property.name }.each do |property|
|
168
|
+
property_value = property.default.clone unless Dolly::Property::CANT_CLONE.any? { |klass| property.default.is_a? klass }
|
169
|
+
property_value ||= property.default
|
170
|
+
self.doc[property.name] ||= property_value
|
171
|
+
end
|
150
172
|
end
|
151
173
|
|
152
174
|
def init_doc options
|
@@ -163,5 +185,7 @@ module Dolly
|
|
163
185
|
def properties_include? property
|
164
186
|
_properties.map(&:name).include? property
|
165
187
|
end
|
188
|
+
|
189
|
+
def valid?; true; end
|
166
190
|
end
|
167
191
|
end
|
data/lib/dolly/property.rb
CHANGED
@@ -4,10 +4,13 @@ module Dolly
|
|
4
4
|
attr_accessor :name
|
5
5
|
attr_reader :class_name, :default
|
6
6
|
|
7
|
+
CANT_CLONE = [NilClass, TrueClass, FalseClass, Fixnum].freeze
|
8
|
+
|
7
9
|
def initialize opts = {}
|
8
10
|
@class_name = opts.delete(:class_name) if opts.present?
|
9
11
|
@name = opts.delete(:name).to_s
|
10
12
|
@default = opts.delete(:default)
|
13
|
+
@default = @default.clone if @default && CANT_CLONE.none? { |klass| @default.is_a? klass }
|
11
14
|
@value = @default if @default
|
12
15
|
warn 'There are some unprocessed options!' if opts.present?
|
13
16
|
end
|
data/lib/dolly/request.rb
CHANGED
@@ -40,6 +40,11 @@ module Dolly
|
|
40
40
|
request :delete, full_path(resource), {}
|
41
41
|
end
|
42
42
|
|
43
|
+
def attach resource, attachment_name, data, headers = {}
|
44
|
+
data = StringIO.new(data) if data.is_a?(String)
|
45
|
+
request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
|
46
|
+
end
|
47
|
+
|
43
48
|
def protocol
|
44
49
|
@protocol || 'http'
|
45
50
|
end
|
@@ -61,8 +66,8 @@ module Dolly
|
|
61
66
|
response = self.class.send method, resource, data.merge(headers: headers)
|
62
67
|
if response.code == 404
|
63
68
|
raise Dolly::ResourceNotFound
|
64
|
-
elsif (
|
65
|
-
raise Dolly::ServerError
|
69
|
+
elsif (400..600).include? response.code
|
70
|
+
raise Dolly::ServerError.new( response )
|
66
71
|
else
|
67
72
|
response
|
68
73
|
end
|
@@ -88,6 +93,10 @@ module Dolly
|
|
88
93
|
def full_path resource
|
89
94
|
"/#{database_name}/#{resource}"
|
90
95
|
end
|
96
|
+
|
97
|
+
def attachment_path resource, attachment_name
|
98
|
+
"#{full_path(resource)}/#{attachment_name}"
|
99
|
+
end
|
91
100
|
end
|
92
101
|
|
93
102
|
end
|
data/lib/dolly/timestamps.rb
CHANGED
@@ -4,13 +4,16 @@ module Dolly
|
|
4
4
|
def timestamps!
|
5
5
|
property :created_at, :updated_at, class_name: DateTime
|
6
6
|
|
7
|
+
self.class_variable_set :@@timestamps, true
|
8
|
+
|
7
9
|
Dolly::Document.class_eval do
|
10
|
+
|
8
11
|
def set_created_at
|
9
|
-
|
12
|
+
doc['created_at'] ||= DateTime.now
|
10
13
|
end
|
11
14
|
|
12
15
|
def set_updated_at
|
13
|
-
|
16
|
+
doc['updated_at'] = DateTime.now
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
data/lib/dolly/version.rb
CHANGED
data/lib/exceptions/dolly.rb
CHANGED
@@ -5,8 +5,12 @@ module Dolly
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
class ServerError < RuntimeError
|
8
|
+
def initialize msg
|
9
|
+
@msg = msg
|
10
|
+
end
|
11
|
+
|
8
12
|
def to_s
|
9
|
-
|
13
|
+
"There has been an error on the couchdb server: #{@msg.inspect}"
|
10
14
|
end
|
11
15
|
end
|
12
16
|
class MissingDesignError < RuntimeError
|
@@ -29,4 +33,5 @@ module Dolly
|
|
29
33
|
"Trying to set an undefined property."
|
30
34
|
end
|
31
35
|
end
|
36
|
+
class DocumentInvalidError < RuntimeError; end
|
32
37
|
end
|
data/test/collection_test.rb
CHANGED
data/test/document_test.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class BaseDolly < Dolly::Document; end
|
4
|
+
|
5
|
+
class BarFoo < BaseDolly
|
6
|
+
property :a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :persist
|
7
|
+
end
|
8
|
+
|
9
|
+
class FooBar < BaseDolly
|
4
10
|
property :foo, :bar
|
5
11
|
property :with_default, default: 1
|
6
12
|
property :boolean, class_name: TrueClass, default: true
|
7
13
|
property :date, class_name: Date
|
8
14
|
property :time, class_name: Time
|
9
15
|
property :datetime, class_name: DateTime
|
16
|
+
property :is_nil, class_name: NilClass, default: nil
|
10
17
|
|
11
18
|
timestamps!
|
12
19
|
end
|
@@ -22,10 +29,26 @@ class FooBaz < Dolly::Document
|
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
32
|
+
class WithTime < Dolly::Document
|
33
|
+
property :created_at
|
34
|
+
end
|
35
|
+
|
25
36
|
class TestFoo < Dolly::Document
|
26
37
|
property :default_test_property, class_name: String, default: 'FOO'
|
27
38
|
end
|
28
39
|
|
40
|
+
class DocumentWithValidMethod < Dolly::Document
|
41
|
+
property :foo
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
foo.present?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class DocWithSameDefaults < Dolly::Document
|
49
|
+
property :foo, :bar, class_name: Array, default: []
|
50
|
+
end
|
51
|
+
|
29
52
|
class DocumentTest < ActiveSupport::TestCase
|
30
53
|
DB_BASE_PATH = "http://localhost:5984/test".freeze
|
31
54
|
|
@@ -55,6 +78,7 @@ class DocumentTest < ActiveSupport::TestCase
|
|
55
78
|
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2Ferror%22%5D&include_docs=true", body: 'error', status: ["500", "Error"]
|
56
79
|
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F1%22%2C%22foo_bar%2F2%22%5D&include_docs=true", body: @multi_resp.to_json
|
57
80
|
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F2%22%5D&include_docs=true", body: not_found_resp.to_json
|
81
|
+
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2Fbig_doc%22%5D&include_docs=true", body: build_view_response([data.merge(other_property: 'other')]).to_json
|
58
82
|
end
|
59
83
|
|
60
84
|
test 'new in memory document' do
|
@@ -284,7 +308,7 @@ class DocumentTest < ActiveSupport::TestCase
|
|
284
308
|
test 'trying to update invalid property' do
|
285
309
|
foo = FooBar.new 'id' => 'a', foo: 'ab', bar: 'ba'
|
286
310
|
assert_raise Dolly::InvalidProperty do
|
287
|
-
|
311
|
+
foo.update_properties key_to_success: false
|
288
312
|
end
|
289
313
|
end
|
290
314
|
|
@@ -348,6 +372,101 @@ class DocumentTest < ActiveSupport::TestCase
|
|
348
372
|
assert_equal 'bar', test_foo.doc['default_test_property']
|
349
373
|
end
|
350
374
|
|
375
|
+
test 'doc and method and instance var are the same' do
|
376
|
+
test_foo = FooBar.new
|
377
|
+
test_foo.foo = 'test_value'
|
378
|
+
assert_equal 'test_value', test_foo.foo
|
379
|
+
assert_equal 'test_value', test_foo.doc['foo']
|
380
|
+
assert_equal 'test_value', test_foo.instance_variable_get(:@foo)
|
381
|
+
end
|
382
|
+
|
383
|
+
test 'created at is current time' do
|
384
|
+
resp = {ok: true, id: "with_time/timed", rev: "FF0000"}
|
385
|
+
FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/with_time%2F.+/, body: resp.to_json
|
386
|
+
test = WithTime.new id: "timed"
|
387
|
+
assert test.respond_to?(:created_at)
|
388
|
+
assert test.save
|
389
|
+
assert test.created_at
|
390
|
+
end
|
391
|
+
|
392
|
+
test 'nil default' do
|
393
|
+
properties = {foo: nil, is_nil: nil}
|
394
|
+
resp = {ok: true, id: "foo_bar/1", rev: "FF0000"}
|
395
|
+
FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/foo_bar%2F.+/, body: resp.to_json
|
396
|
+
foo = FooBar.new properties
|
397
|
+
foo.save
|
398
|
+
properties.each do |k, v|
|
399
|
+
assert_equal v, foo[k]
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
test 'setting on instance value does set it for other instances' do
|
404
|
+
foo = FooBar.new
|
405
|
+
foo.bar = 'I belong to the foo, not the bar'
|
406
|
+
bar = FooBar.new
|
407
|
+
assert_not_equal foo.bar, bar.bar
|
408
|
+
end
|
409
|
+
|
410
|
+
test 'subclass raises DocumentInvalidError if valid? fails' do
|
411
|
+
foo = DocumentWithValidMethod.new
|
412
|
+
assert_raise Dolly::DocumentInvalidError do
|
413
|
+
foo.save!
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
test 'save returns false for invalid document on save' do
|
418
|
+
foo = DocumentWithValidMethod.new
|
419
|
+
assert_not foo.save
|
420
|
+
end
|
421
|
+
|
422
|
+
test 'save succeeds for invalid document if skipping validations' do
|
423
|
+
resp = {ok: true, id: "document_with_valid_method/1", rev: "FF0000"}
|
424
|
+
FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/document_with_valid_method%2F.+/, body: resp.to_json
|
425
|
+
foo = DocumentWithValidMethod.new
|
426
|
+
assert foo.save(validate: false)
|
427
|
+
end
|
428
|
+
|
429
|
+
test 'default objects are not the same in memory' do
|
430
|
+
doc_with_same_default = DocWithSameDefaults.new
|
431
|
+
assert_not doc_with_same_default.foo.equal? doc_with_same_default.bar
|
432
|
+
doc_with_same_default.foo.push 'foo'
|
433
|
+
assert doc_with_same_default.bar == []
|
434
|
+
end
|
435
|
+
|
436
|
+
test 'default properties do not update the class default properties' do
|
437
|
+
doc = DocWithSameDefaults.new
|
438
|
+
assert_equal [], DocWithSameDefaults.properties[:foo].default
|
439
|
+
assert doc.foo.push 'foo'
|
440
|
+
assert_equal ['foo'], doc.foo
|
441
|
+
assert_equal [], DocWithSameDefaults.properties[:foo].default
|
442
|
+
assert second_doc = DocWithSameDefaults.new
|
443
|
+
assert_equal [], second_doc.foo
|
444
|
+
end
|
445
|
+
|
446
|
+
test 'attach_file! will add a standalone attachment to the document' do
|
447
|
+
assert save_response = {ok: true, id: "base_dolly/79178957-96ff-40d9-9ecb-217fa35bdea7", rev: "1"}
|
448
|
+
assert FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/base_dolly%2F.+/, body: save_response.to_json
|
449
|
+
assert doc = BaseDolly.new
|
450
|
+
assert doc.save
|
451
|
+
assert resp = {ok: true, id: '79178957-96ff-40d9-9ecb-217fa35bdea7', rev: '2'}
|
452
|
+
assert FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/base_dolly\/79178957-96ff-40d9-9ecb-217fa35bdea7\/test.txt/, body: resp.to_json
|
453
|
+
assert data = File.open("#{FileUtils.pwd}/test/support/test.txt").read
|
454
|
+
assert doc.attach_file! 'test.txt', 'text/plain', data
|
455
|
+
end
|
456
|
+
|
457
|
+
test 'attach_file! will add an inline attachment if specified' do
|
458
|
+
assert save_response = {ok: true, id: "base_dolly/79178957-96ff-40d9-9ecb-217fa35bdea7", rev: "1"}
|
459
|
+
assert FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/base_dolly%2F.+/, body: save_response.to_json
|
460
|
+
assert doc = BaseDolly.new
|
461
|
+
assert doc.save
|
462
|
+
assert resp = {ok: true, id: '79178957-96ff-40d9-9ecb-217fa35bdea7', rev: '2'}
|
463
|
+
assert FakeWeb.register_uri :put, /http:\/\/localhost:5984\/test\/base_dolly\/79178957-96ff-40d9-9ecb-217fa35bdea7\/test.txt/, body: resp.to_json
|
464
|
+
assert data = File.open("#{FileUtils.pwd}/test/support/test.txt").read
|
465
|
+
assert doc.attach_file! 'test.txt', 'text/plain', data, inline: true
|
466
|
+
assert doc.doc['_attachments']['test.txt'].present?
|
467
|
+
assert_equal Base64.encode64(data), doc.doc['_attachments']['test.txt']['data']
|
468
|
+
end
|
469
|
+
|
351
470
|
private
|
352
471
|
def generic_response rows, count = 1
|
353
472
|
{total_rows: count, offset:0, rows: rows}
|