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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c0ed634c57601285c5f226c689864684e556edbe
4
- data.tar.gz: 343cf2e87366a95ca10a22cd76a7e9b014fe8eeb
3
+ metadata.gz: 1952fc2428d46ea5090b64c1785e63c2665196e1
4
+ data.tar.gz: 365cf7c705ebd578fe5116721a94653a28cca9fc
5
5
  SHA512:
6
- metadata.gz: 46eb110e75de2a3511cd5682bc5f8a4caddf11c4c8b41c2af20dd09b29e5413c9380385981c4b957655ac163f24b7a17beb6b43dac012695eaf6d1b2a2794275
7
- data.tar.gz: 4cab1eb32ab93835832897fda1ed3a300063df79e1e6f6895024355939843608ba1c3a3168635d63d4a339cc39f4f06eb05322a673e91f03babee3146f180bbb
6
+ metadata.gz: c043052831b7bc395ca53d49771026d1450e409a1bd92dcc2efc45377d4ae63d1cf81ec95ebed62717998308cadfa8f4a12085c43f1bd2e77fc66631d500e7da
7
+ data.tar.gz: 0a90e6732e7125a4f835d320c6f6902bff4327f37afd63794789fc4fb8ec0687e30ef638bc1b98dfac1a9ac38fe3388908c90070158cd918b52d275ab8f07d63
@@ -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 respond_to? :created_at
57
- set_updated_at if respond_to? :updated_at
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
- #TODO: decide how to handle validations...
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
- @doc[name.to_s] = self.properties[name].value = value
141
+ instance_variable_set(:"@#{name}", value)
142
+ @doc[name.to_s] = value
128
143
  end
129
144
 
130
145
  def read_property name
131
- doc[name.to_s] || self.properties[name].value
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 { |property| self.doc[property.name] ||= property.default }
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
@@ -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 (500..600).include? response.code
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
@@ -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
- self.created_at ||= DateTime.now
12
+ doc['created_at'] ||= DateTime.now
10
13
  end
11
14
 
12
15
  def set_updated_at
13
- self.updated_at = DateTime.now
16
+ doc['updated_at'] = DateTime.now
14
17
  end
15
18
  end
16
19
  end
data/lib/dolly/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dolly
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
- 'There has been an error on the couchdb server. Please review your couch logs.'
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
@@ -1,6 +1,8 @@
1
1
  require 'test_helper'
2
2
 
3
- class FooBar < Dolly::Document
3
+ class BaseDolly < Dolly::Document; end
4
+
5
+ class FooBar < BaseDolly
4
6
  property :foo, :bar
5
7
  end
6
8
 
@@ -1,12 +1,19 @@
1
1
  require 'test_helper'
2
2
 
3
- class FooBar < Dolly::Document
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
- foo.update_properties key_to_success: false
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}