dolly 0.9.0 → 1.0.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.
- 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}
|