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
@@ -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