couchbase-orm 1.1.1 → 2.0.2
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.
- 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,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
|
-
|
2
|
-
|
3
|
-
require 'libcouchbase'
|
1
|
+
require 'couchbase'
|
4
2
|
|
5
3
|
module CouchbaseOrm
|
6
4
|
class Connection
|
7
|
-
|
8
|
-
|
9
|
-
|
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 ||=
|
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
|
data/lib/couchbase-orm/error.rb
CHANGED
@@ -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
|
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
|