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.
- 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
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module CouchbaseOrm
|
5
|
+
module JsonSchema
|
6
|
+
class Loader
|
7
|
+
include Singleton
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
JSON_SCHEMAS_PATH = 'db/cborm_schemas'
|
11
|
+
|
12
|
+
attr_reader :schemas
|
13
|
+
|
14
|
+
def initialize(json_schemas_path = JSON_SCHEMAS_PATH)
|
15
|
+
@schemas_directory = json_schemas_path
|
16
|
+
@schemas = {}
|
17
|
+
unless File.directory?(schemas_directory)
|
18
|
+
CouchbaseOrm.logger.info { "Directory not found #{schemas_directory}" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_type(entity = {})
|
23
|
+
entity[:type]
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_json_schema!(entity, schema_path: nil)
|
27
|
+
document_type = extract_type!(entity)
|
28
|
+
|
29
|
+
return schemas[document_type] if schemas.key?(document_type)
|
30
|
+
|
31
|
+
schema_path ||= File.join(schemas_directory, "#{document_type}.json")
|
32
|
+
|
33
|
+
raise(Error, "Schema not found for #{document_type} in #{schema_path}") unless File.exist?(schema_path)
|
34
|
+
|
35
|
+
schemas[document_type] = File.read schema_path
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :schemas_directory
|
41
|
+
|
42
|
+
def extract_type!(entity = {})
|
43
|
+
extract_type(entity) || raise(Error, "No type found in #{entity}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module JsonSchema
|
3
|
+
module Validation
|
4
|
+
|
5
|
+
def validate_json_schema(mode: :strict, schema_path: nil)
|
6
|
+
@json_validation_config = {
|
7
|
+
enabled: true,
|
8
|
+
mode: mode,
|
9
|
+
schema_path: schema_path,
|
10
|
+
}.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def json_validation_config
|
14
|
+
@json_validation_config ||= {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require 'json-schema'
|
5
|
+
|
6
|
+
module CouchbaseOrm
|
7
|
+
module JsonSchema
|
8
|
+
class Validator
|
9
|
+
|
10
|
+
def initialize(json_validation_config)
|
11
|
+
@json_validation_config = json_validation_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_entity(entity, json)
|
15
|
+
case json_validation_config[:mode]
|
16
|
+
when :strict
|
17
|
+
strict_validation(entity, json)
|
18
|
+
when :logger
|
19
|
+
logger_validation(entity, json)
|
20
|
+
else
|
21
|
+
raise "Unknown validation mode #{json_validation_config[:mode]}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :json_validation_config
|
28
|
+
|
29
|
+
def strict_validation(entity, json)
|
30
|
+
error_results = common_validate(entity, json)
|
31
|
+
raise JsonValidationError.new(Loader.instance.extract_type(entity), error_results) unless error_results.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def logger_validation(entity, json)
|
35
|
+
error_results = common_validate(entity, json)
|
36
|
+
CouchbaseOrm.logger.error { "[COUCHBASEORM]: Invalid document #{Loader.instance.extract_type(entity)} with errors : #{error_results}" } unless error_results.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def common_validate(entity, json)
|
40
|
+
schema = Loader.instance.get_json_schema!(entity, schema_path: json_validation_config[:schema_path])
|
41
|
+
JSON::Validator.fully_validate(schema, json)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "json"
|
2
|
+
require 'couchbase/json_transcoder'
|
3
|
+
require 'couchbase-orm/json_schema'
|
4
|
+
|
5
|
+
module CouchbaseOrm
|
6
|
+
class JsonTranscoder < Couchbase::JsonTranscoder
|
7
|
+
|
8
|
+
attr_reader :ignored_properties, :json_validation_config
|
9
|
+
|
10
|
+
def initialize(ignored_properties: [], json_validation_config: {}, **options, &block)
|
11
|
+
@ignored_properties = ignored_properties
|
12
|
+
@json_validation_config = json_validation_config
|
13
|
+
super(**options, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decode(blob, _flags)
|
17
|
+
original = super
|
18
|
+
original&.except(*ignored_properties)
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode(document)
|
22
|
+
original = super
|
23
|
+
CouchbaseOrm::JsonSchema::Validator.new(json_validation_config).validate_entity(document, original[0]) if document.present? && !original.empty? && json_validation_config[:enabled]
|
24
|
+
original
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'active_support/core_ext/array/wrap'
|
5
|
+
require 'active_support/core_ext/object/try'
|
6
|
+
|
7
|
+
module CouchbaseOrm
|
8
|
+
module N1ql
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
NO_VALUE = :no_value_specified
|
11
|
+
DEFAULT_SCAN_CONSISTENCY = :request_plus
|
12
|
+
# sanitize for injection query
|
13
|
+
def self.sanitize(value)
|
14
|
+
if value.is_a?(String)
|
15
|
+
value.gsub("'", "''").gsub("\\"){"\\\\"}.gsub('"', '\"')
|
16
|
+
elsif value.is_a?(Array)
|
17
|
+
value.map{ |v| sanitize(v) }
|
18
|
+
else
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config(new_config = nil)
|
24
|
+
Thread.current['__couchbaseorm_n1ql_config__'] = new_config if new_config
|
25
|
+
Thread.current['__couchbaseorm_n1ql_config__'] || {
|
26
|
+
scan_consistency: DEFAULT_SCAN_CONSISTENCY
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
# Defines a query N1QL for the model
|
32
|
+
#
|
33
|
+
# @param [Symbol, String, Array] names names of the views
|
34
|
+
# @param [Hash] options options passed to the {Couchbase::N1QL}
|
35
|
+
#
|
36
|
+
# @example Define some N1QL queries for a model
|
37
|
+
# class Post < CouchbaseOrm::Base
|
38
|
+
# n1ql :by_rating, emit_key: :rating
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Post.by_rating do |response|
|
42
|
+
# # ...
|
43
|
+
# end
|
44
|
+
# TODO: add range keys [:startkey, :endkey]
|
45
|
+
def n1ql(name, query_fn: nil, emit_key: [], custom_order: nil, **options)
|
46
|
+
raise ArgumentError, "#{self} already respond_to? #{name}" if self.respond_to?(name)
|
47
|
+
|
48
|
+
emit_key = Array.wrap(emit_key)
|
49
|
+
emit_key.each do |key|
|
50
|
+
raise "unknown emit_key attribute for n1ql :#{name}, emit_key: :#{key}" if key && !attribute_names.include?(key.to_s)
|
51
|
+
end
|
52
|
+
options = N1QL_DEFAULTS.merge(options)
|
53
|
+
method_opts = {}
|
54
|
+
method_opts[:emit_key] = emit_key
|
55
|
+
|
56
|
+
@indexes ||= {}
|
57
|
+
@indexes[name] = method_opts
|
58
|
+
|
59
|
+
singleton_class.__send__(:define_method, name) do |key: NO_VALUE, **opts, &result_modifier|
|
60
|
+
opts = options.merge(opts).reverse_merge(scan_consistency: CouchbaseOrm::N1ql.config[:scan_consistency])
|
61
|
+
values = key == NO_VALUE ? NO_VALUE : convert_values(method_opts[:emit_key], key)
|
62
|
+
current_query = run_query(method_opts[:emit_key], values, query_fn, custom_order: custom_order, **opts.except(:include_docs, :key))
|
63
|
+
if result_modifier
|
64
|
+
opts[:include_docs] = true
|
65
|
+
current_query.results &result_modifier
|
66
|
+
elsif opts[:include_docs]
|
67
|
+
current_query.results { |res| find(res) }
|
68
|
+
else
|
69
|
+
current_query.results
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
N1QL_DEFAULTS = { include_docs: true }
|
74
|
+
|
75
|
+
# add a n1ql query and lookup method to the model for finding all records
|
76
|
+
# using a value in the supplied attr.
|
77
|
+
def index_n1ql(attr, validate: true, find_method: nil, n1ql_method: nil)
|
78
|
+
n1ql_method ||= "by_#{attr}"
|
79
|
+
find_method ||= "find_#{n1ql_method}"
|
80
|
+
|
81
|
+
validates(attr, presence: true) if validate
|
82
|
+
n1ql n1ql_method, emit_key: attr
|
83
|
+
|
84
|
+
define_singleton_method find_method do |value|
|
85
|
+
send n1ql_method, key: [value]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def convert_values(keys, values)
|
92
|
+
return values if keys.empty? && Array.wrap(values).any?
|
93
|
+
keys.zip(Array.wrap(values)).map do |key, value_before_type_cast|
|
94
|
+
serialize_value(key, value_before_type_cast)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_where(keys, values)
|
99
|
+
where = values == NO_VALUE ? '' : keys.zip(Array.wrap(values))
|
100
|
+
.reject { |key, value| key.nil? && value.nil? }
|
101
|
+
.map { |key, value| build_match(key, value) }
|
102
|
+
.join(" AND ")
|
103
|
+
"type=\"#{design_document}\" #{"AND " + where unless where.blank?}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# order-by-clause ::= ORDER BY ordering-term [ ',' ordering-term ]*
|
107
|
+
# ordering-term ::= expr [ ASC | DESC ] [ NULLS ( FIRST | LAST ) ]
|
108
|
+
# see https://docs.couchbase.com/server/5.0/n1ql/n1ql-language-reference/orderby.html
|
109
|
+
def build_order(keys, descending)
|
110
|
+
"#{keys.dup.push("meta().id").map { |k| "#{k} #{descending ? "desc" : "asc" }" }.join(",")}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_limit(limit)
|
114
|
+
limit ? "limit #{limit}" : ""
|
115
|
+
end
|
116
|
+
|
117
|
+
def run_query(keys, values, query_fn, custom_order: nil, descending: false, limit: nil, **options)
|
118
|
+
if query_fn
|
119
|
+
N1qlProxy.new(query_fn.call(bucket, values, Couchbase::Options::Query.new(**options)))
|
120
|
+
else
|
121
|
+
bucket_name = bucket.name
|
122
|
+
where = build_where(keys, values)
|
123
|
+
order = custom_order || build_order(keys, descending)
|
124
|
+
limit = build_limit(limit)
|
125
|
+
n1ql_query = "select raw meta().id from `#{bucket_name}` where #{where} order by #{order} #{limit}"
|
126
|
+
result = cluster.query(n1ql_query, Couchbase::Options::Query.new(**options))
|
127
|
+
CouchbaseOrm.logger.debug "N1QL query: #{n1ql_query} return #{result.rows.to_a.length} rows with scan_consistency : #{options[:scan_consistency]}"
|
128
|
+
N1qlProxy.new(result)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -2,11 +2,18 @@
|
|
2
2
|
|
3
3
|
require 'active_model'
|
4
4
|
require 'active_support/hash_with_indifferent_access'
|
5
|
+
require 'couchbase-orm/json_transcoder'
|
6
|
+
require 'couchbase-orm/encrypt'
|
5
7
|
|
6
8
|
module CouchbaseOrm
|
7
9
|
module Persistence
|
8
10
|
extend ActiveSupport::Concern
|
9
11
|
|
12
|
+
include CouchbaseOrm::Encrypt
|
13
|
+
|
14
|
+
included do
|
15
|
+
attribute :id, :string
|
16
|
+
end
|
10
17
|
|
11
18
|
module ClassMethods
|
12
19
|
def create(attributes = nil, &block)
|
@@ -15,6 +22,7 @@ module CouchbaseOrm
|
|
15
22
|
else
|
16
23
|
instance = new(attributes, &block)
|
17
24
|
instance.save
|
25
|
+
instance.reset_object!
|
18
26
|
instance
|
19
27
|
end
|
20
28
|
end
|
@@ -25,6 +33,7 @@ module CouchbaseOrm
|
|
25
33
|
else
|
26
34
|
instance = new(attributes, &block)
|
27
35
|
instance.save!
|
36
|
+
instance.reset_object!
|
28
37
|
instance
|
29
38
|
end
|
30
39
|
end
|
@@ -54,20 +63,19 @@ module CouchbaseOrm
|
|
54
63
|
# Returns true if this object hasn't been saved yet -- that is, a record
|
55
64
|
# for the object doesn't exist in the database yet; otherwise, returns false.
|
56
65
|
def new_record?
|
57
|
-
@__metadata__.cas.nil?
|
66
|
+
@__metadata__.cas.nil?
|
58
67
|
end
|
59
68
|
alias_method :new?, :new_record?
|
60
69
|
|
61
70
|
# Returns true if this object has been destroyed, otherwise returns false.
|
62
71
|
def destroyed?
|
63
|
-
|
72
|
+
@destroyed ||= false
|
64
73
|
end
|
65
74
|
|
66
75
|
# Returns true if the record is persisted, i.e. it's not a new record and it was
|
67
76
|
# not destroyed, otherwise returns false.
|
68
77
|
def persisted?
|
69
|
-
|
70
|
-
!!@__metadata__.key
|
78
|
+
!new_record? && !destroyed?
|
71
79
|
end
|
72
80
|
alias_method :exists?, :persisted?
|
73
81
|
|
@@ -99,16 +107,18 @@ module CouchbaseOrm
|
|
99
107
|
# The record is simply removed, no callbacks are executed.
|
100
108
|
def delete(with_cas: false, **options)
|
101
109
|
options[:cas] = @__metadata__.cas if with_cas
|
102
|
-
|
103
|
-
|
104
|
-
@__metadata__.key = nil
|
105
|
-
@id = nil
|
110
|
+
CouchbaseOrm.logger.debug "Data - Delete #{self.id}"
|
111
|
+
self.class.collection.remove(self.id, **options)
|
106
112
|
|
113
|
+
self.id = nil
|
107
114
|
clear_changes_information
|
115
|
+
@destroyed = true
|
108
116
|
self.freeze
|
109
117
|
self
|
110
118
|
end
|
111
119
|
|
120
|
+
alias :remove :delete
|
121
|
+
|
112
122
|
# Deletes the record in the database and freezes this instance to reflect
|
113
123
|
# that no changes should be made (since they can't be persisted).
|
114
124
|
#
|
@@ -121,12 +131,13 @@ module CouchbaseOrm
|
|
121
131
|
destroy_associations!
|
122
132
|
|
123
133
|
options[:cas] = @__metadata__.cas if with_cas
|
124
|
-
|
134
|
+
CouchbaseOrm.logger.debug "Data - Destroy #{id}"
|
135
|
+
self.class.collection.remove(id, **options)
|
125
136
|
|
126
|
-
|
127
|
-
@id = nil
|
137
|
+
self.id = nil
|
128
138
|
|
129
139
|
clear_changes_information
|
140
|
+
@destroyed = true
|
130
141
|
freeze
|
131
142
|
end
|
132
143
|
end
|
@@ -141,7 +152,12 @@ module CouchbaseOrm
|
|
141
152
|
public_send(:"#{name}=", value)
|
142
153
|
changed? ? save(validate: false) : true
|
143
154
|
end
|
144
|
-
|
155
|
+
|
156
|
+
def assign_attributes(hash)
|
157
|
+
hash = hash.with_indifferent_access if hash.is_a?(Hash)
|
158
|
+
super(hash.except("type"))
|
159
|
+
end
|
160
|
+
|
145
161
|
# Updates the attributes of the model from the passed-in hash and saves the
|
146
162
|
# record. If the object is invalid, the saving will fail and false will be returned.
|
147
163
|
def update(hash)
|
@@ -158,10 +174,12 @@ module CouchbaseOrm
|
|
158
174
|
end
|
159
175
|
alias_method :update_attributes!, :update!
|
160
176
|
|
161
|
-
# Updates the record without validating or running callbacks
|
177
|
+
# Updates the record without validating or running callbacks.
|
178
|
+
# Updates only the attributes that are passed in as parameters
|
179
|
+
# except if there is more than 16 attributes, in which case
|
180
|
+
# the whole record is saved.
|
162
181
|
def update_columns(with_cas: false, **hash)
|
163
|
-
|
164
|
-
raise "unable to update columns, model not persisted" unless _id
|
182
|
+
raise "unable to update columns, model not persisted" unless id
|
165
183
|
|
166
184
|
assign_attributes(hash)
|
167
185
|
|
@@ -170,20 +188,17 @@ module CouchbaseOrm
|
|
170
188
|
|
171
189
|
# There is a limit of 16 subdoc operations per request
|
172
190
|
resp = if hash.length <= 16
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
subdoc.execute!(options)
|
191
|
+
self.class.collection.mutate_in(
|
192
|
+
id,
|
193
|
+
hash.map { |k, v| Couchbase::MutateInSpec.replace(k.to_s, v) }
|
194
|
+
)
|
178
195
|
else
|
179
196
|
# Fallback to writing the whole document
|
180
|
-
|
181
|
-
|
182
|
-
self.class.bucket.replace(_id, @__attributes__, **options)
|
197
|
+
CouchbaseOrm.logger.debug { "Data - Replace #{id} #{attributes.to_s.truncate(200)}" }
|
198
|
+
self.class.collection.replace(id, attributes.except("id").merge(type: self.class.design_document), **options)
|
183
199
|
end
|
184
200
|
|
185
201
|
# Ensure the model is up to date
|
186
|
-
@__metadata__.key = resp.key
|
187
202
|
@__metadata__.cas = resp.cas
|
188
203
|
|
189
204
|
changes_applied
|
@@ -194,47 +209,43 @@ module CouchbaseOrm
|
|
194
209
|
#
|
195
210
|
# This method finds record by its key and modifies the receiver in-place:
|
196
211
|
def reload
|
197
|
-
|
198
|
-
raise "unable to reload, model not persisted" unless key
|
212
|
+
raise "unable to reload, model not persisted" unless id
|
199
213
|
|
200
|
-
|
201
|
-
|
202
|
-
|
214
|
+
CouchbaseOrm.logger.debug "Data - Get #{id}"
|
215
|
+
resp = self.class.collection.get!(id)
|
216
|
+
assign_attributes(decode_encrypted_attributes(resp.content.except("id", *self.class.ignored_properties ))) # API return a nil id
|
203
217
|
@__metadata__.cas = resp.cas
|
204
218
|
|
205
219
|
reset_associations
|
206
220
|
clear_changes_information
|
221
|
+
reset_object!
|
207
222
|
self
|
208
223
|
end
|
209
224
|
|
210
225
|
# Updates the TTL of the document
|
211
226
|
def touch(**options)
|
212
|
-
|
227
|
+
CouchbaseOrm.logger.debug "Data - Touch #{id}"
|
228
|
+
_res = self.class.collection.touch(id, async: false, **options)
|
213
229
|
@__metadata__.cas = resp.cas
|
214
230
|
self
|
215
231
|
end
|
216
232
|
|
217
233
|
|
218
|
-
protected
|
219
|
-
|
220
234
|
|
221
|
-
def _update_record(with_cas: false, **options)
|
235
|
+
def _update_record(*_args, with_cas: false, **options)
|
222
236
|
return false unless perform_validations(:update, options)
|
223
|
-
return true unless changed?
|
237
|
+
return true unless changed? || self.class.attribute_types.any? { |_, type| type.is_a?(CouchbaseOrm::Types::Nested) || type.is_a?(CouchbaseOrm::Types::Array) }
|
224
238
|
|
225
239
|
run_callbacks :update do
|
226
240
|
run_callbacks :save do
|
227
|
-
# Ensure the type is set
|
228
|
-
@__attributes__[:type] = self.class.design_document
|
229
|
-
@__attributes__.delete(:id)
|
230
|
-
|
231
|
-
_id = @__metadata__.key
|
232
241
|
options[:cas] = @__metadata__.cas if with_cas
|
233
|
-
|
234
|
-
|
242
|
+
CouchbaseOrm.logger.debug { "_update_record - replace #{id} #{serialized_attributes.to_s.truncate(200)}" }
|
243
|
+
if options[:transcoder].nil?
|
244
|
+
options[:transcoder] = CouchbaseOrm::JsonTranscoder.new(json_validation_config: self.class.json_validation_config)
|
245
|
+
end
|
246
|
+
resp = self.class.collection.replace(id, serialized_attributes.except("id").merge(type: self.class.design_document), Couchbase::Options::Replace.new(**options))
|
235
247
|
|
236
248
|
# Ensure the model is up to date
|
237
|
-
@__metadata__.key = resp.key
|
238
249
|
@__metadata__.cas = resp.cas
|
239
250
|
|
240
251
|
changes_applied
|
@@ -242,21 +253,19 @@ module CouchbaseOrm
|
|
242
253
|
end
|
243
254
|
end
|
244
255
|
end
|
245
|
-
|
246
|
-
def _create_record(**options)
|
256
|
+
def _create_record(*_args, **options)
|
247
257
|
return false unless perform_validations(:create, options)
|
248
258
|
|
249
259
|
run_callbacks :create do
|
250
260
|
run_callbacks :save do
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
resp = self.class.
|
261
|
+
assign_attributes(id: self.class.uuid_generator.next(self)) unless self.id
|
262
|
+
CouchbaseOrm.logger.debug { "_create_record - Upsert #{id} #{serialized_attributes.to_s.truncate(200)}" }
|
263
|
+
if options[:transcoder].nil?
|
264
|
+
options[:transcoder] = CouchbaseOrm::JsonTranscoder.new(json_validation_config: self.class.json_validation_config)
|
265
|
+
end
|
266
|
+
resp = self.class.collection.upsert(self.id, serialized_attributes.except("id").merge(type: self.class.design_document), Couchbase::Options::Upsert.new(**options))
|
257
267
|
|
258
268
|
# Ensure the model is up to date
|
259
|
-
@__metadata__.key = resp.key
|
260
269
|
@__metadata__.cas = resp.cas
|
261
270
|
|
262
271
|
changes_applied
|
@@ -270,4 +279,4 @@ module CouchbaseOrm
|
|
270
279
|
true
|
271
280
|
end
|
272
281
|
end
|
273
|
-
end
|
282
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'couchbase-orm/proxies/n1ql_proxy'
|
4
|
+
|
5
|
+
module CouchbaseOrm
|
6
|
+
class BucketProxy
|
7
|
+
def initialize(proxyfied)
|
8
|
+
raise ArgumentError, "Must proxy a non nil object" if proxyfied.nil?
|
9
|
+
|
10
|
+
@proxyfied = proxyfied
|
11
|
+
|
12
|
+
self.class.define_method(:n1ql) do
|
13
|
+
N1qlProxy.new(@proxyfied.n1ql)
|
14
|
+
end
|
15
|
+
|
16
|
+
self.class.define_method(:view) do |design, view, **opts, &block|
|
17
|
+
@results = nil if @current_query != "#{design}_#{view}"
|
18
|
+
@current_query = "#{design}_#{view}"
|
19
|
+
return @results if @results
|
20
|
+
|
21
|
+
CouchbaseOrm.logger.debug "View - #{design} #{view}"
|
22
|
+
@results = ResultsProxy.new(@proxyfied.send(:view, design, view, **opts, &block))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if RUBY_VERSION.to_i >= 3
|
27
|
+
def method_missing(name, *args, **options, &block)
|
28
|
+
@proxyfied.public_send(name, *args, **options, &block)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
def method_missing(name, *args, &block)
|
32
|
+
@proxyfied.public_send(name, *args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "couchbase"
|
2
|
+
|
3
|
+
module CouchbaseOrm
|
4
|
+
class CollectionProxy
|
5
|
+
|
6
|
+
def get!(id, **options)
|
7
|
+
@proxyfied.get(id, Couchbase::Options::Get.new(**options))
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(id, **options)
|
11
|
+
@proxyfied.get(id, Couchbase::Options::Get.new(**options))
|
12
|
+
rescue Couchbase::Error::DocumentNotFound
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_multi!(*ids, **options)
|
17
|
+
result = @proxyfied.get_multi(*ids, Couchbase::Options::GetMulti.new(**options))
|
18
|
+
first_result_with_error = result.find(&:error)
|
19
|
+
raise first_result_with_error.error if first_result_with_error
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_multi(*ids, **options)
|
24
|
+
@proxyfied.get_multi(*ids, Couchbase::Options::GetMulti.new(**options))
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove!(id, **options)
|
28
|
+
@proxyfied.remove(id, Couchbase::Options::Remove.new(**options))
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove(id, **options)
|
32
|
+
@proxyfied.remove(id, Couchbase::Options::Remove.new(**options))
|
33
|
+
rescue Couchbase::Error::DocumentNotFound
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(proxyfied)
|
38
|
+
raise "Must proxy a non nil object" if proxyfied.nil?
|
39
|
+
@proxyfied = proxyfied
|
40
|
+
end
|
41
|
+
|
42
|
+
if RUBY_VERSION.to_i >= 3
|
43
|
+
def method_missing(name, *args, **options, &block)
|
44
|
+
@proxyfied.public_send(name, *args, **options, &block)
|
45
|
+
end
|
46
|
+
else # :nocov:
|
47
|
+
def method_missing(name, *args, &block)
|
48
|
+
@proxyfied.public_send(name, *args, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'couchbase-orm/proxies/results_proxy'
|
4
|
+
|
5
|
+
module CouchbaseOrm
|
6
|
+
class N1qlProxy
|
7
|
+
def initialize(proxyfied)
|
8
|
+
@proxyfied = proxyfied
|
9
|
+
|
10
|
+
self.class.define_method(:results) do |*params, &block|
|
11
|
+
@results = nil if @current_query != self.to_s
|
12
|
+
@current_query = self.to_s
|
13
|
+
return @results if @results
|
14
|
+
|
15
|
+
CouchbaseOrm.logger.debug { 'Query - ' + self.to_s }
|
16
|
+
|
17
|
+
results = @proxyfied.rows
|
18
|
+
results = results.map { |r| block.call(r) } if block
|
19
|
+
@results = ResultsProxy.new(results.to_a)
|
20
|
+
end
|
21
|
+
|
22
|
+
self.class.define_method(:to_s) do
|
23
|
+
@proxyfied.to_s.tr("\n", ' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
proxyfied.public_methods.each do |method|
|
27
|
+
next if self.public_methods.include?(method)
|
28
|
+
|
29
|
+
self.class.define_method(method) do |*params, &block|
|
30
|
+
ret = @proxyfied.send(method, *params, &block)
|
31
|
+
ret.is_a?(@proxyfied.class) ? self : ret
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(m, *args, &block)
|
37
|
+
self.results.send(m, *args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|