couchbase-orm 3.1.0 → 3.3.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/.github/workflows/release.yml +28 -0
- data/.github/workflows/test.yml +4 -12
- data/README.md +27 -1
- data/RELEASE.md +6 -0
- data/couchbase-orm.gemspec +1 -1
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/03-defining-models.md +39 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/07-sqlpp-queries.md +41 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/09-nested-documents.md +19 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/11-encryption.md +11 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/12-logging.md +14 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/13-troubleshooting.md +2 -0
- data/lib/couchbase-orm/active_record_compat.rb +0 -26
- data/lib/couchbase-orm/base.rb +3 -0
- data/lib/couchbase-orm/n1ql.rb +9 -4
- data/lib/couchbase-orm/persistence.rb +4 -0
- data/lib/couchbase-orm/proxies/bucket_proxy.rb +2 -8
- data/lib/couchbase-orm/relation.rb +9 -4
- data/lib/couchbase-orm/types.rb +0 -7
- data/lib/couchbase-orm/unknown_attributes.rb +82 -0
- data/lib/couchbase-orm/version.rb +1 -1
- data/spec/base_spec.rb +137 -0
- data/spec/couchbase-orm/active_record_compat_spec.rb +73 -0
- data/spec/couchbase-orm/changeable_spec.rb +37 -0
- data/spec/n1ql_spec.rb +19 -0
- data/spec/proxies/bucket_proxy_spec.rb +53 -0
- data/spec/relation_spec.rb +33 -0
- data/spec/type_encrypted_spec.rb +20 -0
- data/spec/type_nested_spec.rb +92 -0
- data/spec/unknown_attributes_spec.rb +227 -0
- data/spec/utilities/ignored_properties_spec.rb +17 -0
- metadata +9 -3
data/spec/base_spec.rb
CHANGED
|
@@ -22,6 +22,33 @@ class BaseTestWithIgnoredProperties < CouchbaseOrm::Base
|
|
|
22
22
|
attribute :job, :string
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
class BaseTestWithUnknownAttributesAllowed < CouchbaseOrm::Base
|
|
26
|
+
self.raise_on_unknown_attributes = false
|
|
27
|
+
attribute :name, :string
|
|
28
|
+
attribute :job, :string
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class NestedDocWithUnknownAttributesAllowed < CouchbaseOrm::NestedDocument
|
|
32
|
+
self.raise_on_unknown_attributes = false
|
|
33
|
+
attribute :title, :string
|
|
34
|
+
attribute :value, :integer
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class NestedDocWithDefaultBehavior < CouchbaseOrm::NestedDocument
|
|
38
|
+
attribute :title, :string
|
|
39
|
+
attribute :value, :integer
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class ParentDocWithNestedUnknownAllowed < CouchbaseOrm::Base
|
|
43
|
+
attribute :name, :string
|
|
44
|
+
attribute :nested_item, :nested, type: NestedDocWithUnknownAttributesAllowed
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class ParentDocWithNestedDefault < CouchbaseOrm::Base
|
|
48
|
+
attribute :name, :string
|
|
49
|
+
attribute :nested_item, :nested, type: NestedDocWithDefaultBehavior
|
|
50
|
+
end
|
|
51
|
+
|
|
25
52
|
class BaseTestWithPropertiesAlwaysExistsInDocument < CouchbaseOrm::Base
|
|
26
53
|
self.properties_always_exists_in_document = true
|
|
27
54
|
attribute :name, :string
|
|
@@ -349,6 +376,116 @@ describe CouchbaseOrm::Base do
|
|
|
349
376
|
end
|
|
350
377
|
end
|
|
351
378
|
|
|
379
|
+
describe 'handling unknown attributes' do
|
|
380
|
+
context 'when raise_on_unknown_attributes is set to false' do
|
|
381
|
+
it 'returns false when queried' do
|
|
382
|
+
expect(BaseTestWithUnknownAttributesAllowed.raise_on_unknown_attributes).to be(false)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
it 'silently ignores unknown attributes in new' do
|
|
386
|
+
model = BaseTestWithUnknownAttributesAllowed.new(name: 'test', job: 'dev', unknown_attr: 'value')
|
|
387
|
+
expect(model.name).to eq('test')
|
|
388
|
+
expect(model.job).to eq('dev')
|
|
389
|
+
expect(model.respond_to?(:unknown_attr)).to be(false)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it 'silently ignores unknown attributes in assign_attributes' do
|
|
393
|
+
model = BaseTestWithUnknownAttributesAllowed.new(name: 'test')
|
|
394
|
+
expect {
|
|
395
|
+
model.assign_attributes(name: 'updated', job: 'engineer', foo: 'bar', baz: 'qux')
|
|
396
|
+
}.not_to raise_error
|
|
397
|
+
expect(model.name).to eq('updated')
|
|
398
|
+
expect(model.job).to eq('engineer')
|
|
399
|
+
expect(model.respond_to?(:foo)).to be(false)
|
|
400
|
+
expect(model.respond_to?(:baz)).to be(false)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
it 'only stores known attributes' do
|
|
404
|
+
model = BaseTestWithUnknownAttributesAllowed.new(
|
|
405
|
+
name: 'Alice',
|
|
406
|
+
job: 'Developer',
|
|
407
|
+
unknown_field_1: 'value1',
|
|
408
|
+
unknown_field_2: 'value2'
|
|
409
|
+
)
|
|
410
|
+
# Only known attributes should be stored
|
|
411
|
+
expect(model.name).to eq('Alice')
|
|
412
|
+
expect(model.job).to eq('Developer')
|
|
413
|
+
expect(model.respond_to?(:unknown_field_1)).to be(false)
|
|
414
|
+
expect(model.respond_to?(:unknown_field_2)).to be(false)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
context 'default behavior (raise_on_unknown_attributes = true)' do
|
|
419
|
+
it 'returns true by default' do
|
|
420
|
+
expect(BaseTest.raise_on_unknown_attributes).to be(true)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'raises ActiveModel::UnknownAttributeError on unknown attributes in new' do
|
|
424
|
+
expect {
|
|
425
|
+
BaseTest.new(name: 'bob', job: 'dev', foo: 'bar')
|
|
426
|
+
}.to raise_error(ActiveModel::UnknownAttributeError)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it 'raises ActiveModel::UnknownAttributeError on unknown attributes in assign_attributes' do
|
|
430
|
+
model = BaseTest.new(name: 'bob')
|
|
431
|
+
expect {
|
|
432
|
+
model.assign_attributes(job: 'dev', foo: 'bar')
|
|
433
|
+
}.to raise_error(ActiveModel::UnknownAttributeError)
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
context 'for NestedDocument classes' do
|
|
438
|
+
it 'returns false when queried on NestedDocument with raise_on_unknown_attributes = false' do
|
|
439
|
+
expect(NestedDocWithUnknownAttributesAllowed.raise_on_unknown_attributes).to be(false)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
it 'returns true by default on NestedDocument' do
|
|
443
|
+
expect(NestedDocWithDefaultBehavior.raise_on_unknown_attributes).to be(true)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
it 'silently ignores unknown attributes in NestedDocument when disabled' do
|
|
447
|
+
nested = NestedDocWithUnknownAttributesAllowed.new(title: 'Test', value: 42, unknown: 'ignored')
|
|
448
|
+
expect(nested.title).to eq('Test')
|
|
449
|
+
expect(nested.value).to eq(42)
|
|
450
|
+
expect(nested.respond_to?(:unknown)).to be(false)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it 'raises error for unknown attributes in NestedDocument when enabled' do
|
|
454
|
+
expect {
|
|
455
|
+
NestedDocWithDefaultBehavior.new(title: 'Test', value: 42, unknown: 'error')
|
|
456
|
+
}.to raise_error(ActiveModel::UnknownAttributeError)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
it 'silently ignores unknown attributes in nested documents within parent' do
|
|
460
|
+
parent = ParentDocWithNestedUnknownAllowed.new(name: 'Parent')
|
|
461
|
+
nested = NestedDocWithUnknownAttributesAllowed.new(title: 'Nested', value: 100, extra: 'ignored')
|
|
462
|
+
parent.nested_item = nested
|
|
463
|
+
|
|
464
|
+
expect(parent.nested_item.title).to eq('Nested')
|
|
465
|
+
expect(parent.nested_item.value).to eq(100)
|
|
466
|
+
expect(parent.nested_item.respond_to?(:extra)).to be(false)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it 'raises error for unknown attributes in nested documents with default behavior' do
|
|
470
|
+
parent = ParentDocWithNestedDefault.new(name: 'Parent')
|
|
471
|
+
expect {
|
|
472
|
+
NestedDocWithDefaultBehavior.new(title: 'Nested', value: 100, extra: 'error')
|
|
473
|
+
}.to raise_error(ActiveModel::UnknownAttributeError)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
it 'works with assign_attributes on NestedDocument' do
|
|
477
|
+
nested = NestedDocWithUnknownAttributesAllowed.new(title: 'Initial')
|
|
478
|
+
expect {
|
|
479
|
+
nested.assign_attributes(title: 'Updated', value: 50, unknown_field: 'ignored')
|
|
480
|
+
}.not_to raise_error
|
|
481
|
+
|
|
482
|
+
expect(nested.title).to eq('Updated')
|
|
483
|
+
expect(nested.value).to eq(50)
|
|
484
|
+
expect(nested.respond_to?(:unknown_field)).to be(false)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
352
489
|
describe '.properties_always_exists_in_document' do
|
|
353
490
|
it 'Uses NOT VALUED when properties_always_exists_in_document = false' do
|
|
354
491
|
where_clause = BaseTest.where(name: nil)
|
|
@@ -3,6 +3,18 @@ require './lib/couchbase-orm/active_record_compat'
|
|
|
3
3
|
class Foo
|
|
4
4
|
include CouchbaseOrm::ActiveRecordCompat
|
|
5
5
|
|
|
6
|
+
def self.attribute_names
|
|
7
|
+
%w[id compute_age]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.attribute_types
|
|
11
|
+
{ 'id' => :string }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def attribute_names
|
|
15
|
+
self.class.attribute_names
|
|
16
|
+
end
|
|
17
|
+
|
|
6
18
|
def compute_age
|
|
7
19
|
10 + 32
|
|
8
20
|
end
|
|
@@ -21,4 +33,65 @@ describe CouchbaseOrm::ActiveRecordCompat do
|
|
|
21
33
|
expect(foo.values_at([:compute_age])).to eq([42])
|
|
22
34
|
end
|
|
23
35
|
end
|
|
36
|
+
|
|
37
|
+
describe '.primary_key' do
|
|
38
|
+
it 'is always "id"' do
|
|
39
|
+
expect(Foo.primary_key).to eq('id')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '.base_class?' do
|
|
44
|
+
it 'is always true' do
|
|
45
|
+
expect(Foo.base_class?).to be true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '.column_names' do
|
|
50
|
+
it 'delegates to .attribute_names' do
|
|
51
|
+
expect(Foo.column_names).to eq(%w[id compute_age])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe '.abstract_class?' do
|
|
56
|
+
it 'is always false' do
|
|
57
|
+
expect(Foo.abstract_class?).to be false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '.connected?' do
|
|
62
|
+
it 'is always true' do
|
|
63
|
+
expect(Foo.connected?).to be true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '.table_exists?' do
|
|
68
|
+
it 'is always true' do
|
|
69
|
+
expect(Foo.table_exists?).to be true
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '._reflect_on_association' do
|
|
74
|
+
it 'is always false' do
|
|
75
|
+
expect(Foo._reflect_on_association(:anything)).to be false
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '.type_for_attribute' do
|
|
80
|
+
it 'delegates to .attribute_types' do
|
|
81
|
+
expect(Foo.type_for_attribute('id')).to eq(:string)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe '#_has_attribute?' do
|
|
86
|
+
it 'checks membership in #attribute_names' do
|
|
87
|
+
expect(foo._has_attribute?(:compute_age)).to be true
|
|
88
|
+
expect(foo._has_attribute?(:unknown)).to be false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#attribute_for_inspect' do
|
|
93
|
+
it 'inspects the value returned by the named method' do
|
|
94
|
+
expect(foo.attribute_for_inspect(:compute_age)).to eq('42')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
24
97
|
end
|
|
@@ -13,4 +13,41 @@ describe 'CouchbaseOrm::Changeable' do
|
|
|
13
13
|
doc = Doc.find(doc.id)
|
|
14
14
|
expect(doc.changes).to be_empty
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
describe 'per-attribute dirty methods generated by create_dirty_methods' do
|
|
18
|
+
# `changes_applied` runs the same in-memory bookkeeping create/update
|
|
19
|
+
# triggers after a save (no DB round trip needed to exercise it).
|
|
20
|
+
let(:doc) do
|
|
21
|
+
Doc.new(title: 'initial').tap do |d|
|
|
22
|
+
d.title = 'Test'
|
|
23
|
+
d.changes_applied
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'exposes the change made in the last save' do
|
|
28
|
+
expect(doc.changed?).to be false # cleared by changes_applied
|
|
29
|
+
expect(doc.title_change).to be_nil # no change pending right now
|
|
30
|
+
expect(doc.will_save_change_to_title?).to be false
|
|
31
|
+
expect(doc.title_previously_was).to eq('initial')
|
|
32
|
+
expect(doc.title_before_last_save).to be_nil # only set by a real save, not by changes_applied alone
|
|
33
|
+
expect(doc.title_previously_changed?).to be true
|
|
34
|
+
expect(doc.title_previous_change).to eq(['initial', 'Test'])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'exposes the change during the save that triggered it, with an explicit from/to' do
|
|
38
|
+
expect(doc.saved_change_to_title?(from: 'initial')).to be true
|
|
39
|
+
expect(doc.saved_change_to_title?(to: 'Test')).to be true
|
|
40
|
+
expect(doc.saved_change_to_title?(from: 'initial', to: 'Test')).to be true
|
|
41
|
+
expect(doc.saved_change_to_title?(from: 'nope')).to be false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'can flag an attribute as about to change, and reset it back' do
|
|
45
|
+
expect { doc.title_will_change! }.not_to raise_error
|
|
46
|
+
expect { doc.reset_title! }.not_to raise_error
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'falls back to nil for an attribute that was never saved' do
|
|
50
|
+
expect(Doc.new.title_previously_was).to be_nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
16
53
|
end
|
data/spec/n1ql_spec.rb
CHANGED
|
@@ -196,6 +196,25 @@ describe CouchbaseOrm::N1ql do
|
|
|
196
196
|
end
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
+
it "should use adhoc: true by default (no prepared statement plan caching)" do
|
|
200
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: true)).and_call_original
|
|
201
|
+
N1QLTest.by_rating_reverse()
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "should allow overriding adhoc per call to enable plan caching" do
|
|
205
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: false)).and_call_original
|
|
206
|
+
N1QLTest.by_rating_reverse(adhoc: false)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "should respect N1ql.config adhoc setting" do
|
|
210
|
+
default_config = CouchbaseOrm::N1ql.config
|
|
211
|
+
CouchbaseOrm::N1ql.config({ adhoc: false })
|
|
212
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: false)).and_call_original
|
|
213
|
+
N1QLTest.by_rating_reverse()
|
|
214
|
+
ensure
|
|
215
|
+
CouchbaseOrm::N1ql.config(default_config)
|
|
216
|
+
end
|
|
217
|
+
|
|
199
218
|
after(:all) do
|
|
200
219
|
N1QLTest.delete_all
|
|
201
220
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'couchbase-orm/proxies/bucket_proxy'
|
|
2
|
+
require 'couchbase-orm/proxies/n1ql_proxy'
|
|
3
|
+
require 'couchbase-orm/proxies/results_proxy'
|
|
4
|
+
|
|
5
|
+
RSpec.describe CouchbaseOrm::BucketProxy do
|
|
6
|
+
let(:proxyfied) { double('bucket') }
|
|
7
|
+
|
|
8
|
+
it 'raises when asked to proxy nil' do
|
|
9
|
+
expect { CouchbaseOrm::BucketProxy.new(nil) }.to raise_error(ArgumentError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'delegates unknown methods to the proxyfied object' do
|
|
13
|
+
allow(proxyfied).to receive(:name).and_return('my_bucket')
|
|
14
|
+
expect(CouchbaseOrm::BucketProxy.new(proxyfied).name).to eq('my_bucket')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'delegates unknown methods with args, kwargs and a block' do
|
|
18
|
+
allow(proxyfied).to receive(:default_collection).with('a', b: 1).and_yield(:yielded)
|
|
19
|
+
CouchbaseOrm::BucketProxy.new(proxyfied).default_collection('a', b: 1) do |arg|
|
|
20
|
+
expect(arg).to eq(:yielded)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'wraps #n1ql in a N1qlProxy' do
|
|
25
|
+
allow(proxyfied).to receive(:n1ql).and_return(:the_n1ql)
|
|
26
|
+
expect(CouchbaseOrm::BucketProxy.new(proxyfied).n1ql).to be_a(CouchbaseOrm::N1qlProxy)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#view' do
|
|
30
|
+
it 'wraps the result in a ResultsProxy and caches it for the same design/view' do
|
|
31
|
+
allow(proxyfied).to receive(:view).with('design', 'view').and_return([:row1, :row2])
|
|
32
|
+
proxy = CouchbaseOrm::BucketProxy.new(proxyfied)
|
|
33
|
+
|
|
34
|
+
first = proxy.view('design', 'view')
|
|
35
|
+
second = proxy.view('design', 'view')
|
|
36
|
+
|
|
37
|
+
expect(first).to be_a(CouchbaseOrm::ResultsProxy)
|
|
38
|
+
expect(second).to be(first)
|
|
39
|
+
expect(proxyfied).to have_received(:view).once
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'requeries when the design/view pair changes' do
|
|
43
|
+
allow(proxyfied).to receive(:view).with('design', 'view').and_return([:row1])
|
|
44
|
+
allow(proxyfied).to receive(:view).with('design', 'other').and_return([:row2])
|
|
45
|
+
proxy = CouchbaseOrm::BucketProxy.new(proxyfied)
|
|
46
|
+
|
|
47
|
+
proxy.view('design', 'view')
|
|
48
|
+
proxy.view('design', 'other')
|
|
49
|
+
|
|
50
|
+
expect(proxyfied).to have_received(:view).twice
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/spec/relation_spec.rb
CHANGED
|
@@ -501,5 +501,38 @@ describe CouchbaseOrm::Relation do
|
|
|
501
501
|
end
|
|
502
502
|
end
|
|
503
503
|
end
|
|
504
|
+
|
|
505
|
+
it "should use adhoc: true by default (no prepared statement plan caching)" do
|
|
506
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: true)).and_call_original
|
|
507
|
+
RelationModel.where(active: true).ids
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
describe "adhoc option via with" do
|
|
511
|
+
it "should return a relation when calling with(adhoc:)" do
|
|
512
|
+
expect(RelationModel.all.with(adhoc: false)).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it "should pass adhoc: false to query options when set on the relation" do
|
|
516
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: false)).and_call_original
|
|
517
|
+
RelationModel.where(active: true).with(adhoc: false).ids
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
it "should override N1ql.config adhoc when set on the relation" do
|
|
521
|
+
default_config = CouchbaseOrm::N1ql.config
|
|
522
|
+
CouchbaseOrm::N1ql.config(adhoc: false)
|
|
523
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: true)).and_call_original
|
|
524
|
+
RelationModel.where(active: true).with(adhoc: true).ids
|
|
525
|
+
ensure
|
|
526
|
+
CouchbaseOrm::N1ql.config(default_config)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it "should be chainable with other relation methods" do
|
|
530
|
+
m1 = RelationModel.create!(active: true, age: 10)
|
|
531
|
+
_m2 = RelationModel.create!(active: false, age: 20)
|
|
532
|
+
expect(Couchbase::Options::Query).to receive(:new).with(hash_including(adhoc: false)).and_call_original
|
|
533
|
+
result = RelationModel.where(active: true).order(:age).with(adhoc: false).to_a
|
|
534
|
+
expect(result).to match_array([m1])
|
|
535
|
+
end
|
|
536
|
+
end
|
|
504
537
|
end
|
|
505
538
|
|
data/spec/type_encrypted_spec.rb
CHANGED
|
@@ -25,6 +25,16 @@ describe CouchbaseOrm::Types::Encrypted do
|
|
|
25
25
|
let(:the_secret) { "\x17`\x1F\xEE\el\xE0\x9F<\x94\xFE\x8A.\x1A\x92\xB9\xC3@\x86\x9Cp\xBEl\x86\x0E\x8CJ\tB\x97*U)\x96\x06\xA3\xE9\x84\xA6xW%\xDCT\x8C^\xEA\t\xC7\xD8\xFC\xF1\xD3\xD3\xE2\xEA\x89\xCBuUs\xB3\xFF'W>\xDE\x9CP\xA9\xDE%\xA2\xDE\x11\xFD\b\x9C\xD4\x87J,\x91\x02f\x16R\xDE\x908\x05\x1C\xF9\xDF{\x0F\xB3e\xB2\xB2\x96\xD7\xCC\x16As\xD3I\x02w\xE0\x8FL\xC6S\xEFP\xAC\x15\e^\xC4!\x15\"KF1\x17\x06\xA0N\x00\x18\xBA\x87\xEA?H\xD4<\xB5\xBCV\xB50\fc\xC9F\"\xF0B\eg%\x8E\x88\xD0\x9Bc\xE4\x93\t\x98\xC8\x87\xCB4]\xD9K\xA3\xDF\x13Q\xC0T\xCA\x91;\b\x9Cp\xE0\x7FR h\xDA\xB7\xD5\x869\f\xCA\x80\x802\x19\x19\xDD\x9DO\xAE}\xCA\eX\xA3\xA8\xBE\xE1\xBCW0g\x19@5n\r\xD8\xF3\x05\x7F4\x9CI\x1F3\xC0\xBDQJyG\v\xED!s\xD5\xD0&\xC1\x1A\xBC\x17\xFD\x9Cd\xB5\xAF\xB6U\x8A" }
|
|
26
26
|
let(:base64_secret) { Base64.strict_encode64(the_secret) }
|
|
27
27
|
|
|
28
|
+
describe "#serialize" do
|
|
29
|
+
it "returns nil for a nil value" do
|
|
30
|
+
expect(CouchbaseOrm::Types::Encrypted.new.serialize(nil)).to be_nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "returns the value unchanged otherwise (wrapping happens in Encrypt#encode_encrypted_attributes)" do
|
|
34
|
+
expect(CouchbaseOrm::Types::Encrypted.new.serialize(base64_secret)).to eq(base64_secret)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
28
38
|
it "prefix attribute on serialization" do
|
|
29
39
|
obj = TypeEncryptedTest.new(secret: base64_secret, secret2: "a secret")
|
|
30
40
|
expect_serialized_secret(obj)
|
|
@@ -50,6 +60,16 @@ describe CouchbaseOrm::Types::Encrypted do
|
|
|
50
60
|
expect(obj.as_json["secret2"]).to eq "a secret"
|
|
51
61
|
end
|
|
52
62
|
|
|
63
|
+
it "raises when serializing a non-String value for an encrypted attribute" do
|
|
64
|
+
obj = TypeEncryptedTest.new(secret: 12345)
|
|
65
|
+
expect { obj.send(:serialized_attributes) }.to raise_error(/Can not serialize value/)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "raises from #as_json when a non-String value is held by an encrypted attribute" do
|
|
69
|
+
obj = TypeEncryptedTest.new(secret: 12345)
|
|
70
|
+
expect { obj.as_json }.to raise_error(/Can not serialize value/)
|
|
71
|
+
end
|
|
72
|
+
|
|
53
73
|
it "prefix with custom algo" do
|
|
54
74
|
obj = SpecificAlgoTest.new(secret: base64_secret)
|
|
55
75
|
expect(obj.send(:serialized_attributes)["encrypted$secret"]).to eq({alg:"3DES", ciphertext: base64_secret})
|
data/spec/type_nested_spec.rb
CHANGED
|
@@ -18,6 +18,18 @@ class TypeNestedTest < CouchbaseOrm::Base
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
describe CouchbaseOrm::Types::Nested do
|
|
21
|
+
describe "#serialize" do
|
|
22
|
+
it "builds an instance from a raw Hash before serializing it, filtering ignored properties" do
|
|
23
|
+
# `serialize` is normally called with an already-built NestedDocument
|
|
24
|
+
# instance (see the specs below); this exercises its Hash branch
|
|
25
|
+
# directly, using string keys to match the shape a real document
|
|
26
|
+
# (and thus `ignored_properties`, which stores string names) has.
|
|
27
|
+
type = CouchbaseOrm::Types::Nested.new(type: SubTypeWithIgnoredProperties)
|
|
28
|
+
serialized = type.serialize("name" => "Nested", "value" => "Valid", "deprecated_property" => "should be dropped")
|
|
29
|
+
expect(serialized).to eq("name" => "Nested", "value" => "Valid")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
21
33
|
it "should be able to store and retrieve a nested object" do
|
|
22
34
|
obj = TypeNestedTest.new
|
|
23
35
|
obj.main = SubTypeTest.new
|
|
@@ -255,4 +267,84 @@ describe CouchbaseOrm::Types::Nested do
|
|
|
255
267
|
expect(parent.nested.value).to eq("Parent Value")
|
|
256
268
|
end
|
|
257
269
|
end
|
|
270
|
+
|
|
271
|
+
describe "raise_on_unknown_attributes" do
|
|
272
|
+
class SubTypeWithUnknownAttributesAllowed < CouchbaseOrm::NestedDocument
|
|
273
|
+
self.raise_on_unknown_attributes = false
|
|
274
|
+
attribute :label, :string
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class SubTypeWithDefaultUnknownAttributes < CouchbaseOrm::NestedDocument
|
|
278
|
+
attribute :label, :string
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class ParentWithNestedUnknownAttributesAllowed < CouchbaseOrm::Base
|
|
282
|
+
self.raise_on_unknown_attributes = false
|
|
283
|
+
attribute :title, :string
|
|
284
|
+
attribute :main, :nested, type: SubTypeWithUnknownAttributesAllowed
|
|
285
|
+
attribute :others, :array, type: SubTypeWithUnknownAttributesAllowed
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
class ParentWithStrictNestedUnknownAttributes < CouchbaseOrm::Base
|
|
289
|
+
self.raise_on_unknown_attributes = false
|
|
290
|
+
attribute :title, :string
|
|
291
|
+
attribute :main, :nested, type: SubTypeWithDefaultUnknownAttributes
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
it "tolerates an unknown key in a nested document assigned as a Hash, via Types::Nested#cast" do
|
|
295
|
+
# This is the path the actual document-loading code takes; assigning an
|
|
296
|
+
# already-built NestedDocument *instance* (as opposed to a Hash) never
|
|
297
|
+
# reaches Types::Nested#cast at all.
|
|
298
|
+
parent = ParentWithNestedUnknownAttributesAllowed.new(title: "Parent")
|
|
299
|
+
parent.main = SubTypeWithUnknownAttributesAllowed.new(label: "Main")
|
|
300
|
+
parent.save!
|
|
301
|
+
|
|
302
|
+
doc_id = parent.id
|
|
303
|
+
raw_doc = ParentWithNestedUnknownAttributesAllowed.bucket.default_collection.get(doc_id).content
|
|
304
|
+
raw_doc["main"]["legacy"] = "should be ignored"
|
|
305
|
+
ParentWithNestedUnknownAttributesAllowed.bucket.default_collection.replace(doc_id, raw_doc)
|
|
306
|
+
|
|
307
|
+
parent.reload
|
|
308
|
+
expect(parent.main.label).to eq("Main")
|
|
309
|
+
expect(parent.main.attributes.keys).not_to include("legacy")
|
|
310
|
+
|
|
311
|
+
# Same assertion via `find` - a different code path (the GetResult
|
|
312
|
+
# branch of Document#initialize) than `reload`.
|
|
313
|
+
reloaded = ParentWithNestedUnknownAttributesAllowed.find(doc_id)
|
|
314
|
+
expect(reloaded.main.label).to eq("Main")
|
|
315
|
+
expect(reloaded.main.attributes.keys).not_to include("legacy")
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "tolerates an unknown key in a nested document inside a Types::Array, assigned as Hashes" do
|
|
319
|
+
parent = ParentWithNestedUnknownAttributesAllowed.new(title: "Parent")
|
|
320
|
+
parent.others = [
|
|
321
|
+
SubTypeWithUnknownAttributesAllowed.new(label: "First"),
|
|
322
|
+
SubTypeWithUnknownAttributesAllowed.new(label: "Second")
|
|
323
|
+
]
|
|
324
|
+
parent.save!
|
|
325
|
+
|
|
326
|
+
doc_id = parent.id
|
|
327
|
+
raw_doc = ParentWithNestedUnknownAttributesAllowed.bucket.default_collection.get(doc_id).content
|
|
328
|
+
raw_doc["others"][0]["legacy"] = "should be ignored"
|
|
329
|
+
ParentWithNestedUnknownAttributesAllowed.bucket.default_collection.replace(doc_id, raw_doc)
|
|
330
|
+
|
|
331
|
+
parent.reload
|
|
332
|
+
expect(parent.others.map(&:label)).to eq(["First", "Second"])
|
|
333
|
+
expect(parent.others[0].attributes.keys).not_to include("legacy")
|
|
334
|
+
expect(parent.others[1].attributes.keys).not_to include("legacy")
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it "is per nested class: a tolerant parent does not make a strict nested document tolerant" do
|
|
338
|
+
parent = ParentWithStrictNestedUnknownAttributes.new(title: "Parent")
|
|
339
|
+
parent.main = SubTypeWithDefaultUnknownAttributes.new(label: "Main")
|
|
340
|
+
parent.save!
|
|
341
|
+
|
|
342
|
+
doc_id = parent.id
|
|
343
|
+
raw_doc = ParentWithStrictNestedUnknownAttributes.bucket.default_collection.get(doc_id).content
|
|
344
|
+
raw_doc["main"]["legacy"] = "should raise"
|
|
345
|
+
ParentWithStrictNestedUnknownAttributes.bucket.default_collection.replace(doc_id, raw_doc)
|
|
346
|
+
|
|
347
|
+
expect { parent.reload }.to raise_error(ActiveModel::UnknownAttributeError)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
258
350
|
end
|