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,512 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module CouchbaseOrm
6
+ # Defines behavior for dirty tracking.
7
+ module Changeable
8
+ extend ActiveSupport::Concern
9
+
10
+ # Get the changed attributes for the document.
11
+ #
12
+ # @example Get the changed attributes.
13
+ # model.changed
14
+ #
15
+ # @return [ Array<String> ] The changed attributes.
16
+ def changed
17
+ changed_attributes.keys.select { |attr| attribute_change(attr) }
18
+ end
19
+
20
+ # Has the document changed?
21
+ #
22
+ # @example Has the document changed?
23
+ # model.changed?
24
+ #
25
+ # @return [ true | false ] If the document is changed.
26
+ def changed?
27
+ changes.values.any? { |val| val } || children_changed?
28
+ end
29
+
30
+ def _children
31
+ attributes.select { |name, _value| self.class.type_for_attribute(name) == CouchbaseOrm::NestedDocument }
32
+ end
33
+
34
+ # Have any children (embedded documents) of this document changed?
35
+ #
36
+ # @note This intentionally only considers children and not descendants.
37
+ #
38
+ # @return [ true | false ] If any children have changed.
39
+ def children_changed?
40
+ _children.any?(&:changed?)
41
+ end
42
+
43
+ # Get the attribute changes.
44
+ #
45
+ # @example Get the attribute changes.
46
+ # model.changed_attributes
47
+ #
48
+ # @return [ Hash<String, Object> ] The attribute changes.
49
+ def changed_attributes
50
+ @changed_attributes ||= {}
51
+ end
52
+
53
+ # Get all the changes for the document.
54
+ #
55
+ # @example Get all the changes.
56
+ # model.changes
57
+ #
58
+ # @return [ Hash<String, Array<Object, Object> ] The changes.
59
+ def changes
60
+ changed.each_with_object({}) do |attr, changes|
61
+ change = attribute_change(attr)
62
+ changes[attr] = change if change
63
+ end.with_indifferent_access
64
+ end
65
+
66
+ # Call this method after save, so the changes can be properly switched.
67
+ #
68
+ # This will unset the memoized children array, set new record flag to
69
+ # false, set the document as validated, and move the dirty changes.
70
+ #
71
+ # @example Move the changes to previous.
72
+ # person.move_changes
73
+ def move_changes
74
+ @changes_before_last_save = @previous_changes
75
+ @previous_changes = changes
76
+ @attributes_before_last_save = @previous_attributes
77
+ @previous_attributes = attributes.dup
78
+ changed_attributes.clear
79
+ end
80
+
81
+ def changes_applied
82
+ move_changes
83
+ super
84
+ end
85
+
86
+ def reset_object!
87
+ # @attributes = attributes
88
+ # @attributes_before_type_cast = @attributes.dup
89
+ @changed_attributes = {}
90
+ @previous_changes = {}
91
+ @previous_attributes = {}
92
+ end
93
+ # for AR compatibility
94
+ # TODO add coverage and move it in AR compat module
95
+ alias clear_changes_information reset_object!
96
+
97
+ # Get the previous changes on the document.
98
+ #
99
+ # @example Get the previous changes.
100
+ # model.previous_changes
101
+ #
102
+ # @return [ Hash<String, Array<Object, Object> ] The previous changes.
103
+ def previous_changes
104
+ @previous_changes ||= {}
105
+ end
106
+
107
+ # Gets all the new values for each of the changed fields, to be passed to
108
+ # a CouchbaseOrm $set modifier.
109
+ #
110
+ # @example Get the setters for the atomic updates.
111
+ # person = Person.new(:title => "Sir")
112
+ # person.title = "Madam"
113
+ # person.setters # returns { "title" => "Madam" }
114
+ #
115
+ # @return [ Hash ] A +Hash+ of atomic setters.
116
+ def setters
117
+ mods = {}
118
+ changes.each_pair do |name, changes|
119
+ next unless changes
120
+
121
+ old, new = changes
122
+ field = fields[name]
123
+ key = atomic_attribute_name(name)
124
+ if field&.resizable?
125
+ field.add_atomic_changes(self, name, key, mods, new, old)
126
+ else
127
+ mods[key] = new unless atomic_unsets.include?(key)
128
+ end
129
+ end
130
+ mods
131
+ end
132
+
133
+ # Returns the original value of an attribute before the last save.
134
+ #
135
+ # This method is useful in after callbacks to get the original value of
136
+ # an attribute before the save that triggered the callbacks to run.
137
+ #
138
+ # @param [ Symbol | String ] attr The name of the attribute.
139
+ #
140
+ # @return [ Object ] Value of the attribute before the last save.
141
+ def attribute_before_last_save(attr)
142
+ attributes_before_last_save[attr]
143
+ end
144
+
145
+ # Returns the change to an attribute during the last save.
146
+ #
147
+ # @param [ Symbol | String ] attr The name of the attribute.
148
+ #
149
+ # @return [ Array<Object> | nil ] If the attribute was changed, returns
150
+ # an array containing the original value and the saved value, otherwise nil.
151
+ def saved_change_to_attribute(attr)
152
+ previous_changes[attr]
153
+ end
154
+
155
+ # Returns whether this attribute changed during the last save.
156
+ #
157
+ # This method is useful in after callbacks, to see the change
158
+ # in an attribute during the save that triggered the callbacks to run.
159
+ #
160
+ # @param [ String ] attr The name of the attribute.
161
+ # @param [ Object ] from The object the attribute was changed from (optional).
162
+ # @param [ Object ] to The object the attribute was changed to (optional).
163
+ #
164
+ # @return [ true | false ] Whether the attribute has changed during the last save.
165
+ def saved_change_to_attribute?(attr, from: Utils::PLACEHOLDER, to: Utils::PLACEHOLDER)
166
+ changes = saved_change_to_attribute(attr)
167
+ return false unless changes.is_a?(Array)
168
+
169
+ return true if Utils.placeholder?(from) && Utils.placeholder?(to)
170
+ return changes.first == from if Utils.placeholder?(to)
171
+ return changes.last == to if Utils.placeholder?(from)
172
+
173
+ changes.first == from && changes.last == to
174
+ end
175
+
176
+ # Returns whether this attribute change the next time we save.
177
+ #
178
+ # This method is useful in validations and before callbacks to determine
179
+ # if the next call to save will change a particular attribute.
180
+ #
181
+ # @param [ String ] attr The name of the attribute.
182
+ # @param **kwargs The optional keyword arguments.
183
+ #
184
+ # @option **kwargs [ Object ] :from The object the attribute was changed from.
185
+ # @option **kwargs [ Object ] :to The object the attribute was changed to.
186
+ #
187
+ # @return [ true | false ] Whether the attribute change the next time we save.
188
+ def will_save_change_to_attribute?(attr, **kwargs)
189
+ attribute_changed?(attr, **kwargs)
190
+ end
191
+
192
+ private
193
+
194
+ # Get attributes of the document before the document was saved.
195
+ #
196
+ # @return [ Hash ] Previous attributes
197
+ def previous_attributes
198
+ @previous_attributes ||= {}
199
+ end
200
+
201
+ def changes_before_last_save
202
+ @changes_before_last_save ||= {}
203
+ end
204
+
205
+ def attributes_before_last_save
206
+ @attributes_before_last_save ||= {}
207
+ end
208
+
209
+ # Get the old and new value for the provided attribute.
210
+ #
211
+ # @example Get the attribute change.
212
+ # model.attribute_change("name")
213
+ #
214
+ # @param [ String ] attr The name of the attribute.
215
+ #
216
+ # @return [ Array<Object> ] The old and new values.
217
+ def attribute_change(attr)
218
+ changed_attributes[attr] if attribute_changed?(attr)
219
+ end
220
+
221
+ # A class for representing the default value that an attribute was changed
222
+ # from or to.
223
+ #
224
+ # @api private
225
+ class Anything
226
+ # `Anything` objects are always equal to everything. This simplifies
227
+ # the logic for asking whether an attribute has changed or not. If the
228
+ # `from` or `to` value is a `Anything` (because it was not
229
+ # explicitly given), any comparison with it will suggest the value has
230
+ # not changed.
231
+ #
232
+ # @param [ Object ] _other The object being compared with this object.
233
+ #
234
+ # @return [ true ] Always returns true.
235
+ def ==(_other)
236
+ true
237
+ end
238
+ end
239
+
240
+ # a singleton object to represent an optional `to` or `from` value
241
+ # that was not explicitly provided to #attribute_changed?
242
+ ATTRIBUTE_UNCHANGED = Anything.new
243
+
244
+ # Determine if a specific attribute has changed.
245
+ #
246
+ # @example Has the attribute changed?
247
+ # model.attribute_changed?("name")
248
+ #
249
+ # @param [ String ] attr The name of the attribute.
250
+ # @param [ Object ] from The object the attribute was changed from (optional).
251
+ # @param [ Object ] to The object the attribute was changed to (optional).
252
+ #
253
+ # @return [ true | false ] Whether the attribute has changed.
254
+ def attribute_changed?(attr, from: ATTRIBUTE_UNCHANGED, to: ATTRIBUTE_UNCHANGED)
255
+ return false unless changed_attributes.key?(attr)
256
+ return false if changed_attributes[attr] == attributes[attr]
257
+ return false if from != changed_attributes[attr]
258
+ return false if to != attributes[attr]
259
+
260
+ true
261
+ end
262
+
263
+ # Get whether or not the field has a different value from the default.
264
+ #
265
+ # @example Is the field different from the default?
266
+ # model.attribute_changed_from_default?
267
+ #
268
+ # @param [ String ] attr The name of the attribute.
269
+ #
270
+ # @return [ true | false ] If the attribute differs.
271
+ def attribute_changed_from_default?(attr)
272
+ return false unless (field = fields[attr])
273
+
274
+ attributes[attr] != field.eval_default(self)
275
+ end
276
+
277
+ # Get the previous value for the attribute.
278
+ #
279
+ # @example Get the previous value.
280
+ # model.attribute_was("name")
281
+ #
282
+ # @param [ String ] attr The attribute name.
283
+ def attribute_was(attr)
284
+ attribute_changed?(attr) ? changed_attributes[attr].first : attributes[attr]
285
+ end
286
+
287
+ # Get the previous attribute value that was changed
288
+ # before the document was saved.
289
+ #
290
+ # It the document has not been saved yet, or was just loaded from database,
291
+ # this method returns nil for all attributes.
292
+ #
293
+ # @param [ String ] attr The attribute name.
294
+ #
295
+ # @return [ Object | nil ] Attribute value before the document was saved,
296
+ # or nil if the document has not been saved yet.
297
+ def attribute_previously_was(attr)
298
+ if previous_changes.key?(attr)
299
+ previous_changes[attr].first
300
+ else
301
+ previous_attributes[attr]
302
+ end
303
+ end
304
+
305
+ # Flag an attribute as going to change.
306
+ #
307
+ # @example Flag the attribute.
308
+ # model.attribute_will_change!("name")
309
+ #
310
+ # @param [ String ] attr The name of the attribute.
311
+ #
312
+ # @return [ Object ] The old value.
313
+ def attribute_will_change!(attr)
314
+ return if changed_attributes.key?(attr)
315
+
316
+ changed_attributes[attr] = attributes[attr]&.__deep_copy__
317
+ end
318
+
319
+ # Set the attribute back to its old value.
320
+ #
321
+ # @example Reset the attribute.
322
+ # model.reset_attribute!("name")
323
+ #
324
+ # @param [ String ] attr The name of the attribute.
325
+ #
326
+ # @return [ Object ] The old value.
327
+ def reset_attribute!(attr)
328
+ attributes[attr] = changed_attributes.delete(attr) if attribute_changed?(attr)
329
+ end
330
+
331
+ def reset_attribute_to_default!(attr)
332
+ if (field = fields[attr])
333
+ __send__("#{attr}=", field.eval_default(self))
334
+ else
335
+ __send__("#{attr}=", nil)
336
+ end
337
+ end
338
+
339
+ def reset_attributes_before_type_cast
340
+ @attributes_before_type_cast = @attributes.dup
341
+ end
342
+
343
+ # Class-level methods for changeable objects.
344
+ module ClassMethods
345
+ private
346
+
347
+ # Generate all the dirty methods needed for the attribute.
348
+ #
349
+ # @example Generate the dirty methods.
350
+ # Model.create_dirty_methods("name", "name")
351
+ #
352
+ # @param [ String ] name The name of the field.
353
+ # @param [ String ] meth The name of the accessor.
354
+ #
355
+ # @return [ Module ] The fields module.
356
+ def create_dirty_methods(name, meth)
357
+ create_dirty_change_accessor(name, meth)
358
+ create_dirty_change_check(name, meth)
359
+ create_dirty_change_flag(name, meth)
360
+ create_dirty_default_change_check(name, meth)
361
+ create_dirty_previous_value_accessor(name, meth)
362
+ create_dirty_reset(name, meth)
363
+ create_dirty_reset_to_default(name, meth)
364
+ create_dirty_previously_changed?(name, meth)
365
+ create_dirty_previous_change(name, meth)
366
+ end
367
+
368
+ def create_setters(name)
369
+ define_method("#{name}=") do |new_attribute_value|
370
+ previous_value = attributes[name.to_s]
371
+ ret = super(new_attribute_value)
372
+ if previous_value != attributes[name.to_s]
373
+ changed_attributes.merge!(Hash[name, [previous_value, attributes[name.to_s]]])
374
+ end
375
+ ret
376
+ end
377
+ end
378
+
379
+ # Creates the dirty change accessor.
380
+ #
381
+ # @example Create the accessor.
382
+ # Model.create_dirty_change_accessor("name", "alias")
383
+ #
384
+ # @param [ String ] name The attribute name.
385
+ # @param [ String ] meth The name of the accessor.
386
+ def create_dirty_change_accessor(name, meth)
387
+ define_method("#{meth}_change") do
388
+ attribute_change(name)
389
+ end
390
+ end
391
+
392
+ # Creates the dirty change check.
393
+ #
394
+ # @example Create the check.
395
+ # Model.create_dirty_change_check("name", "alias")
396
+ #
397
+ # @param [ String ] name The attribute name.
398
+ # @param [ String ] meth The name of the accessor.
399
+ def create_dirty_change_check(name, meth)
400
+ define_method("#{meth}_changed?") do |**kwargs|
401
+ attribute_changed?(name, **kwargs)
402
+ end
403
+ define_method("will_save_change_to_#{meth}?") do |**kwargs|
404
+ will_save_change_to_attribute?(name, **kwargs)
405
+ end
406
+ end
407
+
408
+ # Creates the dirty default change check.
409
+ #
410
+ # @example Create the check.
411
+ # Model.create_dirty_default_change_check("name", "alias")
412
+ #
413
+ # @param [ String ] name The attribute name.
414
+ # @param [ String ] meth The name of the accessor.
415
+ def create_dirty_default_change_check(name, meth)
416
+ define_method("#{meth}_changed_from_default?") do
417
+ attribute_changed_from_default?(name)
418
+ end
419
+ end
420
+
421
+ # Creates the dirty change previous value accessors.
422
+ #
423
+ # @example Create the accessor.
424
+ # Model.create_dirty_previous_value_accessor("name", "alias")
425
+ #
426
+ # @param [ String ] name The attribute name.
427
+ # @param [ String ] meth The name of the accessor.
428
+ def create_dirty_previous_value_accessor(name, meth)
429
+ define_method("#{meth}_was") do
430
+ attribute_was(name)
431
+ end
432
+ define_method("#{meth}_previously_was") do
433
+ attribute_previously_was(name)
434
+ end
435
+ define_method("#{meth}_before_last_save") do
436
+ attribute_before_last_save(name)
437
+ end
438
+ define_method("saved_change_to_#{meth}") do
439
+ saved_change_to_attribute(name)
440
+ end
441
+ define_method("saved_change_to_#{meth}?") do |**kwargs|
442
+ saved_change_to_attribute?(name, **kwargs)
443
+ end
444
+ end
445
+
446
+ # Creates the dirty change flag.
447
+ #
448
+ # @example Create the flag.
449
+ # Model.create_dirty_change_flag("name", "alias")
450
+ #
451
+ # @param [ String ] name The attribute name.
452
+ # @param [ String ] meth The name of the accessor.
453
+ def create_dirty_change_flag(name, meth)
454
+ define_method("#{meth}_will_change!") do
455
+ attribute_will_change!(name)
456
+ end
457
+ end
458
+
459
+ # Creates the dirty change reset.
460
+ #
461
+ # @example Create the reset.
462
+ # Model.create_dirty_reset("name", "alias")
463
+ #
464
+ # @param [ String ] name The attribute name.
465
+ # @param [ String ] meth The name of the accessor.
466
+ def create_dirty_reset(name, meth)
467
+ define_method("reset_#{meth}!") do
468
+ reset_attribute!(name)
469
+ end
470
+ end
471
+
472
+ # Creates the dirty change reset to default.
473
+ #
474
+ # @example Create the reset.
475
+ # Model.create_dirty_reset_to_default("name", "alias")
476
+ #
477
+ # @param [ String ] name The attribute name.
478
+ # @param [ String ] meth The name of the accessor.
479
+ def create_dirty_reset_to_default(name, meth)
480
+ define_method("reset_#{meth}_to_default!") do
481
+ reset_attribute_to_default!(name)
482
+ end
483
+ end
484
+
485
+ # Creates the dirty change check.
486
+ #
487
+ # @example Create the dirty change check.
488
+ # Model.create_dirty_previously_changed?("name", "alias")
489
+ #
490
+ # @param [ String ] name The attribute name.
491
+ # @param [ String ] meth The name of the accessor.
492
+ def create_dirty_previously_changed?(name, meth)
493
+ define_method("#{meth}_previously_changed?") do
494
+ previous_changes.key?(name)
495
+ end
496
+ end
497
+
498
+ # Creates the dirty change accessor.
499
+ #
500
+ # @example Create the dirty change accessor.
501
+ # Model.create_dirty_previous_change("name", "alias")
502
+ #
503
+ # @param [ String ] name The attribute name.
504
+ # @param [ String ] meth The name of the accessor.
505
+ def create_dirty_previous_change(name, meth)
506
+ define_method("#{meth}_previous_change") do
507
+ previous_changes[name]
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end
@@ -1,16 +1,36 @@
1
- # frozen_string_literal: true, encoding: ASCII-8BIT
2
-
3
- require 'libcouchbase'
1
+ require 'couchbase'
4
2
 
5
3
  module CouchbaseOrm
6
4
  class Connection
7
- @options = {}
8
- class << self
9
- attr_accessor :options
5
+ @@config = nil
6
+ def self.config
7
+ @@config || {
8
+ :connection_string => "couchbase://#{ENV['COUCHBASE_HOST'] || '127.0.0.1'}",
9
+ :username => ENV['COUCHBASE_USER'],
10
+ :password => ENV['COUCHBASE_PASSWORD'],
11
+ :bucket => ENV['COUCHBASE_BUCKET']
12
+ }
13
+ end
14
+
15
+ def self.config=(config)
16
+ @@config = config
17
+ end
18
+
19
+ def self.cluster
20
+ @cluster ||= begin
21
+ cb_config = Couchbase::Configuration.new
22
+ cb_config.connection_string = config[:connection_string] || raise(CouchbaseOrm::Error, 'Missing CouchbaseOrm connection string')
23
+ cb_config.username = config[:username] || raise(CouchbaseOrm::Error, 'Missing CouchbaseOrm username')
24
+ cb_config.password = config[:password] || raise(CouchbaseOrm::Error, 'Missing CouchbaseOrm password')
25
+ Couchbase::Cluster.connect(cb_config)
26
+ end
10
27
  end
11
28
 
12
29
  def self.bucket
13
- @bucket ||= ::Libcouchbase::Bucket.new(**@options)
30
+ @bucket ||= begin
31
+ bucket_name = config[:bucket] || raise(CouchbaseOrm::Error, 'Missing CouchbaseOrm bucket name')
32
+ cluster.bucket(bucket_name)
33
+ end
14
34
  end
15
35
  end
16
- end
36
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true, encoding: ASCII-8BIT
2
+
3
+ module CouchbaseOrm
4
+ module Encrypt
5
+ def encode_encrypted_attributes
6
+ attributes.map do |key, value|
7
+ type = self.class.attribute_types[key.to_s]
8
+ if type.is_a?(CouchbaseOrm::Types::Encrypted)
9
+ next unless value
10
+ raise "Can not serialize value #{value} of type '#{value.class}' for Tanker encrypted attribute" unless value.is_a?(String)
11
+ ["encrypted$#{key}", {
12
+ alg: type.alg,
13
+ ciphertext: value
14
+ }]
15
+ else
16
+ [key,value]
17
+ end
18
+ end.compact.to_h
19
+ end
20
+
21
+ def decode_encrypted_attributes(attributes)
22
+ attributes.map do |key, value|
23
+ key = key.to_s
24
+ if key.start_with?('encrypted$')
25
+ key = key.gsub('encrypted$', '')
26
+ value = value.with_indifferent_access[:ciphertext]
27
+ end
28
+ [key, value]
29
+ end.to_h
30
+ end
31
+
32
+
33
+ def to_json(*args, **kwargs)
34
+ as_json.to_json(*args, **kwargs)
35
+ end
36
+
37
+ def as_json(*args, **kwargs)
38
+ super(*args, **kwargs).map do |key, value|
39
+ type = self.class.attribute_types[key.to_s]
40
+ if type.is_a?(CouchbaseOrm::Types::Encrypted) && value
41
+ raise "Can not serialize value #{value} of type '#{value.class}' for encrypted attribute" unless value.is_a?(String)
42
+ end
43
+ [key, value]
44
+ end.to_h.with_indifferent_access
45
+ end
46
+
47
+ end
48
+ end
@@ -3,13 +3,28 @@
3
3
  module CouchbaseOrm
4
4
  class Error < ::StandardError
5
5
  attr_reader :record
6
-
6
+
7
7
  def initialize(message = nil, record = nil)
8
8
  @record = record
9
9
  super(message)
10
10
  end
11
11
 
12
- class RecordInvalid < Error; end
12
+ class RecordInvalid < Error
13
+ def initialize(message = nil, record = nil)
14
+ if record
15
+ errors = record.errors.full_messages.join(", ")
16
+ message = I18n.t(
17
+ :"couchbase.#{record.class.design_document}.errors.messages.record_invalid",
18
+ errors: errors,
19
+ default: :"couchbase.errors.messages.record_invalid"
20
+ )
21
+ end
22
+ super(message, record)
23
+ end
24
+ end
25
+ class TypeMismatchError < Error; end
13
26
  class RecordExists < Error; end
27
+ class CouchbaseOrm::Error::EmptyNotAllowed < Error; end
14
28
  end
29
+ class StrictLoadingViolationError < Error; end
15
30
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CouchbaseOrm
4
+
5
+ # Contains the behavior around inspecting documents via inspect.
6
+ module Inspectable
7
+
8
+ # Returns the class name plus its attributes.
9
+ #
10
+ # @example Inspect the document.
11
+ # person.inspect
12
+ #
13
+ # @return [ String ] A nice pretty string to look at.
14
+ def inspect
15
+ inspection = []
16
+ inspection.concat(inspect_attributes)
17
+ "#<#{self.class.name} id: #{respond_to?(:id)? id.inspect : 'no id'}, #{inspection * ', '}>"
18
+ end
19
+
20
+ private
21
+
22
+ # Get an array of inspected fields for the document.
23
+ #
24
+ # @api private
25
+ #
26
+ # @example Inspect the defined fields.
27
+ # document.inspect_attributes
28
+ #
29
+ # @return [ String ] An array of pretty printed field values.
30
+ def inspect_attributes
31
+ attributes.map do |name, value|
32
+ next if name.to_s == "id"
33
+ "#{name}: #{value.inspect}"
34
+ end.compact
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CouchbaseOrm
4
+ module JsonSchema
5
+ class JsonValidationError < StandardError
6
+
7
+ def initialize(class_name, errors)
8
+ super("[COUCHBASEORM]: Invalid document #{class_name} with errors : #{errors}")
9
+ end
10
+
11
+ end
12
+ end
13
+ end