couchbase-orm 1.1.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +45 -0
- data/.gitignore +2 -0
- data/.travis.yml +3 -2
- data/CODEOWNERS +1 -0
- data/Gemfile +5 -3
- data/LICENSE +201 -24
- data/README.md +248 -35
- data/ci/run_couchbase.sh +22 -0
- data/couchbase-orm.gemspec +26 -20
- data/lib/couchbase-orm/active_record_compat.rb +92 -0
- data/lib/couchbase-orm/associations.rb +119 -0
- data/lib/couchbase-orm/base.rb +143 -166
- data/lib/couchbase-orm/changeable.rb +512 -0
- data/lib/couchbase-orm/connection.rb +28 -8
- data/lib/couchbase-orm/encrypt.rb +48 -0
- data/lib/couchbase-orm/error.rb +17 -2
- data/lib/couchbase-orm/inspectable.rb +37 -0
- data/lib/couchbase-orm/json_schema/json_validation_error.rb +13 -0
- data/lib/couchbase-orm/json_schema/loader.rb +47 -0
- data/lib/couchbase-orm/json_schema/validation.rb +18 -0
- data/lib/couchbase-orm/json_schema/validator.rb +45 -0
- data/lib/couchbase-orm/json_schema.rb +9 -0
- data/lib/couchbase-orm/json_transcoder.rb +27 -0
- data/lib/couchbase-orm/locale/en.yml +5 -0
- data/lib/couchbase-orm/n1ql.rb +133 -0
- data/lib/couchbase-orm/persistence.rb +61 -52
- data/lib/couchbase-orm/proxies/bucket_proxy.rb +36 -0
- data/lib/couchbase-orm/proxies/collection_proxy.rb +52 -0
- data/lib/couchbase-orm/proxies/n1ql_proxy.rb +40 -0
- data/lib/couchbase-orm/proxies/results_proxy.rb +23 -0
- data/lib/couchbase-orm/railtie.rb +6 -17
- data/lib/couchbase-orm/relation.rb +249 -0
- data/lib/couchbase-orm/strict_loading.rb +21 -0
- data/lib/couchbase-orm/timestamps/created.rb +20 -0
- data/lib/couchbase-orm/timestamps/updated.rb +21 -0
- data/lib/couchbase-orm/timestamps.rb +15 -0
- data/lib/couchbase-orm/types/array.rb +32 -0
- data/lib/couchbase-orm/types/date.rb +9 -0
- data/lib/couchbase-orm/types/date_time.rb +14 -0
- data/lib/couchbase-orm/types/encrypted.rb +17 -0
- data/lib/couchbase-orm/types/nested.rb +43 -0
- data/lib/couchbase-orm/types/timestamp.rb +18 -0
- data/lib/couchbase-orm/types.rb +20 -0
- data/lib/couchbase-orm/utilities/enum.rb +13 -1
- data/lib/couchbase-orm/utilities/has_many.rb +72 -36
- data/lib/couchbase-orm/utilities/ignored_properties.rb +15 -0
- data/lib/couchbase-orm/utilities/index.rb +18 -20
- data/lib/couchbase-orm/utilities/properties_always_exists_in_document.rb +16 -0
- data/lib/couchbase-orm/utilities/query_helper.rb +148 -0
- data/lib/couchbase-orm/utils.rb +25 -0
- data/lib/couchbase-orm/version.rb +1 -1
- data/lib/couchbase-orm/views.rb +38 -41
- data/lib/couchbase-orm.rb +44 -9
- data/lib/ext/query_n1ql.rb +124 -0
- data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +3 -2
- data/spec/associations_spec.rb +219 -50
- data/spec/base_spec.rb +296 -14
- data/spec/collection_proxy_spec.rb +29 -0
- data/spec/connection_spec.rb +27 -0
- data/spec/couchbase-orm/active_record_compat_spec.rb +24 -0
- data/spec/couchbase-orm/changeable_spec.rb +16 -0
- data/spec/couchbase-orm/json_schema/validation_spec.rb +23 -0
- data/spec/couchbase-orm/json_schema/validator_spec.rb +13 -0
- data/spec/couchbase-orm/timestamps_spec.rb +85 -0
- data/spec/couchbase-orm/timestamps_spec_models.rb +36 -0
- data/spec/empty-json-schema/.gitkeep +0 -0
- data/spec/enum_spec.rb +34 -0
- data/spec/has_many_spec.rb +101 -54
- data/spec/index_spec.rb +13 -9
- data/spec/json-schema/JsonSchemaBaseTest.json +19 -0
- data/spec/json-schema/entity_snakecase.json +20 -0
- data/spec/json-schema/loader_spec.rb +42 -0
- data/spec/json-schema/specific_path.json +20 -0
- data/spec/json_schema_spec.rb +178 -0
- data/spec/n1ql_spec.rb +193 -0
- data/spec/persistence_spec.rb +49 -9
- data/spec/relation_nested_spec.rb +88 -0
- data/spec/relation_spec.rb +430 -0
- data/spec/support.rb +16 -8
- data/spec/type_array_spec.rb +52 -0
- data/spec/type_encrypted_spec.rb +114 -0
- data/spec/type_nested_spec.rb +191 -0
- data/spec/type_spec.rb +317 -0
- data/spec/utilities/ignored_properties_spec.rb +20 -0
- data/spec/utilities/properties_always_exists_in_document_spec.rb +24 -0
- data/spec/views_spec.rb +32 -11
- metadata +193 -30
@@ -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
|