couchbase-orm 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +45 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +3 -2
  5. data/CODEOWNERS +1 -0
  6. data/Gemfile +5 -3
  7. data/README.md +237 -31
  8. data/ci/run_couchbase.sh +22 -0
  9. data/couchbase-orm.gemspec +26 -20
  10. data/lib/couchbase-orm/active_record_compat.rb +92 -0
  11. data/lib/couchbase-orm/associations.rb +119 -0
  12. data/lib/couchbase-orm/base.rb +143 -166
  13. data/lib/couchbase-orm/changeable.rb +512 -0
  14. data/lib/couchbase-orm/connection.rb +28 -8
  15. data/lib/couchbase-orm/encrypt.rb +48 -0
  16. data/lib/couchbase-orm/error.rb +17 -2
  17. data/lib/couchbase-orm/inspectable.rb +37 -0
  18. data/lib/couchbase-orm/json_schema/json_validation_error.rb +13 -0
  19. data/lib/couchbase-orm/json_schema/loader.rb +47 -0
  20. data/lib/couchbase-orm/json_schema/validation.rb +18 -0
  21. data/lib/couchbase-orm/json_schema/validator.rb +45 -0
  22. data/lib/couchbase-orm/json_schema.rb +9 -0
  23. data/lib/couchbase-orm/json_transcoder.rb +27 -0
  24. data/lib/couchbase-orm/locale/en.yml +5 -0
  25. data/lib/couchbase-orm/n1ql.rb +133 -0
  26. data/lib/couchbase-orm/persistence.rb +61 -52
  27. data/lib/couchbase-orm/proxies/bucket_proxy.rb +36 -0
  28. data/lib/couchbase-orm/proxies/collection_proxy.rb +52 -0
  29. data/lib/couchbase-orm/proxies/n1ql_proxy.rb +40 -0
  30. data/lib/couchbase-orm/proxies/results_proxy.rb +23 -0
  31. data/lib/couchbase-orm/railtie.rb +6 -17
  32. data/lib/couchbase-orm/relation.rb +249 -0
  33. data/lib/couchbase-orm/strict_loading.rb +21 -0
  34. data/lib/couchbase-orm/timestamps/created.rb +20 -0
  35. data/lib/couchbase-orm/timestamps/updated.rb +21 -0
  36. data/lib/couchbase-orm/timestamps.rb +15 -0
  37. data/lib/couchbase-orm/types/array.rb +32 -0
  38. data/lib/couchbase-orm/types/date.rb +9 -0
  39. data/lib/couchbase-orm/types/date_time.rb +14 -0
  40. data/lib/couchbase-orm/types/encrypted.rb +17 -0
  41. data/lib/couchbase-orm/types/nested.rb +43 -0
  42. data/lib/couchbase-orm/types/timestamp.rb +18 -0
  43. data/lib/couchbase-orm/types.rb +20 -0
  44. data/lib/couchbase-orm/utilities/enum.rb +13 -1
  45. data/lib/couchbase-orm/utilities/has_many.rb +72 -36
  46. data/lib/couchbase-orm/utilities/ignored_properties.rb +15 -0
  47. data/lib/couchbase-orm/utilities/index.rb +18 -20
  48. data/lib/couchbase-orm/utilities/properties_always_exists_in_document.rb +16 -0
  49. data/lib/couchbase-orm/utilities/query_helper.rb +148 -0
  50. data/lib/couchbase-orm/utils.rb +25 -0
  51. data/lib/couchbase-orm/version.rb +1 -1
  52. data/lib/couchbase-orm/views.rb +38 -41
  53. data/lib/couchbase-orm.rb +44 -9
  54. data/lib/ext/query_n1ql.rb +124 -0
  55. data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +3 -2
  56. data/spec/associations_spec.rb +219 -50
  57. data/spec/base_spec.rb +296 -14
  58. data/spec/collection_proxy_spec.rb +29 -0
  59. data/spec/connection_spec.rb +27 -0
  60. data/spec/couchbase-orm/active_record_compat_spec.rb +24 -0
  61. data/spec/couchbase-orm/changeable_spec.rb +16 -0
  62. data/spec/couchbase-orm/json_schema/validation_spec.rb +23 -0
  63. data/spec/couchbase-orm/json_schema/validator_spec.rb +13 -0
  64. data/spec/couchbase-orm/timestamps_spec.rb +85 -0
  65. data/spec/couchbase-orm/timestamps_spec_models.rb +36 -0
  66. data/spec/empty-json-schema/.gitkeep +0 -0
  67. data/spec/enum_spec.rb +34 -0
  68. data/spec/has_many_spec.rb +101 -54
  69. data/spec/index_spec.rb +13 -9
  70. data/spec/json-schema/JsonSchemaBaseTest.json +19 -0
  71. data/spec/json-schema/entity_snakecase.json +20 -0
  72. data/spec/json-schema/loader_spec.rb +42 -0
  73. data/spec/json-schema/specific_path.json +20 -0
  74. data/spec/json_schema_spec.rb +178 -0
  75. data/spec/n1ql_spec.rb +193 -0
  76. data/spec/persistence_spec.rb +49 -9
  77. data/spec/relation_nested_spec.rb +88 -0
  78. data/spec/relation_spec.rb +430 -0
  79. data/spec/support.rb +16 -8
  80. data/spec/type_array_spec.rb +52 -0
  81. data/spec/type_encrypted_spec.rb +114 -0
  82. data/spec/type_nested_spec.rb +191 -0
  83. data/spec/type_spec.rb +317 -0
  84. data/spec/utilities/ignored_properties_spec.rb +20 -0
  85. data/spec/utilities/properties_always_exists_in_document_spec.rb +24 -0
  86. data/spec/views_spec.rb +32 -11
  87. metadata +192 -29
data/spec/base_spec.rb CHANGED
@@ -2,20 +2,71 @@
2
2
 
3
3
  require File.expand_path("../support", __FILE__)
4
4
 
5
-
6
5
  class BaseTest < CouchbaseOrm::Base
7
- attribute :name, :job
6
+ attribute :name, :string
7
+ attribute :job, :string
8
8
  end
9
9
 
10
10
  class CompareTest < CouchbaseOrm::Base
11
- attribute :age
11
+ attribute :age, :integer
12
+ end
13
+
14
+ class TimestampTest < CouchbaseOrm::Base
15
+ attribute :created_at, :datetime, precision: 6
16
+ attribute :updated_at, :datetime, precision: 6
17
+ end
18
+
19
+ class BaseTestWithIgnoredProperties < CouchbaseOrm::Base
20
+ self.ignored_properties += [:deprecated_property]
21
+ attribute :name, :string
22
+ attribute :job, :string
23
+ end
24
+
25
+ class BaseTestWithPropertiesAlwaysExistsInDocument < CouchbaseOrm::Base
26
+ self.properties_always_exists_in_document = true
27
+ attribute :name, :string
12
28
  end
13
29
 
30
+ class BaseTestWithTimeframe < CouchbaseOrm::Base
31
+ attribute :name, :string
32
+ attribute :start_date, :datetime
33
+ attribute :age, :integer
34
+ end
35
+
36
+ class DocInvalidOnUpdate < CouchbaseOrm::Base
37
+ attribute :title
38
+ validate :foo, on: :update
14
39
 
40
+ def foo
41
+ errors.add(:title, 'should not be updated')
42
+ end
43
+ end
15
44
 
16
45
  describe CouchbaseOrm::Base do
46
+
47
+ it 'should have clean model after find' do
48
+ base = BaseTest.create!(name: 'joe')
49
+ base = BaseTest.find base.id
50
+ expect(base.changed?).to be false
51
+ end
52
+
53
+ it 'stores not stringified changes' do
54
+ compare = CompareTest.create!
55
+ compare.age = '42'
56
+ compare.save!
57
+ expect(compare.saved_change_to_age).to eq [nil, 42]
58
+ end
59
+
60
+ it 'should update changed_attributes after update' do
61
+ base = BaseTest.create!(name: 'joe')
62
+ base = BaseTest.find base.id
63
+ base.name = 'toto'
64
+ base.save!
65
+ expect(base.saved_change_to_name?).to be true
66
+ expect(base.saved_change_to_name).to eq ["joe", "toto"]
67
+ end
17
68
  it "should be comparable to other objects" do
18
- base = BaseTest.create!(name: 'joe')
69
+ base = BaseTest.create!(name: 'joe')
19
70
  base2 = BaseTest.create!(name: 'joe')
20
71
  base3 = BaseTest.create!(ActiveSupport::HashWithIndifferentAccess.new(name: 'joe'))
21
72
 
@@ -33,14 +84,19 @@ describe CouchbaseOrm::Base do
33
84
  base3.delete
34
85
  end
35
86
 
87
+ it "should be inspectable" do
88
+ base = BaseTest.create!(name: 'joe')
89
+ expect(base.inspect).to eq("#<BaseTest id: \"#{base.id}\", name: \"joe\", job: nil>")
90
+ end
91
+
36
92
  it "should load database responses" do
37
93
  base = BaseTest.create!(name: 'joe')
38
- resp = BaseTest.bucket.get(base.id, extended: true)
94
+ resp = BaseTest.bucket.default_collection.get(base.id)
39
95
 
40
- expect(resp.key).to eq(base.id)
96
+ base_loaded = BaseTest.new(resp, id: base.id)
41
97
 
42
- base_loaded = BaseTest.new(resp)
43
- expect(base_loaded).to eq(base)
98
+ expect(base_loaded.id).to eq(base.id)
99
+ expect(base_loaded).to eq(base)
44
100
  expect(base_loaded).not_to be(base)
45
101
 
46
102
  base.destroy
@@ -49,17 +105,44 @@ describe CouchbaseOrm::Base do
49
105
  it "should not load objects if there is a type mismatch" do
50
106
  base = BaseTest.create!(name: 'joe')
51
107
 
52
- expect { CompareTest.find_by_id(base.id) }.to raise_error(RuntimeError)
108
+ expect { CompareTest.find_by_id(base.id) }.to raise_error(CouchbaseOrm::Error::TypeMismatchError)
53
109
 
54
110
  base.destroy
55
111
  end
56
112
 
113
+ it "raises ActiveModel::UnknownAttributeError on loading objects with unexpected properties" do
114
+ too_much_properties_doc = {
115
+ type: BaseTest.design_document,
116
+ name: 'Pierre',
117
+ job: 'dev',
118
+ age: '42'
119
+ }
120
+ BaseTest.bucket.default_collection.upsert 'doc_1', too_much_properties_doc
121
+
122
+ expect { BaseTest.find_by_id('doc_1') }.to raise_error(ActiveModel::UnknownAttributeError)
123
+
124
+ BaseTest.bucket.default_collection.remove 'doc_1'
125
+ end
126
+
127
+ it "loads objects even if there is a missing property in doc" do
128
+ missing_properties_doc = {
129
+ type: BaseTest.design_document,
130
+ name: 'Pierre'
131
+ }
132
+ BaseTest.bucket.default_collection.upsert 'doc_1', missing_properties_doc
133
+ base = BaseTest.find('doc_1')
134
+
135
+ expect(base.name).to eq('Pierre')
136
+ expect(base.job).to be_nil
137
+ base.destroy
138
+ end
139
+
57
140
  it "should support serialisation" do
58
141
  base = BaseTest.create!(name: 'joe')
59
142
 
60
143
  base_id = base.id
61
- expect(base.to_json).to eq({name: 'joe', job: nil, id: base_id}.to_json)
62
- expect(base.to_json(only: :name)).to eq({name: 'joe'}.to_json)
144
+ expect(base.to_json).to eq({ id: base_id, name: 'joe', job: nil }.to_json)
145
+ expect(base.to_json(only: :name)).to eq({ name: 'joe' }.to_json)
63
146
 
64
147
  base.destroy
65
148
  end
@@ -73,21 +156,27 @@ describe CouchbaseOrm::Base do
73
156
  base.name = 'change'
74
157
  expect(base.changes.empty?).to be(false)
75
158
 
76
- base = BaseTest.new({name: 'bob'})
159
+ # Attributes are set by key
160
+ base = BaseTest.new
161
+ base[:name] = 'bob'
162
+ expect(base.changes.empty?).to be(false)
163
+
164
+ # Attributes are set by initializer from hash
165
+ base = BaseTest.new({ name: 'bob' })
77
166
  expect(base.changes.empty?).to be(false)
78
167
  expect(base.previous_changes.empty?).to be(true)
79
168
 
80
169
  # A saved model should have no changes
81
170
  base = BaseTest.create!(name: 'joe')
82
171
  expect(base.changes.empty?).to be(true)
83
- expect(base.previous_changes.empty?).to be(false)
172
+ expect(base.previous_changes.empty?).to be(true)
84
173
 
85
174
  # Attributes are copied from the existing model
86
175
  base = BaseTest.new(base)
87
176
  expect(base.changes.empty?).to be(false)
88
177
  expect(base.previous_changes.empty?).to be(true)
89
178
  ensure
90
- base.destroy if base.id
179
+ base.destroy if base.persisted?
91
180
  end
92
181
  end
93
182
 
@@ -101,6 +190,85 @@ describe CouchbaseOrm::Base do
101
190
  end
102
191
  end
103
192
 
193
+ it "should be able to create model with a custom ID" do
194
+ begin
195
+ base = BaseTest.create!(id: 'custom_id', name: 'joe')
196
+ expect(base.id).to eq('custom_id')
197
+
198
+ base = BaseTest.find('custom_id')
199
+ expect(base.id).to eq('custom_id')
200
+ ensure
201
+ base&.destroy
202
+ end
203
+ end
204
+
205
+
206
+ it "should try to load a model with nothing but single-multiple ID" do
207
+ begin
208
+ bases = [BaseTest.create!(name: 'joe')]
209
+ objs = CouchbaseOrm.try_load(bases.map(&:id))
210
+ expect(objs).to match_array(bases)
211
+ ensure
212
+ bases.each(&:destroy)
213
+ end
214
+ end
215
+
216
+ it "should try to load a model with nothing but multiple ID" do
217
+ begin
218
+ bases = [BaseTest.create!(name: 'joe'), CompareTest.create!(age: 12)]
219
+ objs = CouchbaseOrm.try_load(bases.map(&:id))
220
+ expect(objs).to match_array(bases)
221
+ ensure
222
+ bases.each(&:destroy)
223
+ end
224
+ end
225
+
226
+ it "should set the attribute on creation" do
227
+ base = BaseTest.create!(name: 'joe')
228
+ expect(base.name).to eq('joe')
229
+ ensure
230
+ base.destroy
231
+ end
232
+
233
+ it "should support getting the attribute by key" do
234
+ base = BaseTest.create!(name: 'joe')
235
+ expect(base[:name]).to eq('joe')
236
+ ensure
237
+ base.destroy
238
+ end
239
+
240
+ it "cannot change the id of a loaded object" do
241
+ base = BaseTest.create!(name: 'joe')
242
+ expect(base.id).to_not be_nil
243
+ expect{base.id = "foo"}.to raise_error(RuntimeError, 'ID cannot be changed')
244
+ end
245
+
246
+ it "should generate a timestamp on creation" do
247
+ base = TimestampTest.create!
248
+ expect(base.created_at).to be_a(Time)
249
+ end
250
+
251
+ it "should find multiple ids at same time" do
252
+ base1 = BaseTest.create!(name: 'joe1')
253
+ base2 = BaseTest.create!(name: 'joe2')
254
+ base3 = BaseTest.create!(name: 'joe3')
255
+ expect(BaseTest.find([base1.id, base2.id, base3.id])).to eq([base1, base2, base3])
256
+ end
257
+
258
+ it "should find multiple ids at same time with a not found id with exception" do
259
+ base1 = BaseTest.create!(name: 'joe1')
260
+ base2 = BaseTest.create!(name: 'joe2')
261
+ base3 = BaseTest.create!(name: 'joe3')
262
+ expect { BaseTest.find([base1.id, 't', base3.id]) }.to raise_error(Couchbase::Error::DocumentNotFound)
263
+ end
264
+
265
+ it "should find multiple ids at same time with a not found id without exception" do
266
+ base1 = BaseTest.create!(name: 'joe1')
267
+ base2 = BaseTest.create!(name: 'joe2')
268
+ base3 = BaseTest.create!(name: 'joe3')
269
+ expect(BaseTest.find([base1.id, 't', 't', base2.id, base3.id], quiet: true)).to eq([base1, base2, base3])
270
+ end
271
+
104
272
  describe BaseTest do
105
273
  it_behaves_like "ActiveModel"
106
274
  end
@@ -108,4 +276,118 @@ describe CouchbaseOrm::Base do
108
276
  describe CompareTest do
109
277
  it_behaves_like "ActiveModel"
110
278
  end
279
+
280
+ it 'does not expose callbacks for nested that wont never be called' do
281
+ expect{
282
+ class InvalidNested < CouchbaseOrm::NestedDocument
283
+ before_save {p "this should raise on loading class"}
284
+ end
285
+
286
+ }.to raise_error NoMethodError
287
+ end
288
+
289
+ it 'should unassign attributes on validation error' do
290
+ doc = DocInvalidOnUpdate.new(title: 'Test')
291
+ doc.save
292
+ expect(doc.title).to eq('Test')
293
+ expect { doc.update!(title: 'changed wich assignation should not stay after raise') }.to raise_error(CouchbaseOrm::Error::RecordInvalid)
294
+ expect(doc.title_was).to eq('Test') # raising in master with "changed wich assignation should not stay after raise"
295
+ expect(doc.title).not_to eq(doc.title_was)
296
+ expect(doc.title).to eq('changed wich assignation should not stay after raise')
297
+ end
298
+
299
+ describe '.ignored_properties' do
300
+ it 'returns an array of ignored properties' do
301
+ expect(BaseTestWithIgnoredProperties.ignored_properties).to eq(['deprecated_property'])
302
+ end
303
+
304
+ context 'given a document with ignored properties' do
305
+ let(:doc_id) { 'doc_1' }
306
+ let(:document_properties) do
307
+ {
308
+ 'type' => BaseTestWithIgnoredProperties.design_document,
309
+ 'name' => 'Pierre',
310
+ 'job' => 'dev',
311
+ 'deprecated_property' => 'depracted that could be removed on next save'
312
+ }
313
+ end
314
+ let(:loaded_model) { BaseTestWithIgnoredProperties.find(doc_id) }
315
+
316
+ before { BaseTestWithIgnoredProperties.bucket.default_collection.upsert doc_id, document_properties }
317
+ after { BaseTestWithIgnoredProperties.bucket.default_collection.remove doc_id }
318
+
319
+ it 'ignores the ignored properties on load from db (and dont raise)' do
320
+ expect(loaded_model.attributes.keys).not_to include('deprecated_property')
321
+ expect(loaded_model.name).to eq('Pierre')
322
+ expect(BaseTestWithIgnoredProperties.bucket.default_collection.get(doc_id).content).to include(document_properties)
323
+ end
324
+
325
+ it 'delete the ignored properties on save' do
326
+ loaded_model.name = 'Updated Name'
327
+ expect{ loaded_model.save }.to change { BaseTestWithIgnoredProperties.bucket.default_collection.get(doc_id).content.keys.sort }.
328
+ from(%w[deprecated_property job name type]).
329
+ to(%w[job name type])
330
+ end
331
+
332
+ it 'does not raise for reload' do
333
+ expect{ loaded_model.reload }.not_to raise_error
334
+ end
335
+ end
336
+ end
337
+
338
+ describe '.properties_always_exists_in_document' do
339
+ it 'Uses NOT VALUED when properties_always_exists_in_document = false' do
340
+ where_clause = BaseTest.where(name: nil)
341
+ expect(where_clause.to_n1ql).to include("AND name IS NOT VALUED")
342
+ end
343
+
344
+ it 'Uses VALUED when properties_always_exists_in_document = false' do
345
+ where_clause = BaseTest.where.not(name: nil)
346
+ expect(where_clause.to_n1ql).to include("AND name IS VALUED")
347
+ end
348
+
349
+ it 'Uses IS NULL when properties_always_exists_in_document = true' do
350
+ where_clause = BaseTestWithPropertiesAlwaysExistsInDocument.where(name: nil)
351
+ expect(where_clause.to_n1ql).to include("AND name IS NULL")
352
+ end
353
+
354
+ it 'Uses IS NOT NULL when properties_always_exists_in_document = true' do
355
+ where_clause = BaseTestWithPropertiesAlwaysExistsInDocument.where.not(name: nil)
356
+ expect(where_clause.to_n1ql).to include("AND name IS NOT NULL")
357
+ end
358
+ end
359
+
360
+ describe 'With a range in the where clause' do
361
+ before do
362
+ BaseTestWithTimeframe.delete_all
363
+ BaseTestWithTimeframe.create!(name: 'january', start_date: DateTime.new(2020, 1, 1, 10, 0, 0), age: 10)
364
+ BaseTestWithTimeframe.create!(name: 'midjanuary', start_date: DateTime.new(2020, 1, 15,11, 0, 0), age: 15)
365
+ BaseTestWithTimeframe.create!(name: 'february', start_date: DateTime.new(2020, 2, 1,15, 0, 0), age: 20)
366
+ end
367
+
368
+ it 'manages the datetime range correctly' do
369
+ result = BaseTestWithTimeframe.where(start_date: DateTime.new(2020, 1, 1, 11, 0,0)..DateTime.new(2020, 1, 31)).pluck(:name)
370
+ expect(result).to eq(%w[midjanuary])
371
+ end
372
+
373
+ it 'manages the date range correctly' do
374
+ result = BaseTestWithTimeframe.where(start_date: Date.new(2020, 1, 1)..Date.new(2020, 1, 31)).pluck(:name)
375
+ expect(result).to eq(%w[january midjanuary])
376
+ end
377
+
378
+ it 'manages the time range correctly' do
379
+ result = BaseTestWithTimeframe.where(start_date: Time.new(2020, 1, 1, 14, 35, 0)..Time.new(2020, 1, 31, 16, 35, 0)).pluck(:name)
380
+ expect(result).to eq(%w[midjanuary])
381
+ end
382
+
383
+ it 'manages the integer range correctly' do
384
+ result = BaseTestWithTimeframe.where(age: 12..25).pluck(:name)
385
+ expect(result).to eq(%w[midjanuary february])
386
+ end
387
+
388
+ it 'manages the exclude end correctly' do
389
+ result = BaseTestWithTimeframe.where(age: 10...15).pluck(:name)
390
+ expect(result).to eq(%w[january])
391
+ end
392
+ end
111
393
  end
@@ -0,0 +1,29 @@
1
+ require File.expand_path("../support", __FILE__)
2
+ require File.expand_path("../../lib/couchbase-orm/proxies/collection_proxy", __FILE__)
3
+
4
+ class Proxyfied
5
+ def get(key, options = nil)
6
+ raise Couchbase::Error::DocumentNotFound
7
+ end
8
+ def remove(key, options = nil)
9
+ raise Couchbase::Error::DocumentNotFound
10
+ end
11
+ end
12
+
13
+ describe CouchbaseOrm::CollectionProxy do
14
+ it "should raise an error when get is called with bang version" do
15
+ expect { CouchbaseOrm::CollectionProxy.new(Proxyfied.new).get!('key') }.to raise_error(Couchbase::Error::DocumentNotFound)
16
+ end
17
+
18
+ it "should not raise an error when get is called with non bang version" do
19
+ expect { CouchbaseOrm::CollectionProxy.new(Proxyfied.new).get('key') }.to_not raise_error
20
+ end
21
+
22
+ it "should raise an error when remove is called with bang version" do
23
+ expect { CouchbaseOrm::CollectionProxy.new(Proxyfied.new).remove!('key') }.to raise_error(Couchbase::Error::DocumentNotFound)
24
+ end
25
+
26
+ it "should not raise an error when remove is called with non bang version" do
27
+ expect { CouchbaseOrm::CollectionProxy.new(Proxyfied.new).remove('key') }.to_not raise_error
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true, encoding: ASCII-8BIT
2
+
3
+ require File.expand_path("../support", __FILE__)
4
+
5
+ class ConnectedModel < CouchbaseOrm::Base
6
+ attribute :name, :string
7
+ end
8
+
9
+ # disabled by default because a little hacky
10
+ # and test couchbase ruby client not couchbase orm
11
+
12
+ return unless ENV["TEST_DOCKER_CONTAINER"]
13
+
14
+ describe CouchbaseOrm::Base do
15
+ it "should reconnect after a disconnection" do
16
+ s = ConnectedModel.create!(name: "foo")
17
+ `docker stop #{ENV["TEST_DOCKER_CONTAINER"]}`
18
+ sleep 3
19
+ expect {ConnectedModel.find(s.id)}.to raise_error(Couchbase::Error::UnambiguousTimeout)
20
+ `docker start #{ENV["TEST_DOCKER_CONTAINER"]}`
21
+ sleep 10
22
+ s2 = ConnectedModel.find(s.id)
23
+ expect(s2.name).to eq (s.name)
24
+ ensure
25
+ `docker start #{ENV["TEST_DOCKER_CONTAINER"]}`
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ require './lib/couchbase-orm/active_record_compat'
2
+
3
+ class Foo
4
+ include CouchbaseOrm::ActiveRecordCompat
5
+
6
+ def compute_age
7
+ 10 + 32
8
+ end
9
+ end
10
+
11
+ describe CouchbaseOrm::ActiveRecordCompat do
12
+ let(:foo) { Foo.new }
13
+ describe '#slice' do
14
+ it 'creates a hash with method names as keys and results as values' do
15
+ expect(foo.slice(:compute_age).to_h).to eq(HashWithIndifferentAccess.new({ compute_age: 42 }))
16
+ end
17
+ end
18
+
19
+ describe '#values_at' do
20
+ it 'creates an array of results from given method names' do
21
+ expect(foo.values_at([:compute_age])).to eq([42])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'couchbase-orm'
4
+
5
+ class Doc < CouchbaseOrm::Base
6
+ attribute :title
7
+ end
8
+
9
+ describe 'CouchbaseOrm::Changeable' do
10
+ it 'should have empty changes after loading a record from db' do
11
+ doc = Doc.create(title: 'Test')
12
+ expect(doc.changes).to be_empty
13
+ doc = Doc.find(doc.id)
14
+ expect(doc.changes).to be_empty
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'couchbase-orm/json_schema/validation'
2
+
3
+ class DummyClass
4
+ extend CouchbaseOrm::JsonSchema::Validation
5
+ validate_json_schema
6
+ end
7
+
8
+ class DummyClass2
9
+ extend CouchbaseOrm::JsonSchema::Validation
10
+ end
11
+
12
+ RSpec.describe CouchbaseOrm::JsonSchema::Validation do
13
+ describe '#validate_json_schema' do
14
+ it 'sets @validate_json_schema to true' do
15
+ expect(DummyClass.json_validation_config[:enabled]).to be_truthy
16
+ end
17
+
18
+ it 'does not mixup @validate_json_schema between classes' do
19
+ expect(DummyClass.json_validation_config[:enabled]).to be_truthy
20
+ expect(DummyClass2.json_validation_config[:enabled]).to be_falsey
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ require 'couchbase-orm/json_schema/validator'
2
+
3
+ RSpec.describe CouchbaseOrm::JsonSchema::Validator do
4
+ describe '#validate_entity' do
5
+ context 'when mode is set to an unknown value' do
6
+ let(:json_validation_config) { { mode: :unknown } }
7
+ let(:validator) { CouchbaseOrm::JsonSchema::Validator.new(json_validation_config) }
8
+ it 'raises an error' do
9
+ expect { validator.validate_entity({}, '') }.to raise_error('Unknown validation mode unknown')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ require 'couchbase-orm'
3
+ require 'couchbase-orm/timestamps_spec_models'
4
+
5
+ describe CouchbaseOrm::Timestamps do
6
+ context 'simple document' do
7
+ describe 'without any related attributes' do
8
+ let(:doc) { DocWithoutTimestamps.new }
9
+ it 'does not run timestamp callbacks on create' do
10
+ expect(doc).not_to receive(:updated_at=)
11
+ expect(doc).not_to receive(:created_at=)
12
+ doc.save
13
+ end
14
+ it 'does not run timestamp callbacks on update' do
15
+ doc.save
16
+ expect(doc).not_to receive(:updated_at=)
17
+ expect(doc).not_to receive(:created_at=)
18
+ doc.title = 'New title'
19
+ doc.save
20
+ end
21
+ end
22
+
23
+ describe 'with only created_at attribute' do
24
+ let(:doc) { DocWithCreatedAt.new }
25
+ it 'runs created_at timestamp callback on create' do
26
+ expect(doc).to receive(:created_at=)
27
+ expect(doc).not_to receive(:updated_at=)
28
+ doc.save
29
+ end
30
+ it 'does not run timestamp callback on update' do
31
+ doc.save
32
+ expect(doc).not_to receive(:created_at=)
33
+ expect(doc).not_to receive(:updated_at=)
34
+ doc.title = 'New title'
35
+ doc.save
36
+ end
37
+ end
38
+
39
+ describe 'with only updated_at attribute' do
40
+ let(:doc) { DocWithUpdatedAt.new }
41
+ it 'does run timestamp callback on create only for updated_at' do
42
+ expect(doc).not_to receive(:created_at=)
43
+ expect(doc).to receive(:updated_at=)
44
+ doc.save
45
+ end
46
+ it 'runs updated_at timestamp callback on update' do
47
+ doc.save
48
+ expect(doc).to receive(:updated_at=)
49
+ expect(doc).not_to receive(:created_at=)
50
+ doc.title = 'New title'
51
+ doc.save
52
+ end
53
+ end
54
+
55
+ describe 'with both created_at and updated_at attributes' do
56
+ let(:doc) { DocWithBothTimestampsAttributes.new }
57
+ it 'runs created_at timestamp callback on create' do
58
+ expect(doc).to receive(:created_at=)
59
+ expect(doc).to receive(:updated_at=)
60
+ doc.save
61
+ end
62
+ it 'runs updated_at timestamp callback on update' do
63
+ doc.save
64
+ expect(doc).to receive(:updated_at=)
65
+ doc.title = 'New title'
66
+ doc.save
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'with nested documents' do
72
+ let(:nested_doc) { SimpleNestedDoc.new }
73
+ describe 'when parent document has both timestamp attributes' do
74
+ let(:doc) { DocWithBothTimestampsAttributesAndNested.new }
75
+ # nested document is not saved, so it does not have created_at
76
+ it 'runs created_at timestamp callback on create only for parent document' do
77
+ expect(doc).to receive(:created_at=)
78
+ expect(doc).to receive(:updated_at=)
79
+ doc.sub = nested_doc
80
+ expect(nested_doc).not_to receive(:created_at=)
81
+ doc.save
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require 'couchbase-orm/base'
3
+
4
+ class DocWithoutTimestamps < CouchbaseOrm::Base
5
+ attribute :title
6
+ end
7
+
8
+ class DocWithCreatedAt < CouchbaseOrm::Base
9
+ attribute :title
10
+ attribute :created_at, :datetime
11
+ end
12
+
13
+ class DocWithUpdatedAt < CouchbaseOrm::Base
14
+ attribute :title
15
+ attribute :updated_at, :datetime
16
+ end
17
+
18
+ class DocWithBothTimestampsAttributes < CouchbaseOrm::Base
19
+ attribute :title
20
+ attribute :created_at, :datetime
21
+ attribute :updated_at, :datetime
22
+ end
23
+
24
+ class SimpleNestedDoc < CouchbaseOrm::NestedDocument
25
+ attribute :sub_title
26
+ attribute :created_at, :datetime
27
+ attribute :updated_at, :datetime
28
+ end
29
+
30
+ class DocWithBothTimestampsAttributesAndNested < CouchbaseOrm::Base
31
+ attribute :title
32
+ attribute :created_at, :datetime
33
+ attribute :updated_at, :datetime
34
+ attribute :sub, :nested, type: SimpleNestedDoc
35
+ attribute :subs, :array, type: SimpleNestedDoc
36
+ end
File without changes
data/spec/enum_spec.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true, encoding: ASCII-8BIT
2
+
3
+ require File.expand_path("../support", __FILE__)
4
+
5
+ class EnumTest < CouchbaseOrm::Base
6
+ enum rating: [:awesome, :good, :okay, :bad], default: :okay
7
+ enum color: [:red, :green, :blue]
8
+ end
9
+
10
+ describe CouchbaseOrm::Base do
11
+ it "should create an attribute" do
12
+ base = EnumTest.create!(rating: :good, color: :red)
13
+ expect(base.attribute_names).to eq(["id", "rating", "color"])
14
+ end
15
+
16
+ it "should set the attribute" do
17
+ base = EnumTest.create!(rating: :good, color: :red)
18
+ expect(base.rating).to_not be_nil
19
+ expect(base.color).to_not be_nil
20
+ end
21
+
22
+ it "should convert it to an int" do
23
+ base = EnumTest.create!(rating: :good, color: :red)
24
+ expect(base.rating).to eq 2
25
+ expect(base.color).to eq 1
26
+ end
27
+
28
+ it "should use default value" do
29
+ base = EnumTest.create!
30
+ expect(base.rating).to eq 3
31
+ expect(base.color).to eq 1
32
+ end
33
+ end
34
+