couchbase-orm 1.1.1 → 2.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.
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
@@ -0,0 +1,430 @@
1
+ # frozen_string_literal: true, encoding: ASCII-8BIT
2
+
3
+ require File.expand_path("../support", __FILE__)
4
+
5
+ class NestedRelationModel < CouchbaseOrm::NestedDocument
6
+ attribute :name, :string
7
+ attribute :age, :integer
8
+ end
9
+
10
+ class PathRelationModel < CouchbaseOrm::NestedDocument
11
+ attribute :pathelement, :nested, type: PathRelationModel
12
+ attribute :children, :array, type: NestedRelationModel
13
+ end
14
+
15
+ class RelationModel < CouchbaseOrm::Base
16
+ attribute :name, :string
17
+ attribute :last_name, :string
18
+ attribute :active, :boolean
19
+ attribute :age, :integer
20
+ attribute :children, :array, type: NestedRelationModel
21
+ attribute :pathelement, :nested, type: PathRelationModel
22
+ def self.adult
23
+ where(age: {_gte: 18})
24
+ end
25
+
26
+ def self.active
27
+ where(active: true)
28
+ end
29
+ end
30
+
31
+ describe CouchbaseOrm::Relation do
32
+ before(:each) do
33
+ RelationModel.delete_all
34
+ CouchbaseOrm.logger.debug "Cleaned before tests"
35
+ end
36
+
37
+ after(:all) do
38
+ CouchbaseOrm.logger.debug "Cleanup after all tests"
39
+ RelationModel.delete_all
40
+ end
41
+
42
+ it "should return a relation" do
43
+ expect(RelationModel.all).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation)
44
+ end
45
+
46
+ it "should query with conditions" do
47
+ RelationModel.create! name: :bob, active: true, age: 10
48
+ RelationModel.create! name: :alice, active: true, age: 20
49
+ RelationModel.create! name: :john, active: false, age: 30
50
+ expect(RelationModel.where(active: true).count).to eq(2)
51
+ expect(RelationModel.where(active: true).size).to eq(2)
52
+
53
+ expect(RelationModel.where(active: true).to_a.map(&:name)).to match_array(%w[bob alice])
54
+ expect(RelationModel.where(active: true).where(age: 10).to_a.map(&:name)).to match_array(%w[bob])
55
+ end
56
+
57
+ it "should query with merged conditions" do
58
+ RelationModel.create! name: :bob, active: true, age: 10
59
+ RelationModel.create! name: :bob, active: false, age: 10
60
+ RelationModel.create! name: :alice, active: true, age: 20
61
+ RelationModel.create! name: :john, active: false, age: 30
62
+
63
+ expect(RelationModel.where(active: true).where(name: 'bob').count).to eq(1)
64
+ end
65
+
66
+ it "should find_by conditions" do
67
+ RelationModel.create! name: :bob, active: true, age: 10
68
+ m = RelationModel.create! name: :bob, active: false, age: 10
69
+ RelationModel.create! name: :alice, active: true, age: 20
70
+ RelationModel.create! name: :alice, active: false, age: 20
71
+
72
+ expect(RelationModel.where(name: 'bob').find_by(active: false)).to eq(m)
73
+ expect(RelationModel.find_by(name: 'bob', active: false)).to eq(m)
74
+ end
75
+
76
+ it "should count without loading models" do
77
+ RelationModel.create! name: :bob, active: true, age: 10
78
+ RelationModel.create! name: :alice, active: false, age: 20
79
+
80
+ expect(RelationModel).not_to receive(:find)
81
+
82
+ expect(RelationModel.where(active: true).count).to eq(1)
83
+ end
84
+
85
+ it "Should delete_all" do
86
+ RelationModel.create!
87
+ RelationModel.create!
88
+ RelationModel.delete_all
89
+ expect(RelationModel.ids).to match_array([])
90
+ end
91
+
92
+ it "Should delete_all with conditions" do
93
+ RelationModel.create!
94
+ jane = RelationModel.create! name: "Jane"
95
+ RelationModel.where(name: nil).delete_all
96
+ expect(RelationModel.ids).to match_array([jane.id])
97
+ end
98
+
99
+ it "Should query ids" do
100
+ expect(RelationModel.ids).to match_array([])
101
+ m1 = RelationModel.create!
102
+ m2 = RelationModel.create!
103
+ expect(RelationModel.ids).to match_array([m1.id, m2.id])
104
+ end
105
+
106
+ it "Should query ids with conditions" do
107
+ m1 = RelationModel.create!(active: true, name: "Jane")
108
+ _m2 = RelationModel.create!(active: false, name: "Bob" )
109
+ _m3 = RelationModel.create!(active: false, name: "Jane")
110
+ expect(RelationModel.where(active: true, name: "Jane").ids).to match_array([m1.id])
111
+ end
112
+
113
+ it "Should query ids with conditions and limit" do
114
+ RelationModel.create!(active: true, name: "Jane", age: 2)
115
+ RelationModel.create!(active: false, name: "Bob", age: 3)
116
+ m = RelationModel.create!(active: true, name: "Jane", age: 1)
117
+ RelationModel.create!(active: false, name: "Jane", age: 0)
118
+
119
+ expect(RelationModel.where(active: true, name: "Jane").order(:age).limit(1).ids).to match_array([m.id])
120
+ expect(RelationModel.limit(1).where(active: true, name: "Jane").order(:age).ids).to match_array([m.id])
121
+ end
122
+
123
+ it "Should query ids with order" do
124
+ m1 = RelationModel.create!(age: 10, name: 'b')
125
+ m2 = RelationModel.create!(age: 20, name: 'a')
126
+ expect(RelationModel.order(age: :desc).ids).to match_array([m2.id, m1.id])
127
+ expect(RelationModel.order(age: :asc).ids).to match_array([m1.id, m2.id])
128
+ expect(RelationModel.order(name: :desc).ids).to match_array([m1.id, m2.id])
129
+ expect(RelationModel.order(name: :asc).ids).to match_array([m2.id, m1.id])
130
+ expect(RelationModel.order(:name).ids).to match_array([m2.id, m1.id])
131
+ expect(RelationModel.order(:age).ids).to match_array([m1.id, m2.id])
132
+ end
133
+
134
+ it "Should query with list order" do
135
+ m1 = RelationModel.create!(age: 20, name: 'b')
136
+ m2 = RelationModel.create!(age: 5, name: 'a')
137
+ m3 = RelationModel.create!(age: 20, name: 'a')
138
+ expect(RelationModel.order(:age, :name).ids).to match_array([m2.id, m3.id, m1.id])
139
+ end
140
+
141
+ it "Should query with chained order" do
142
+ m1 = RelationModel.create!(age: 10, name: 'b')
143
+ m2 = RelationModel.create!(age: 20, name: 'a')
144
+ m3 = RelationModel.create!(age: 20, name: 'c')
145
+ expect(RelationModel.order(age: :desc).order(name: :asc).ids).to match_array([m2.id, m3.id, m1.id])
146
+ end
147
+
148
+ it "Should query with order chained with list" do
149
+ m1 = RelationModel.create!(age: 20, name: 'b')
150
+ m2 = RelationModel.create!(age: 5, name: 'a')
151
+ m3 = RelationModel.create!(age: 20, name: 'a', last_name: 'c')
152
+ m4 = RelationModel.create!(age: 20, name: 'a', last_name: 'a')
153
+ expect(RelationModel.order(:age, :name).order(:last_name).ids).to match_array([m2.id, m4.id, m3.id, m1.id])
154
+ end
155
+
156
+ it "Should query all" do
157
+ m1 = RelationModel.create!(active: true)
158
+ m2 = RelationModel.create!(active: false)
159
+ expect(RelationModel.all).to match_array([m1, m2])
160
+ end
161
+
162
+ it "should query all with condition and order" do
163
+ m1 = RelationModel.create!(active: true, age: 10)
164
+ m2 = RelationModel.create!(active: true, age: 20)
165
+ _m3 = RelationModel.create!(active: false, age: 30)
166
+ expect(RelationModel.where(active: true).order(age: :desc).all.to_a).to eq([m2, m1])
167
+ expect(RelationModel.all.where(active: true).order(age: :asc).to_a).to eq([m1, m2])
168
+ end
169
+
170
+ it "should query by id" do
171
+ m1 = RelationModel.create!(active: true, age: 10)
172
+ m2 = RelationModel.create!(active: true, age: 20)
173
+ _m3 = RelationModel.create!(active: false, age: 30)
174
+ expect(RelationModel.where(id: [m1.id, m2.id])).to match_array([m1, m2])
175
+ end
176
+
177
+ it "should query first" do
178
+ _m1 = RelationModel.create!(active: true, age: 10)
179
+ m2 = RelationModel.create!(active: true, age: 20)
180
+ _m3 = RelationModel.create!(active: false, age: 30)
181
+ expect(RelationModel.where(active: true).order(age: :desc).first).to eq m2
182
+ end
183
+
184
+ it "should query array first" do
185
+ _m1 = RelationModel.create!(active: true, age: 10)
186
+ m2 = RelationModel.create!(active: true, age: 20)
187
+ _m3 = RelationModel.create!(active: false, age: 30)
188
+ expect(RelationModel.where(active: true).order(age: :desc)[0]).to eq m2
189
+ end
190
+
191
+ it "should query last" do
192
+ _m1 = RelationModel.create!(active: true, age: 10)
193
+ m2 = RelationModel.create!(active: true, age: 20)
194
+ _m3 = RelationModel.create!(active: false, age: 30)
195
+ expect(RelationModel.where(active: true).order(age: :asc).last).to eq m2
196
+ end
197
+
198
+ it "should return a relation when using not" do
199
+ expect(RelationModel.not(active: true)).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation)
200
+ expect(RelationModel.all.not(active: true)).to be_a(CouchbaseOrm::Relation::CouchbaseOrm_Relation)
201
+ end
202
+
203
+ it "should have a to_ary method" do
204
+ expect(RelationModel.not(active: true)).to respond_to(:to_ary)
205
+ expect(RelationModel.all.not(active: true)).to respond_to(:to_ary)
206
+ end
207
+
208
+ it "should have a each method" do
209
+ expect(RelationModel.not(active: true)).to respond_to(:each)
210
+ expect(RelationModel.all.not(active: true)).to respond_to(:each)
211
+ end
212
+
213
+ it "should pluck one element" do
214
+ _m1 = RelationModel.create!(active: true, age: 10)
215
+ _m2 = RelationModel.create!(active: true, age: 20)
216
+ _m3 = RelationModel.create!(active: false, age: 30)
217
+ expect(RelationModel.order(:age).pluck(:age)).to match_array([10, 20, 30])
218
+ end
219
+
220
+ it "should find one element" do
221
+ _m1 = RelationModel.create!(active: true, age: 10)
222
+ m2 = RelationModel.create!(active: true, age: 20)
223
+ _m3 = RelationModel.create!(active: false, age: 30)
224
+ expect(RelationModel.all.find do |m|
225
+ m.age == 20
226
+ end).to eq m2
227
+ end
228
+
229
+ it "should pluck several elements" do
230
+ _m1 = RelationModel.create!(active: true, age: 10)
231
+ _m2 = RelationModel.create!(active: true, age: 20)
232
+ _m3 = RelationModel.create!(active: false, age: 30)
233
+ expect(RelationModel.order(:age).pluck(:age, :active)).to match_array([[10, true], [20, true], [30, false]])
234
+ end
235
+
236
+ it "should query true boolean" do
237
+ m1 = RelationModel.create!(active: true)
238
+ _m2 = RelationModel.create!(active: false)
239
+ _m3 = RelationModel.create!(active: nil)
240
+ expect(RelationModel.where(active: true)).to match_array([m1])
241
+ end
242
+
243
+ it "should not query true boolean" do
244
+ _m1 = RelationModel.create!(active: true)
245
+ m2 = RelationModel.create!(active: false)
246
+ _m3 = RelationModel.create!(active: nil)
247
+ expect(RelationModel.not(active: true)).to match_array([m2]) # keep ActiveRecord compatibility by not returning _m3
248
+ end
249
+
250
+ it "should query false boolean" do
251
+ _m1 = RelationModel.create!(active: true)
252
+ m2 = RelationModel.create!(active: false)
253
+ _m3 = RelationModel.create!(active: nil)
254
+ expect(RelationModel.where(active: false)).to match_array([m2])
255
+ end
256
+
257
+ it "should not query false boolean" do
258
+ m1 = RelationModel.create!(active: true)
259
+ _m2 = RelationModel.create!(active: false)
260
+ _m3 = RelationModel.create!(active: nil)
261
+ expect(RelationModel.not(active: false)).to match_array([m1]) # keep ActiveRecord compatibility by not returning _m3
262
+ end
263
+
264
+ it "should query nil boolean" do
265
+ _m1 = RelationModel.create!(active: true)
266
+ _m2 = RelationModel.create!(active: false)
267
+ m3 = RelationModel.create!(active: nil)
268
+ expect(RelationModel.where(active: nil)).to match_array([m3])
269
+ end
270
+
271
+ it "should not query nil boolean" do
272
+ m1 = RelationModel.create!(active: true)
273
+ m2 = RelationModel.create!(active: false)
274
+ _m3 = RelationModel.create!(active: nil)
275
+ expect(RelationModel.not(active: nil)).to match_array([m1, m2])
276
+ end
277
+
278
+ it "should query nil and false boolean" do
279
+ _m1 = RelationModel.create!(active: true)
280
+ m2 = RelationModel.create!(active: false)
281
+ m3 = RelationModel.create!(active: nil)
282
+ expect(RelationModel.where(active: [false, nil])).to match_array([m2, m3])
283
+ end
284
+
285
+ it "should not query nil and false boolean" do
286
+ m1 = RelationModel.create!(active: true)
287
+ _m2 = RelationModel.create!(active: false)
288
+ _m3 = RelationModel.create!(active: nil)
289
+ expect(RelationModel.not(active: [false, nil])).to match_array([m1])
290
+ end
291
+
292
+ it "should query by string" do
293
+ m1 = RelationModel.create!(age: 20, active: true)
294
+ m2 = RelationModel.create!(age: 10, active: false)
295
+ m3 = RelationModel.create!(age: 20, active: false)
296
+
297
+ expect(RelationModel.where("active = true").count).to eq(1)
298
+ expect(RelationModel.where("active = true")).to match_array([m1])
299
+ expect(RelationModel.where("active = false")).to match_array([m2, m3])
300
+ expect(RelationModel.where(age: 20).where("active = false")).to match_array([m3])
301
+ expect(RelationModel.where("active = false").where(age: 20)).to match_array([m3])
302
+ end
303
+
304
+ it "is empty" do
305
+ expect(RelationModel.empty?).to eq(true)
306
+ end
307
+
308
+ it "is not empty with a created model" do
309
+ RelationModel.create!(active: true)
310
+ expect(RelationModel.empty?).to eq(false)
311
+ end
312
+
313
+ describe "operators" do
314
+ it "should query by gte and lte" do
315
+ _m1 = RelationModel.create!(age: 10)
316
+ m2 = RelationModel.create!(age: 20)
317
+ m3 = RelationModel.create!(age: 30)
318
+ _m4 = RelationModel.create!(age: 40)
319
+ expect(RelationModel.where(age: {_lte: 30, _gt:10})).to match_array([m2, m3])
320
+ end
321
+ end
322
+
323
+ describe "update_all" do
324
+ it "should update matching documents" do
325
+ m1 = RelationModel.create!(age: 10)
326
+ m2 = RelationModel.create!(age: 20)
327
+ m3 = RelationModel.create!(age: 30)
328
+ m4 = RelationModel.create!(age: 40)
329
+ RelationModel.where(age: {_lte: 30, _gt:10}).update_all(age: 50)
330
+ expect(m1.reload.age).to eq(10)
331
+ expect(m2.reload.age).to eq(50)
332
+ expect(m3.reload.age).to eq(50)
333
+ expect(m4.reload.age).to eq(40)
334
+ end
335
+
336
+ it "should update nested attributes with a for clause (when hash style)" do
337
+ m1 = RelationModel.create!(age: 10, children: [NestedRelationModel.new(age: 10, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
338
+ m2 = RelationModel.create!(age: 20, children: [NestedRelationModel.new(age: 15, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
339
+ m3 = RelationModel.create!(age: 20, children: [NestedRelationModel.new(age: 10, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
340
+
341
+ RelationModel.where(age: 20).update_all(child: {age: 50, _for: :children, _when: {child: {name: "Tom"}}})
342
+
343
+ expect(m1.reload.children.map(&:age)).to eq([10, 20])
344
+ expect(m2.reload.children.map(&:age)).to eq([50, 20])
345
+ expect(m3.reload.children.map(&:age)).to eq([50, 20])
346
+ end
347
+
348
+ it "should update nested attributes with a for clause (when path style)" do
349
+ m1 = RelationModel.create!(age: 10, children: [NestedRelationModel.new(age: 10, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
350
+ m2 = RelationModel.create!(age: 20, children: [NestedRelationModel.new(age: 15, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
351
+ m3 = RelationModel.create!(age: 20, children: [NestedRelationModel.new(age: 10, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")])
352
+
353
+ RelationModel.where(age: 20).update_all(child: {age: 50, _for: :children, _when: {'child.name': "Tom"}})
354
+
355
+ expect(m1.reload.children.map(&:age)).to eq([10, 20])
356
+ expect(m2.reload.children.map(&:age)).to eq([50, 20])
357
+ expect(m3.reload.children.map(&:age)).to eq([50, 20])
358
+ end
359
+
360
+ it "should update nested attributes with a path in a for clause" do
361
+ m1 = RelationModel.create!(
362
+ pathelement: PathRelationModel.new(
363
+ pathelement: PathRelationModel.new(
364
+ children: [NestedRelationModel.new(age: 10, name: "Tom"), NestedRelationModel.new(age: 20, name: "Jerry")]
365
+ )
366
+ )
367
+ )
368
+
369
+ RelationModel.update_all(child: {age: 50, _for: 'pathelement.pathelement.children', _when: {'child.name': "Tom"}})
370
+
371
+ expect(m1.reload.pathelement.pathelement.children.map(&:age)).to eq([50, 20])
372
+ end
373
+ end
374
+
375
+ describe "scopes" do
376
+ it "should return block value" do
377
+ RelationModel.create!(active: true)
378
+ RelationModel.create!(active: false)
379
+ count = RelationModel.active.scoping do
380
+ RelationModel.count
381
+ end
382
+ expect(count).to eq 1
383
+ end
384
+
385
+ it "should chain scopes" do
386
+ _m1 = RelationModel.create!(age: 10, active: true)
387
+ _m2 = RelationModel.create!(age: 20, active: false)
388
+ m3 = RelationModel.create!(age: 30, active: true)
389
+ m4 = RelationModel.create!(age: 40, active: true)
390
+
391
+ expect(RelationModel.all.adult.all.active.all).to match_array([m3, m4])
392
+ expect(RelationModel.where(active: true).adult).to match_array([m3, m4])
393
+ end
394
+
395
+ it "should be scoped only in current thread" do
396
+ m1 = RelationModel.create!(active: true)
397
+ m2 = RelationModel.create!(active: false)
398
+ RelationModel.active.scoping do
399
+ expect(RelationModel.all).to match_array([m1])
400
+ Thread.start do
401
+ expect(RelationModel.all).to match_array([m1, m2])
402
+ end.join
403
+ end
404
+ end
405
+
406
+ it "should propagate error" do
407
+ expect{RelationModel.active.scoping do
408
+ raise "error"
409
+ end}.to raise_error(RuntimeError)
410
+ end
411
+
412
+ it "should not keep scope in case of error" do
413
+ _m1 = RelationModel.create!(age: 10, active: true)
414
+ _m2 = RelationModel.create!(age: 10, active: false)
415
+ _m3 = RelationModel.create!(age: 30, active: true)
416
+ _m3 = RelationModel.create!(age: 30, active: false)
417
+ RelationModel.active.scoping do
418
+ expect(RelationModel.count).to eq 2
419
+ begin
420
+ RelationModel.adult.scoping do
421
+ raise "error"
422
+ end
423
+ rescue RuntimeError
424
+ end
425
+ expect(RelationModel.count).to eq 2
426
+ end
427
+ end
428
+ end
429
+ end
430
+
data/spec/support.rb CHANGED
@@ -1,16 +1,24 @@
1
1
  # frozen_string_literal: true, encoding: ASCII-8BIT
2
-
3
-
4
- if ENV['TRAVIS_TEST']
5
- require 'libcouchbase'
6
- Libcouchbase::Defaults.username = 'tester'
7
- Libcouchbase::Defaults.password = 'password123'
8
- end
9
-
2
+ require 'simplecov'
10
3
  require 'couchbase-orm'
11
4
  require 'minitest/assertions'
12
5
  require 'active_model/lint'
6
+ require 'pry'
7
+ require 'pry-stack_explorer'
8
+
9
+ SimpleCov.start do
10
+ add_group 'Core', [/lib\/couchbase-orm\/(?!(proxies|utilities))/, 'lib/couchbase-orm.rb']
11
+ add_group 'Proxies', 'lib/couchbase-orm/proxies'
12
+ add_group 'Utilities', 'lib/couchbase-orm/utilities'
13
+ add_group 'Specs', 'spec'
14
+ minimum_coverage 94
15
+ end
13
16
 
17
+ if ENV["COUCHBASE_FLUSH"]
18
+ CouchbaseOrm.logger.warn "Flushing Couchbase bucket '#{CouchbaseOrm::Connection.bucket.name}'"
19
+ CouchbaseOrm::Connection.cluster.buckets.flush_bucket(CouchbaseOrm::Connection.bucket.name)
20
+ raise "BucketFlushed"
21
+ end
14
22
 
15
23
  shared_examples_for "ActiveModel" do
16
24
  include Minitest::Assertions
@@ -0,0 +1,52 @@
1
+ require File.expand_path("../support", __FILE__)
2
+
3
+ require "active_model"
4
+
5
+ class TypeArrayTest < CouchbaseOrm::Base
6
+ attribute :name
7
+ attribute :tags, :array, type: :string
8
+ attribute :milestones, :array, type: :date
9
+ attribute :flags, :array, type: :boolean
10
+ attribute :things
11
+ end
12
+
13
+ describe CouchbaseOrm::Base do
14
+ it "should be able to store and retrieve an array of strings" do
15
+ obj = TypeArrayTest.new
16
+ obj.tags = ["foo", "bar"]
17
+ obj.save!
18
+
19
+ obj = TypeArrayTest.find(obj.id)
20
+ expect(obj.tags).to eq ["foo", "bar"]
21
+ end
22
+
23
+ it "should be able to store and retrieve an array of date" do
24
+ dates = [Date.today, Date.today + 1]
25
+ obj = TypeArrayTest.new
26
+ obj.milestones = dates
27
+ obj.save!
28
+
29
+ obj = TypeArrayTest.find(obj.id)
30
+ expect(obj.milestones).to eq dates
31
+ end
32
+
33
+ it "should be able to store and retrieve an array of boolean" do
34
+ flags = [true, false]
35
+ obj = TypeArrayTest.new
36
+ obj.flags = flags
37
+ obj.save!
38
+
39
+ obj = TypeArrayTest.find(obj.id)
40
+ expect(obj.flags).to eq flags
41
+ end
42
+
43
+ it "should be able to store and retrieve an array of basic objects" do
44
+ things = [1, "1234", {"key" => 4}]
45
+ obj = TypeArrayTest.new
46
+ obj.things = things
47
+ obj.save!
48
+
49
+ obj = TypeArrayTest.find(obj.id)
50
+ expect(obj.things).to eq things
51
+ end
52
+ end
@@ -0,0 +1,114 @@
1
+ require File.expand_path("../support", __FILE__)
2
+
3
+ require 'active_model'
4
+ require 'base64'
5
+
6
+ class SubTypeEncryptedTest < CouchbaseOrm::NestedDocument
7
+ attribute :name, :string
8
+ attribute :secret, :encrypted
9
+ attribute :secret2, :encrypted
10
+ end
11
+
12
+ class TypeEncryptedTest < CouchbaseOrm::Base
13
+ attribute :main, :nested, type: SubTypeEncryptedTest
14
+ attribute :others, :array, type: SubTypeEncryptedTest
15
+ attribute :secret, :encrypted
16
+ attribute :secret2, :encrypted
17
+ end
18
+
19
+ class SpecificAlgoTest < CouchbaseOrm::Base
20
+ attribute :secret, :encrypted, alg: "3DES"
21
+ end
22
+
23
+ describe CouchbaseOrm::Types::Encrypted do
24
+ # Generated with SecureRandom.bytes(256)
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
+ let(:base64_secret) { Base64.strict_encode64(the_secret) }
27
+
28
+ it "prefix attribute on serialization" do
29
+ obj = TypeEncryptedTest.new(secret: base64_secret, secret2: "a secret")
30
+ expect_serialized_secret(obj)
31
+ end
32
+
33
+ it "prefix attribute on nested objects" do
34
+ obj = TypeEncryptedTest.new(main: SubTypeEncryptedTest.new(secret: base64_secret, secret2: "a secret"))
35
+ expect_serialized_secret(obj.main)
36
+ end
37
+
38
+ it "prefix attribute on array objects" do
39
+ obj = TypeEncryptedTest.new(others: [SubTypeEncryptedTest.new(secret: base64_secret, secret2: "a secret")])
40
+ expect_serialized_secret(obj.others.first)
41
+ end
42
+
43
+ def expect_serialized_secret(obj)
44
+ expect(obj.send(:serialized_attributes)["encrypted$secret"]).to eq({alg:"CB_MOBILE_CUSTOM", ciphertext: base64_secret})
45
+ expect(obj.send(:serialized_attributes)).to_not have_key "secret"
46
+ expect(obj.send(:serialized_attributes)["encrypted$secret2"]).to eq({alg:"CB_MOBILE_CUSTOM", ciphertext: "a secret"})
47
+ expect(obj.send(:serialized_attributes)).to_not have_key "secret2"
48
+ expect(JSON.parse(obj.to_json)["secret"]).to eq base64_secret
49
+ expect(obj.as_json["secret"]).to eq base64_secret
50
+ expect(obj.as_json["secret2"]).to eq "a secret"
51
+ end
52
+
53
+ it "prefix with custom algo" do
54
+ obj = SpecificAlgoTest.new(secret: base64_secret)
55
+ expect(obj.send(:serialized_attributes)["encrypted$secret"]).to eq({alg:"3DES", ciphertext: base64_secret})
56
+ expect(obj.send(:serialized_attributes)).to_not include "secret"
57
+ end
58
+
59
+ it "decode encrypted attribute at reload" do
60
+ obj = TypeEncryptedTest.create!(
61
+ secret: base64_secret,
62
+ )
63
+ obj.save!
64
+ obj.reload
65
+ expect(obj.secret).to eq base64_secret
66
+ end
67
+
68
+ it "decode nested encrypted attribute at reload" do
69
+ obj = TypeEncryptedTest.create!(
70
+ main: SubTypeEncryptedTest.new(secret: base64_secret),
71
+ )
72
+ obj.save!
73
+ obj.reload
74
+ expect(obj.main.secret).to eq base64_secret
75
+ end
76
+
77
+ it "decode array encrypted attribute at reload" do
78
+ obj = TypeEncryptedTest.create!(
79
+ others: [SubTypeEncryptedTest.new(secret: base64_secret)]
80
+ )
81
+ obj.save!
82
+ obj.reload
83
+ expect(obj.others.first.secret).to eq base64_secret
84
+ end
85
+
86
+ it "decode encrypted attribute at load" do
87
+ obj = TypeEncryptedTest.create!(
88
+ secret: base64_secret,
89
+ )
90
+ obj.save!
91
+ obj = TypeEncryptedTest.find(obj.id)
92
+ expect(obj.secret).to eq base64_secret
93
+ end
94
+
95
+ it "decode nested encrypted attribute at load" do
96
+ obj = TypeEncryptedTest.create!(
97
+ main: SubTypeEncryptedTest.new(secret: base64_secret),
98
+ )
99
+ obj.save!
100
+ obj = TypeEncryptedTest.find(obj.id)
101
+
102
+ expect(obj.main.secret).to eq base64_secret
103
+ end
104
+
105
+ it "decode array encrypted attribute at load" do
106
+ obj = TypeEncryptedTest.create!(
107
+ others: [SubTypeEncryptedTest.new(secret: base64_secret)]
108
+ )
109
+ obj.save!
110
+ obj = TypeEncryptedTest.find(obj.id)
111
+
112
+ expect(obj.others.first.secret).to eq base64_secret
113
+ end
114
+ end