couchbase-orm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +34 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.md +131 -0
- data/Rakefile +75 -0
- data/couchbase-orm.gemspec +27 -0
- data/lib/couchbase-orm.rb +43 -0
- data/lib/couchbase-orm/associations.rb +87 -0
- data/lib/couchbase-orm/base.rb +254 -0
- data/lib/couchbase-orm/connection.rb +22 -0
- data/lib/couchbase-orm/error.rb +15 -0
- data/lib/couchbase-orm/id_generator.rb +30 -0
- data/lib/couchbase-orm/persistence.rb +240 -0
- data/lib/couchbase-orm/railtie.rb +96 -0
- data/lib/couchbase-orm/utilities/ensure_unique.rb +18 -0
- data/lib/couchbase-orm/utilities/enum.rb +49 -0
- data/lib/couchbase-orm/utilities/has_many.rb +73 -0
- data/lib/couchbase-orm/utilities/index.rb +117 -0
- data/lib/couchbase-orm/utilities/join.rb +68 -0
- data/lib/couchbase-orm/version.rb +5 -0
- data/lib/couchbase-orm/views.rb +148 -0
- data/lib/rails/generators/couchbase_orm/config/config_generator.rb +42 -0
- data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +18 -0
- data/lib/rails/generators/couchbase_orm_generator.rb +42 -0
- data/spec/associations_spec.rb +80 -0
- data/spec/base_spec.rb +72 -0
- data/spec/has_many_spec.rb +82 -0
- data/spec/id_generator_spec.rb +48 -0
- data/spec/index_spec.rb +73 -0
- data/spec/persistence_spec.rb +237 -0
- data/spec/support.rb +28 -0
- data/spec/views_spec.rb +73 -0
- metadata +184 -0
@@ -0,0 +1,254 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_support/hash_with_indifferent_access'
|
6
|
+
require 'couchbase-orm/error'
|
7
|
+
require 'couchbase-orm/views'
|
8
|
+
require 'couchbase-orm/persistence'
|
9
|
+
require 'couchbase-orm/associations'
|
10
|
+
require 'couchbase-orm/utilities/join'
|
11
|
+
require 'couchbase-orm/utilities/enum'
|
12
|
+
require 'couchbase-orm/utilities/index'
|
13
|
+
require 'couchbase-orm/utilities/has_many'
|
14
|
+
require 'couchbase-orm/utilities/ensure_unique'
|
15
|
+
|
16
|
+
|
17
|
+
module CouchbaseOrm
|
18
|
+
class Base
|
19
|
+
include ::ActiveModel::Model
|
20
|
+
include ::ActiveModel::Dirty
|
21
|
+
include ::ActiveModel::Serializers::JSON
|
22
|
+
|
23
|
+
extend ::ActiveModel::Callbacks
|
24
|
+
define_model_callbacks :initialize, :only => :after
|
25
|
+
define_model_callbacks :create, :destroy, :save, :update
|
26
|
+
|
27
|
+
include Persistence
|
28
|
+
include Associations
|
29
|
+
include Views
|
30
|
+
|
31
|
+
extend Join
|
32
|
+
extend Enum
|
33
|
+
extend EnsureUnique
|
34
|
+
extend HasMany
|
35
|
+
extend Index
|
36
|
+
|
37
|
+
|
38
|
+
Metadata = Struct.new(:key, :cas)
|
39
|
+
|
40
|
+
|
41
|
+
class << self
|
42
|
+
def connect(**options)
|
43
|
+
@bucket = ::Libcouchbase::Bucket.new(**options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def bucket=(bucket)
|
47
|
+
@bucket = bucket
|
48
|
+
end
|
49
|
+
|
50
|
+
def bucket
|
51
|
+
@bucket ||= Connection.bucket
|
52
|
+
end
|
53
|
+
|
54
|
+
at_exit do
|
55
|
+
# This will disconnect the database connection
|
56
|
+
@bucket = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def uuid_generator
|
60
|
+
@uuid_generator ||= IdGenerator
|
61
|
+
end
|
62
|
+
|
63
|
+
def uuid_generator=(generator)
|
64
|
+
@uuid_generator = generator
|
65
|
+
end
|
66
|
+
|
67
|
+
def attribute(*names, **options)
|
68
|
+
@attributes ||= {}
|
69
|
+
names.each do |name|
|
70
|
+
name = name.to_sym
|
71
|
+
|
72
|
+
@attributes[name] = options
|
73
|
+
next if self.instance_methods.include?(name)
|
74
|
+
|
75
|
+
define_method(name) do
|
76
|
+
read_attribute(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method(:"#{name}=") do |value|
|
80
|
+
value = yield(value) if block_given?
|
81
|
+
write_attribute(name, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
@attributes ||= {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def find(*ids, **options)
|
91
|
+
options[:extended] = true
|
92
|
+
options[:quiet] ||= false
|
93
|
+
|
94
|
+
ids = ids.flatten
|
95
|
+
records = bucket.get(*ids, **options)
|
96
|
+
|
97
|
+
records = records.is_a?(Array) ? records : [records]
|
98
|
+
records.map! { |record|
|
99
|
+
if record
|
100
|
+
self.new(record)
|
101
|
+
else
|
102
|
+
false
|
103
|
+
end
|
104
|
+
}
|
105
|
+
records.select! { |rec| rec }
|
106
|
+
ids.length > 1 ? records : records[0]
|
107
|
+
end
|
108
|
+
|
109
|
+
def find_by_id(*ids, **options)
|
110
|
+
options[:quiet] = true
|
111
|
+
find *ids, **options
|
112
|
+
end
|
113
|
+
alias_method :[], :find_by_id
|
114
|
+
|
115
|
+
def exists?(id)
|
116
|
+
!bucket.get(id, quiet: true).nil?
|
117
|
+
end
|
118
|
+
alias_method :has_key?, :exists?
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Add support for libcouchbase response objects
|
123
|
+
def initialize(model = nil, ignore_doc_type: false, **attributes)
|
124
|
+
@__metadata__ = Metadata.new
|
125
|
+
|
126
|
+
# Assign default values
|
127
|
+
@__attributes__ = ::ActiveSupport::HashWithIndifferentAccess.new({type: self.class.design_document})
|
128
|
+
self.class.attributes.each do |key, options|
|
129
|
+
default = options[:default]
|
130
|
+
if default.respond_to?(:call)
|
131
|
+
@__attributes__[key] = default.call
|
132
|
+
else
|
133
|
+
@__attributes__[key] = default
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if model
|
138
|
+
case model
|
139
|
+
when ::Libcouchbase::Response
|
140
|
+
doc = model.value || raise('empty response provided')
|
141
|
+
type = doc.delete(:type)
|
142
|
+
doc.delete(:id)
|
143
|
+
|
144
|
+
if type && !ignore_doc_type && type.to_s != self.class.design_document
|
145
|
+
raise "document type mismatch, #{type} != #{self.class.design_document}"
|
146
|
+
end
|
147
|
+
|
148
|
+
@__metadata__.key = model.key
|
149
|
+
@__metadata__.cas = model.cas
|
150
|
+
|
151
|
+
# This ensures that defaults are applied
|
152
|
+
super(**doc)
|
153
|
+
when CouchbaseOrm::Base
|
154
|
+
attributes = model.attributes
|
155
|
+
attributes.delete(:id)
|
156
|
+
super(**attributes)
|
157
|
+
else
|
158
|
+
super(**attributes.merge(Hash(model)))
|
159
|
+
end
|
160
|
+
else
|
161
|
+
super(**attributes)
|
162
|
+
end
|
163
|
+
|
164
|
+
yield self if block_given?
|
165
|
+
|
166
|
+
run_callbacks :initialize
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Document ID is a special case as it is not stored in the document
|
171
|
+
def id
|
172
|
+
@__metadata__.key || @id
|
173
|
+
end
|
174
|
+
|
175
|
+
def id=(value)
|
176
|
+
raise 'ID cannot be changed' if @__metadata__.cas
|
177
|
+
attribute_will_change!(:id)
|
178
|
+
@id = value.to_s
|
179
|
+
end
|
180
|
+
|
181
|
+
def read_attribute(attr_name)
|
182
|
+
@__attributes__[attr_name]
|
183
|
+
end
|
184
|
+
alias_method :[], :read_attribute
|
185
|
+
|
186
|
+
def write_attribute(attr_name, value)
|
187
|
+
unless value.nil?
|
188
|
+
coerce = self.class.attributes[attr_name][:type]
|
189
|
+
value = Kernel.send(coerce.to_s, value) if coerce
|
190
|
+
end
|
191
|
+
attribute_will_change!(attr_name) unless @__attributes__[attr_name] == value
|
192
|
+
@__attributes__[attr_name] = value
|
193
|
+
end
|
194
|
+
alias_method :[]=, :write_attribute
|
195
|
+
|
196
|
+
#
|
197
|
+
# Add support for Serialization:
|
198
|
+
# http://guides.rubyonrails.org/active_model_basics.html#serialization
|
199
|
+
#
|
200
|
+
|
201
|
+
def attributes
|
202
|
+
copy = @__attributes__.merge({id: id})
|
203
|
+
copy.delete(:type)
|
204
|
+
copy
|
205
|
+
end
|
206
|
+
|
207
|
+
def attributes=(attributes)
|
208
|
+
attributes.each do |key, value|
|
209
|
+
setter = :"#{key}="
|
210
|
+
send(setter, value) if respond_to?(setter)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
#
|
216
|
+
# Add support for comparisons
|
217
|
+
#
|
218
|
+
|
219
|
+
# Public: Allows for access to ActiveModel functionality.
|
220
|
+
#
|
221
|
+
# Returns self.
|
222
|
+
def to_model
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
# Public: Hashes identifying properties of the instance
|
227
|
+
#
|
228
|
+
# Ruby normally hashes an object to be used in comparisons. In our case
|
229
|
+
# we may have two techincally different objects referencing the same entity id.
|
230
|
+
#
|
231
|
+
# Returns a string representing the unique key.
|
232
|
+
def hash
|
233
|
+
"#{self.class.name}-#{self.id}-#{@__metadata__.cas}-#{@__attributes__.hash}".hash
|
234
|
+
end
|
235
|
+
|
236
|
+
# Public: Overrides eql? to use == in the comparison.
|
237
|
+
#
|
238
|
+
# other - Another object to compare to
|
239
|
+
#
|
240
|
+
# Returns a boolean.
|
241
|
+
def eql?(other)
|
242
|
+
self == other
|
243
|
+
end
|
244
|
+
|
245
|
+
# Public: Overrides == to compare via class and entity id.
|
246
|
+
#
|
247
|
+
# other - Another object to compare to
|
248
|
+
#
|
249
|
+
# Returns a boolean.
|
250
|
+
def ==(other)
|
251
|
+
hash == other.hash
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'libcouchbase'
|
4
|
+
|
5
|
+
module CouchbaseOrm
|
6
|
+
class Connection
|
7
|
+
@options = {}
|
8
|
+
class << self
|
9
|
+
attr_accessor :options
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.bucket
|
13
|
+
@bucket ||= ::Libcouchbase::Bucket.new(**@options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# This will disconnect the database connection,
|
17
|
+
# allowing the application to exit
|
18
|
+
at_exit do
|
19
|
+
@bucket = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
module CouchbaseOrm
|
4
|
+
class Error < ::StandardError
|
5
|
+
attr_reader :record
|
6
|
+
|
7
|
+
def initialize(message = nil, record = nil)
|
8
|
+
@record = record
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
class RecordInvalid < Error; end
|
13
|
+
class RecordExists < Error; end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'radix/base'
|
4
|
+
|
5
|
+
module CouchbaseOrm
|
6
|
+
class IdGenerator
|
7
|
+
# Using base 65 as a form of compression (reduced length of ID string)
|
8
|
+
# No escape characters are required to display these in a URL
|
9
|
+
B65 = ::Radix::Base.new(::Radix::BASE::B62 + ['-', '_', '~'])
|
10
|
+
B10 = ::Radix::Base.new(10)
|
11
|
+
|
12
|
+
# We don't really care about dates before this library was created
|
13
|
+
# This reduces the length of the ID significantly
|
14
|
+
Skip46Years = 1451649600 # 46.years.to_i
|
15
|
+
|
16
|
+
# Generate a unique, orderable, ID using minimal bytes
|
17
|
+
def self.next(model)
|
18
|
+
# We are unlikely to see a clash here
|
19
|
+
now = Time.now
|
20
|
+
time = (now.to_i - Skip46Years) * 1_000_000 + now.usec
|
21
|
+
|
22
|
+
# This makes it very very improbable that there will ever be an ID clash
|
23
|
+
# Distributed system safe!
|
24
|
+
prefix = time.to_s
|
25
|
+
tail = (rand(9999) + 1).to_s.rjust(4, '0')
|
26
|
+
|
27
|
+
"#{model.class.design_document}-#{Radix.convert("#{prefix}#{tail}", B10, B65)}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'active_support/hash_with_indifferent_access'
|
5
|
+
|
6
|
+
module CouchbaseOrm
|
7
|
+
module Persistence
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def create(attributes = nil, &block)
|
13
|
+
if attributes.is_a?(Array)
|
14
|
+
attributes.collect { |attr| create(attr, &block) }
|
15
|
+
else
|
16
|
+
instance = new(attributes, &block)
|
17
|
+
instance.save
|
18
|
+
instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def create!(attributes = nil, &block)
|
23
|
+
if attributes.is_a?(Array)
|
24
|
+
attributes.collect { |attr| create!(attr, &block) }
|
25
|
+
else
|
26
|
+
instance = new(attributes, &block)
|
27
|
+
instance.save!
|
28
|
+
instance
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Raise an error if validation failed.
|
33
|
+
def fail_validate!(document)
|
34
|
+
raise Error::RecordInvalid.new("Failed to save the record", document)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Allow classes to overwrite the default document name
|
38
|
+
# extend ActiveModel::Naming (included by ActiveModel::Model)
|
39
|
+
def design_document(name = nil)
|
40
|
+
return @design_document unless name
|
41
|
+
@design_document = name.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set a default design document
|
45
|
+
def inherited(child)
|
46
|
+
super
|
47
|
+
child.instance_eval do
|
48
|
+
@design_document = child.name.underscore
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Returns true if this object hasn't been saved yet -- that is, a record
|
55
|
+
# for the object doesn't exist in the database yet; otherwise, returns false.
|
56
|
+
def new_record?
|
57
|
+
@__metadata__.cas.nil? && @__metadata__.key.nil?
|
58
|
+
end
|
59
|
+
alias_method :new?, :new_record?
|
60
|
+
|
61
|
+
# Returns true if this object has been destroyed, otherwise returns false.
|
62
|
+
def destroyed?
|
63
|
+
!!(@__metadata__.cas && @__metadata__.key.nil?)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if the record is persisted, i.e. it's not a new record and it was
|
67
|
+
# not destroyed, otherwise returns false.
|
68
|
+
def persisted?
|
69
|
+
# Changed? is provided by ActiveModel::Dirty
|
70
|
+
!!@__metadata__.key
|
71
|
+
end
|
72
|
+
alias_method :exists?, :persisted?
|
73
|
+
|
74
|
+
# Saves the model.
|
75
|
+
#
|
76
|
+
# If the model is new, a record gets created in the database, otherwise
|
77
|
+
# the existing record gets updated.
|
78
|
+
def save(**options)
|
79
|
+
raise "Cannot save a destroyed document!" if destroyed?
|
80
|
+
self.new_record? ? _create_record(**options) : _update_record(**options)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Saves the model.
|
84
|
+
#
|
85
|
+
# If the model is new, a record gets created in the database, otherwise
|
86
|
+
# the existing record gets updated.
|
87
|
+
#
|
88
|
+
# By default, #save! always runs validations. If any of them fail
|
89
|
+
# CouchbaseOrm::Error::RecordInvalid gets raised, and the record won't be saved.
|
90
|
+
def save!
|
91
|
+
self.class.fail_validate!(self) unless self.save
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Deletes the record in the database and freezes this instance to
|
96
|
+
# reflect that no changes should be made (since they can't be
|
97
|
+
# persisted). Returns the frozen instance.
|
98
|
+
#
|
99
|
+
# The record is simply removed, no callbacks are executed.
|
100
|
+
def delete(with_cas: false, **options)
|
101
|
+
options[:cas] = @__metadata__.cas if with_cas
|
102
|
+
self.class.bucket.delete(@__metadata__.key, options)
|
103
|
+
|
104
|
+
@__metadata__.key = nil
|
105
|
+
@id = nil
|
106
|
+
|
107
|
+
clear_changes_information
|
108
|
+
self.freeze
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Deletes the record in the database and freezes this instance to reflect
|
113
|
+
# that no changes should be made (since they can't be persisted).
|
114
|
+
#
|
115
|
+
# There's a series of callbacks associated with #destroy.
|
116
|
+
def destroy(with_cas: false, **options)
|
117
|
+
return self if destroyed?
|
118
|
+
raise 'model not persisted' unless persisted?
|
119
|
+
|
120
|
+
run_callbacks :destroy do
|
121
|
+
destroy_associations!
|
122
|
+
|
123
|
+
options[:cas] = @__metadata__.cas if with_cas
|
124
|
+
self.class.bucket.delete(@__metadata__.key, options)
|
125
|
+
|
126
|
+
@__metadata__.key = nil
|
127
|
+
@id = nil
|
128
|
+
|
129
|
+
clear_changes_information
|
130
|
+
freeze
|
131
|
+
end
|
132
|
+
end
|
133
|
+
alias_method :destroy!, :destroy
|
134
|
+
|
135
|
+
# Updates a single attribute and saves the record.
|
136
|
+
# This is especially useful for boolean flags on existing records. Also note that
|
137
|
+
#
|
138
|
+
# * Validation is skipped.
|
139
|
+
# * \Callbacks are invoked.
|
140
|
+
def update_attribute(name, value)
|
141
|
+
public_send(:"#{name}=", value)
|
142
|
+
changed? ? save(validate: false) : true
|
143
|
+
end
|
144
|
+
|
145
|
+
# Updates the attributes of the model from the passed-in hash and saves the
|
146
|
+
# record. If the object is invalid, the saving will fail and false will be returned.
|
147
|
+
def update(hash)
|
148
|
+
assign_attributes(hash)
|
149
|
+
save
|
150
|
+
end
|
151
|
+
alias_method :update_attributes, :update
|
152
|
+
|
153
|
+
# Updates its receiver just like #update but calls #save! instead
|
154
|
+
# of +save+, so an exception is raised if the record is invalid and saving will fail.
|
155
|
+
def update!(hash)
|
156
|
+
assign_attributes(hash) # Assign attributes is provided by ActiveModel::AttributeAssignment
|
157
|
+
save!
|
158
|
+
end
|
159
|
+
alias_method :update_attributes!, :update!
|
160
|
+
|
161
|
+
# Reloads the record from the database.
|
162
|
+
#
|
163
|
+
# This method finds record by its key and modifies the receiver in-place:
|
164
|
+
def reload
|
165
|
+
key = @__metadata__.key
|
166
|
+
raise "unable to reload, model not persisted" unless key
|
167
|
+
|
168
|
+
resp = self.class.bucket.get(key, quiet: false, extended: true)
|
169
|
+
@__attributes__ = ::ActiveSupport::HashWithIndifferentAccess.new(resp.value)
|
170
|
+
@__metadata__.key = resp.key
|
171
|
+
@__metadata__.cas = resp.cas
|
172
|
+
|
173
|
+
reset_associations
|
174
|
+
clear_changes_information
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
# Updates the TTL of the document
|
179
|
+
def touch(**options)
|
180
|
+
res = self.class.bucket.touch(@__metadata__.key, async: false, **options)
|
181
|
+
@__metadata__.cas = resp.cas
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
protected
|
187
|
+
|
188
|
+
|
189
|
+
def _update_record(with_cas: false, **options)
|
190
|
+
return false unless perform_validations(options)
|
191
|
+
return true unless changed?
|
192
|
+
|
193
|
+
run_callbacks :update do
|
194
|
+
run_callbacks :save do
|
195
|
+
# Ensure the type is set
|
196
|
+
@__attributes__[:type] = self.class.design_document
|
197
|
+
@__attributes__.delete(:id)
|
198
|
+
|
199
|
+
_id = @__metadata__.key
|
200
|
+
options[:cas] = @__metadata__.cas if with_cas
|
201
|
+
resp = self.class.bucket.replace(_id, @__attributes__, **options)
|
202
|
+
|
203
|
+
# Ensure the model is up to date
|
204
|
+
@__metadata__.key = resp.key
|
205
|
+
@__metadata__.cas = resp.cas
|
206
|
+
|
207
|
+
clear_changes_information
|
208
|
+
true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def _create_record(**options)
|
214
|
+
return false unless perform_validations(options)
|
215
|
+
|
216
|
+
run_callbacks :create do
|
217
|
+
run_callbacks :save do
|
218
|
+
# Ensure the type is set
|
219
|
+
@__attributes__[:type] = self.class.design_document
|
220
|
+
@__attributes__.delete(:id)
|
221
|
+
|
222
|
+
_id = @id || self.class.uuid_generator.next(self)
|
223
|
+
resp = self.class.bucket.add(_id, @__attributes__, **options)
|
224
|
+
|
225
|
+
# Ensure the model is up to date
|
226
|
+
@__metadata__.key = resp.key
|
227
|
+
@__metadata__.cas = resp.cas
|
228
|
+
|
229
|
+
clear_changes_information
|
230
|
+
true
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def perform_validations(options = {})
|
236
|
+
return valid? if options[:validate] != false
|
237
|
+
true
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|