couchbase-orm 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/README.md +237 -31
- 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 +192 -29
@@ -32,6 +32,8 @@ module CouchbaseOrm
|
|
32
32
|
val = if options[:polymorphic]
|
33
33
|
::CouchbaseOrm.try_load(self.send(ref))
|
34
34
|
else
|
35
|
+
raise CouchbaseOrm::StrictLoadingViolationError, "#{self.class} is marked as strict_loading and #{assoc} cannot be lazily loaded." if strict_loading?
|
36
|
+
|
35
37
|
assoc.constantize.find(self.send(ref), quiet: true)
|
36
38
|
end
|
37
39
|
instance_variable_set(instance_var, val)
|
@@ -55,11 +57,126 @@ module CouchbaseOrm
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
60
|
+
def has_and_belongs_to_many(name, **options)
|
61
|
+
@associations ||= []
|
62
|
+
@associations << [name.to_sym, options[:dependent]]
|
63
|
+
|
64
|
+
ref = options[:foreign_key] || :"#{name.to_s.singularize}_ids"
|
65
|
+
ref_ass = :"#{ref}="
|
66
|
+
instance_var = :"@__assoc_#{name}"
|
67
|
+
|
68
|
+
# Class reference
|
69
|
+
assoc = (options[:class_name] || name.to_s.singularize.camelize).to_s
|
70
|
+
|
71
|
+
# Create the local setter / getter
|
72
|
+
attribute(ref) { |value|
|
73
|
+
remove_instance_variable(instance_var) if instance_variable_defined?(instance_var)
|
74
|
+
value
|
75
|
+
}
|
76
|
+
|
77
|
+
# Define reader
|
78
|
+
define_method(name) do
|
79
|
+
return instance_variable_get(instance_var) if instance_variable_defined?(instance_var)
|
80
|
+
ref_value = self.send(ref)
|
81
|
+
ref_value = nil if ref_value.respond_to?(:empty?) && ref_value.empty?
|
82
|
+
|
83
|
+
val = if options[:polymorphic]
|
84
|
+
::CouchbaseOrm.try_load(ref_value) if ref_value
|
85
|
+
else
|
86
|
+
raise CouchbaseOrm::StrictLoadingViolationError, "#{self.class} is marked as strict_loading and #{assoc} cannot be lazily loaded." if strict_loading?
|
87
|
+
|
88
|
+
assoc.constantize.find(ref_value) if ref_value
|
89
|
+
end
|
90
|
+
val = Array.wrap(val || [])
|
91
|
+
instance_variable_set(instance_var, val)
|
92
|
+
val
|
93
|
+
end
|
94
|
+
|
95
|
+
# Define writer
|
96
|
+
attr_writer name
|
97
|
+
define_method(:"#{name}=") do |value|
|
98
|
+
if value
|
99
|
+
if !options[:polymorphic]
|
100
|
+
klass = assoc.constantize
|
101
|
+
value.each do |v|
|
102
|
+
raise ArgumentError, "type mismatch on association: #{klass.design_document} != #{v.class.design_document}" if klass.design_document != v.class.design_document
|
103
|
+
end
|
104
|
+
end
|
105
|
+
self.send(ref_ass, value.map(&:id))
|
106
|
+
else
|
107
|
+
self.send(ref_ass, nil)
|
108
|
+
end
|
109
|
+
|
110
|
+
instance_variable_set(instance_var, value)
|
111
|
+
end
|
112
|
+
|
113
|
+
return unless options[:autosave]
|
114
|
+
|
115
|
+
save_method = :"autosave_associated_records_for_#{name}"
|
116
|
+
|
117
|
+
define_non_cyclic_method(save_method) do
|
118
|
+
old, new = previous_changes[ref]
|
119
|
+
adds = (new || []) - (old || [])
|
120
|
+
subs = (old || []) - (new || [])
|
121
|
+
update_has_and_belongs_to_many_reverse_association(assoc, adds, true, **options) if adds.any?
|
122
|
+
update_has_and_belongs_to_many_reverse_association(assoc, subs, false, **options) if subs.any?
|
123
|
+
end
|
124
|
+
|
125
|
+
after_create save_method
|
126
|
+
after_update save_method
|
127
|
+
end
|
128
|
+
|
58
129
|
def associations
|
59
130
|
@associations || []
|
60
131
|
end
|
132
|
+
|
133
|
+
def define_non_cyclic_method(name, &block)
|
134
|
+
return if method_defined?(name)
|
135
|
+
|
136
|
+
define_method(name) do |*args|
|
137
|
+
result = true; @_already_called ||= {}
|
138
|
+
# Loop prevention for validation of associations
|
139
|
+
unless @_already_called[name]
|
140
|
+
begin
|
141
|
+
@_already_called[name] = true
|
142
|
+
result = instance_eval(&block)
|
143
|
+
ensure
|
144
|
+
@_already_called[name] = false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
result
|
148
|
+
end
|
149
|
+
end
|
61
150
|
end
|
62
151
|
|
152
|
+
def update_has_and_belongs_to_many_reverse_association(assoc, keys, is_add, **options)
|
153
|
+
remote_method = options[:inverse_of] || self.class.to_s.pluralize.underscore.to_sym
|
154
|
+
return if keys.empty?
|
155
|
+
|
156
|
+
models = if options[:polymorphic]
|
157
|
+
::CouchbaseOrm.try_load(keys)
|
158
|
+
else
|
159
|
+
assoc.constantize.find(keys, quiet: true)
|
160
|
+
end
|
161
|
+
models = Array.wrap(models)
|
162
|
+
models.each do |v|
|
163
|
+
next unless v.respond_to?(remote_method)
|
164
|
+
|
165
|
+
tab = v.__send__(remote_method) || []
|
166
|
+
index = tab.find_index(self)
|
167
|
+
if is_add && !index
|
168
|
+
tab = tab.dup
|
169
|
+
tab.push(self)
|
170
|
+
elsif !is_add && index
|
171
|
+
tab = tab.dup
|
172
|
+
tab.delete_at(index)
|
173
|
+
else
|
174
|
+
next
|
175
|
+
end
|
176
|
+
v[remote_method] = tab
|
177
|
+
v.save!
|
178
|
+
end
|
179
|
+
end
|
63
180
|
|
64
181
|
def destroy_associations!
|
65
182
|
assoc = self.class.associations
|
@@ -72,6 +189,8 @@ module CouchbaseOrm
|
|
72
189
|
when :destroy, :delete
|
73
190
|
if model.respond_to?(:stream)
|
74
191
|
model.stream { |mod| mod.__send__(dependent) }
|
192
|
+
elsif model.is_a?(Array) || model.is_a?(CouchbaseOrm::ResultsProxy)
|
193
|
+
model.each { |m| m.__send__(dependent) }
|
75
194
|
else
|
76
195
|
model.__send__(dependent)
|
77
196
|
end
|
data/lib/couchbase-orm/base.rb
CHANGED
@@ -3,110 +3,198 @@
|
|
3
3
|
|
4
4
|
require 'active_model'
|
5
5
|
require 'active_support/hash_with_indifferent_access'
|
6
|
+
require 'couchbase'
|
7
|
+
require 'couchbase-orm/changeable'
|
8
|
+
require 'couchbase-orm/inspectable'
|
6
9
|
require 'couchbase-orm/error'
|
7
10
|
require 'couchbase-orm/views'
|
11
|
+
require 'couchbase-orm/n1ql'
|
8
12
|
require 'couchbase-orm/persistence'
|
9
13
|
require 'couchbase-orm/associations'
|
14
|
+
require 'couchbase-orm/types'
|
15
|
+
require 'couchbase-orm/relation'
|
16
|
+
require 'couchbase-orm/proxies/bucket_proxy'
|
17
|
+
require 'couchbase-orm/proxies/collection_proxy'
|
10
18
|
require 'couchbase-orm/utilities/join'
|
11
19
|
require 'couchbase-orm/utilities/enum'
|
12
20
|
require 'couchbase-orm/utilities/index'
|
13
21
|
require 'couchbase-orm/utilities/has_many'
|
14
22
|
require 'couchbase-orm/utilities/ensure_unique'
|
15
|
-
|
23
|
+
require 'couchbase-orm/utilities/query_helper'
|
24
|
+
require 'couchbase-orm/utilities/ignored_properties'
|
25
|
+
require 'couchbase-orm/json_transcoder'
|
26
|
+
require 'couchbase-orm/timestamps'
|
27
|
+
require 'couchbase-orm/active_record_compat'
|
28
|
+
require 'couchbase-orm/strict_loading'
|
29
|
+
require 'couchbase-orm/json_schema/validation'
|
30
|
+
require 'couchbase-orm/utilities/properties_always_exists_in_document'
|
16
31
|
|
17
32
|
module CouchbaseOrm
|
18
|
-
class
|
33
|
+
class Document
|
34
|
+
include Inspectable
|
19
35
|
include ::ActiveModel::Model
|
20
36
|
include ::ActiveModel::Dirty
|
37
|
+
include Changeable # override some methods from ActiveModel::Dirty (keep it included after)
|
38
|
+
include ::ActiveModel::Attributes
|
21
39
|
include ::ActiveModel::Serializers::JSON
|
22
40
|
|
23
41
|
include ::ActiveModel::Validations
|
24
42
|
include ::ActiveModel::Validations::Callbacks
|
43
|
+
|
44
|
+
include ActiveRecordCompat
|
45
|
+
include StrictLoading
|
46
|
+
include Encrypt
|
47
|
+
|
48
|
+
extend Enum
|
49
|
+
|
25
50
|
define_model_callbacks :initialize, :only => :after
|
26
|
-
define_model_callbacks :create, :destroy, :save, :update
|
27
51
|
|
52
|
+
Metadata = Struct.new(:cas)
|
53
|
+
|
54
|
+
class MismatchTypeError < RuntimeError; end
|
55
|
+
|
56
|
+
def initialize(model = nil, ignore_doc_type: false, **attributes)
|
57
|
+
CouchbaseOrm.logger.debug { "Initialize model #{model} with #{attributes.to_s.truncate(200)}" }
|
58
|
+
@__metadata__ = Metadata.new
|
59
|
+
|
60
|
+
super()
|
61
|
+
|
62
|
+
if model
|
63
|
+
case model
|
64
|
+
when Couchbase::Collection::GetResult
|
65
|
+
doc = HashWithIndifferentAccess.new(model.content) || raise('empty response provided')
|
66
|
+
type = doc.delete(:type)
|
67
|
+
doc.delete(:id)
|
68
|
+
|
69
|
+
if type && !ignore_doc_type && type.to_s != self.class.design_document
|
70
|
+
raise CouchbaseOrm::Error::TypeMismatchError.new("document type mismatch, #{type} != #{self.class.design_document}", self)
|
71
|
+
end
|
72
|
+
|
73
|
+
self.id = attributes[:id] if attributes[:id].present?
|
74
|
+
@__metadata__.cas = model.cas
|
75
|
+
|
76
|
+
assign_attributes(decode_encrypted_attributes(doc))
|
77
|
+
when CouchbaseOrm::Base
|
78
|
+
clear_changes_information
|
79
|
+
super(model.attributes.except(:id, 'type'))
|
80
|
+
else
|
81
|
+
clear_changes_information
|
82
|
+
assign_attributes(decode_encrypted_attributes(**attributes.merge(Hash(model)).symbolize_keys))
|
83
|
+
end
|
84
|
+
else
|
85
|
+
clear_changes_information
|
86
|
+
super(attributes)
|
87
|
+
end
|
88
|
+
|
89
|
+
yield self if block_given?
|
90
|
+
|
91
|
+
init_strict_loading
|
92
|
+
run_callbacks :initialize
|
93
|
+
end
|
94
|
+
|
95
|
+
def [](key)
|
96
|
+
send(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
def []=(key, value)
|
100
|
+
send(:"#{key}=", value)
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def serialized_attributes
|
106
|
+
encode_encrypted_attributes.map { |k, v|
|
107
|
+
[k, self.class.attribute_types[k].serialize(v)]
|
108
|
+
}.to_h
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class NestedDocument < Document
|
113
|
+
def initialize(*args, **kwargs)
|
114
|
+
super
|
115
|
+
if respond_to?(:id) && id.nil?
|
116
|
+
assign_attributes(id: SecureRandom.hex)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Base < Document
|
122
|
+
define_model_callbacks :create, :destroy, :save, :update
|
28
123
|
include Persistence
|
124
|
+
|
29
125
|
include Associations
|
30
126
|
include Views
|
127
|
+
include QueryHelper
|
128
|
+
include N1ql
|
129
|
+
include Relation
|
130
|
+
include Timestamps
|
31
131
|
|
32
132
|
extend Join
|
33
133
|
extend Enum
|
34
134
|
extend EnsureUnique
|
35
135
|
extend HasMany
|
36
136
|
extend Index
|
137
|
+
extend IgnoredProperties
|
138
|
+
extend JsonSchema::Validation
|
139
|
+
extend PropertiesAlwaysExistsInDocument
|
37
140
|
|
38
141
|
|
39
|
-
|
142
|
+
class << self
|
40
143
|
|
144
|
+
def attribute(name, ...)
|
145
|
+
super
|
146
|
+
create_dirty_methods(name, name)
|
147
|
+
create_setters(name)
|
148
|
+
end
|
41
149
|
|
42
|
-
class << self
|
43
150
|
def connect(**options)
|
44
|
-
@bucket = ::
|
151
|
+
@bucket = BucketProxy.new(::MTLibcouchbase::Bucket.new(**options))
|
45
152
|
end
|
46
153
|
|
47
154
|
def bucket=(bucket)
|
48
|
-
@bucket = bucket
|
155
|
+
@bucket = bucket.is_a?(BucketProxy) ? bucket : BucketProxy.new(bucket)
|
49
156
|
end
|
50
157
|
|
51
158
|
def bucket
|
52
|
-
@bucket ||= Connection.bucket
|
159
|
+
@bucket ||= BucketProxy.new(Connection.bucket)
|
53
160
|
end
|
54
161
|
|
55
|
-
def
|
56
|
-
|
162
|
+
def cluster
|
163
|
+
Connection.cluster
|
57
164
|
end
|
58
165
|
|
59
|
-
def
|
60
|
-
|
166
|
+
def collection
|
167
|
+
CollectionProxy.new(bucket.default_collection)
|
61
168
|
end
|
62
169
|
|
63
|
-
def
|
64
|
-
@
|
65
|
-
names.each do |name|
|
66
|
-
name = name.to_sym
|
67
|
-
|
68
|
-
@attributes[name] = options
|
69
|
-
|
70
|
-
unless self.instance_methods.include?(name)
|
71
|
-
define_method(name) do
|
72
|
-
read_attribute(name)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
eq_meth = :"#{name}="
|
77
|
-
unless self.instance_methods.include?(eq_meth)
|
78
|
-
define_method(eq_meth) do |value|
|
79
|
-
value = yield(value) if block_given?
|
80
|
-
write_attribute(name, value)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
170
|
+
def uuid_generator
|
171
|
+
@uuid_generator ||= IdGenerator
|
84
172
|
end
|
85
173
|
|
86
|
-
def
|
87
|
-
@
|
174
|
+
def uuid_generator=(generator)
|
175
|
+
@uuid_generator = generator
|
88
176
|
end
|
89
177
|
|
90
|
-
def find(*ids,
|
91
|
-
|
92
|
-
options[:quiet] ||= false
|
178
|
+
def find(*ids, quiet: false, with_strict_loading: false)
|
179
|
+
CouchbaseOrm.logger.debug { "Base.find(l##{ids.length}) #{ids}" }
|
93
180
|
|
94
181
|
ids = ids.flatten.select { |id| id.present? }
|
95
182
|
if ids.empty?
|
96
|
-
|
97
|
-
raise Libcouchbase::Error::EmptyKey, 'no id(s) provided'
|
183
|
+
raise CouchbaseOrm::Error::EmptyNotAllowed, 'no id(s) provided'
|
98
184
|
end
|
99
185
|
|
100
|
-
|
101
|
-
records =
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
186
|
+
transcoder = CouchbaseOrm::JsonTranscoder.new(ignored_properties: ignored_properties)
|
187
|
+
records = quiet ? collection.get_multi(ids, transcoder: transcoder) : collection.get_multi!(ids, transcoder: transcoder)
|
188
|
+
CouchbaseOrm.logger.debug { "Base.find found(#{records})" }
|
189
|
+
records = records.zip(ids).map { |record, id|
|
190
|
+
next unless record
|
191
|
+
next if record.error
|
192
|
+
new(record, id: id).tap do |instance|
|
193
|
+
if with_strict_loading
|
194
|
+
instance.strict_loading!
|
195
|
+
end
|
196
|
+
end.tap(&:reset_object!)
|
197
|
+
}.compact
|
110
198
|
ids.length > 1 ? records : records[0]
|
111
199
|
end
|
112
200
|
|
@@ -117,124 +205,18 @@ module CouchbaseOrm
|
|
117
205
|
alias_method :[], :find_by_id
|
118
206
|
|
119
207
|
def exists?(id)
|
120
|
-
|
208
|
+
CouchbaseOrm.logger.debug { "Data - Exists? #{id}" }
|
209
|
+
collection.exists(id).exists
|
121
210
|
end
|
122
211
|
alias_method :has_key?, :exists?
|
123
212
|
end
|
124
213
|
|
125
|
-
|
126
|
-
# Add support for libcouchbase response objects
|
127
|
-
def initialize(model = nil, ignore_doc_type: false, **attributes)
|
128
|
-
@__metadata__ = Metadata.new
|
129
|
-
|
130
|
-
# Assign default values
|
131
|
-
@__attributes__ = ::ActiveSupport::HashWithIndifferentAccess.new({type: self.class.design_document})
|
132
|
-
self.class.attributes.each do |key, options|
|
133
|
-
default = options[:default]
|
134
|
-
if default.respond_to?(:call)
|
135
|
-
write_attribute key, default.call
|
136
|
-
else
|
137
|
-
write_attribute key, default
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
if model
|
142
|
-
case model
|
143
|
-
when ::Libcouchbase::Response
|
144
|
-
doc = model.value || raise('empty response provided')
|
145
|
-
type = doc.delete(:type)
|
146
|
-
doc.delete(:id)
|
147
|
-
|
148
|
-
if type && !ignore_doc_type && type.to_s != self.class.design_document
|
149
|
-
raise "document type mismatch, #{type} != #{self.class.design_document}"
|
150
|
-
end
|
151
|
-
|
152
|
-
@__metadata__.key = model.key
|
153
|
-
@__metadata__.cas = model.cas
|
154
|
-
|
155
|
-
# This ensures that defaults are applied
|
156
|
-
@__attributes__.merge! doc
|
157
|
-
clear_changes_information
|
158
|
-
when CouchbaseOrm::Base
|
159
|
-
clear_changes_information
|
160
|
-
attributes = model.attributes
|
161
|
-
attributes.delete(:id)
|
162
|
-
super(attributes)
|
163
|
-
else
|
164
|
-
clear_changes_information
|
165
|
-
super(attributes.merge(Hash(model)))
|
166
|
-
end
|
167
|
-
else
|
168
|
-
clear_changes_information
|
169
|
-
super(attributes)
|
170
|
-
end
|
171
|
-
|
172
|
-
yield self if block_given?
|
173
|
-
|
174
|
-
run_callbacks :initialize
|
175
|
-
end
|
176
|
-
|
177
|
-
|
178
|
-
# Document ID is a special case as it is not stored in the document
|
179
|
-
def id
|
180
|
-
@__metadata__.key || @id
|
181
|
-
end
|
182
|
-
|
183
214
|
def id=(value)
|
184
|
-
raise 'ID cannot be changed' if @__metadata__.cas
|
215
|
+
raise RuntimeError, 'ID cannot be changed' if @__metadata__.cas && value
|
185
216
|
attribute_will_change!(:id)
|
186
|
-
|
187
|
-
end
|
188
|
-
|
189
|
-
def read_attribute(attr_name)
|
190
|
-
@__attributes__[attr_name]
|
191
|
-
end
|
192
|
-
alias_method :[], :read_attribute
|
193
|
-
|
194
|
-
def write_attribute(attr_name, value)
|
195
|
-
unless value.nil?
|
196
|
-
coerce = self.class.attributes[attr_name][:type]
|
197
|
-
value = Kernel.send(coerce.to_s, value) if coerce
|
198
|
-
end
|
199
|
-
attribute_will_change!(attr_name) unless @__attributes__[attr_name] == value
|
200
|
-
@__attributes__[attr_name] = value
|
201
|
-
end
|
202
|
-
alias_method :[]=, :write_attribute
|
203
|
-
|
204
|
-
#
|
205
|
-
# Add support for Serialization:
|
206
|
-
# http://guides.rubyonrails.org/active_model_basics.html#serialization
|
207
|
-
#
|
208
|
-
|
209
|
-
def attributes
|
210
|
-
copy = @__attributes__.merge({id: id})
|
211
|
-
copy.delete(:type)
|
212
|
-
copy
|
213
|
-
end
|
214
|
-
|
215
|
-
def attributes=(attributes)
|
216
|
-
attributes.each do |key, value|
|
217
|
-
setter = :"#{key}="
|
218
|
-
send(setter, value) if respond_to?(setter)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
ID_LOOKUP = ['id', :id].freeze
|
223
|
-
def attribute(name)
|
224
|
-
return self.id if ID_LOOKUP.include?(name)
|
225
|
-
@__attributes__[name]
|
226
|
-
end
|
227
|
-
alias_method :read_attribute_for_serialization, :attribute
|
228
|
-
|
229
|
-
def attribute=(name, value)
|
230
|
-
__send__(:"#{name}=", value)
|
217
|
+
_write_attribute("id", value)
|
231
218
|
end
|
232
219
|
|
233
|
-
|
234
|
-
#
|
235
|
-
# Add support for comparisons
|
236
|
-
#
|
237
|
-
|
238
220
|
# Public: Allows for access to ActiveModel functionality.
|
239
221
|
#
|
240
222
|
# Returns self.
|
@@ -267,12 +249,7 @@ module CouchbaseOrm
|
|
267
249
|
#
|
268
250
|
# Returns a boolean.
|
269
251
|
def ==(other)
|
270
|
-
|
271
|
-
when self.class
|
272
|
-
hash == other.hash
|
273
|
-
else
|
274
|
-
false
|
275
|
-
end
|
252
|
+
super || other.instance_of?(self.class) && !id.nil? && other.id == id
|
276
253
|
end
|
277
254
|
end
|
278
255
|
end
|