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,23 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
module CouchbaseOrm
|
4
|
+
class ResultsProxy
|
5
|
+
def initialize(proxyfied)
|
6
|
+
@proxyfied = proxyfied
|
7
|
+
|
8
|
+
raise ArgumentError, "Proxyfied object must respond to :to_a" unless @proxyfied.respond_to?(:to_a)
|
9
|
+
|
10
|
+
proxyfied.public_methods.each do |method|
|
11
|
+
next if self.public_methods.include?(method)
|
12
|
+
|
13
|
+
self.class.define_method(method) do |*params, &block|
|
14
|
+
@proxyfied.send(method, *params, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(m, *args, &block)
|
20
|
+
@proxyfied.to_a.send(m, *args, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -23,9 +23,8 @@ require 'couchbase-orm/base'
|
|
23
23
|
module Rails #:nodoc:
|
24
24
|
module Couchbase #:nodoc:
|
25
25
|
class Railtie < Rails::Railtie #:nodoc:
|
26
|
-
|
27
|
-
config.
|
28
|
-
config.couchbase.ensure_design_documents = true
|
26
|
+
config.couchbase_orm = ActiveSupport::OrderedOptions.new
|
27
|
+
config.couchbase_orm.ensure_design_documents = true
|
29
28
|
|
30
29
|
# Maping of rescued exceptions to HTTP responses
|
31
30
|
#
|
@@ -35,10 +34,6 @@ module Rails #:nodoc:
|
|
35
34
|
# @return [Hash] rescued responses
|
36
35
|
def self.rescue_responses
|
37
36
|
{
|
38
|
-
'Libcouchbase::Error::KeyNotFound' => :not_found,
|
39
|
-
'Libcouchbase::Error::NotStored' => :unprocessable_entity,
|
40
|
-
Libcouchbase::Error::KeyNotFound => :not_found,
|
41
|
-
Libcouchbase::Error::NotStored => :unprocessable_entity
|
42
37
|
}
|
43
38
|
end
|
44
39
|
|
@@ -48,14 +43,8 @@ module Rails #:nodoc:
|
|
48
43
|
config.action_dispatch.rescue_responses.merge!(rescue_responses)
|
49
44
|
end
|
50
45
|
|
51
|
-
|
52
|
-
|
53
|
-
initializer 'couchbase.setup_connection' do
|
54
|
-
config_file = Rails.root.join('config', 'couchbase.yml')
|
55
|
-
if config_file.file? &&
|
56
|
-
config = YAML.load(ERB.new(File.read(config_file)).result)[Rails.env]
|
57
|
-
::CouchbaseOrm::Connection.options = config.deep_symbolize_keys
|
58
|
-
end
|
46
|
+
initializer 'couchbase_orm.setup_connection_config' do
|
47
|
+
CouchbaseOrm::Connection.config = Rails.application.config_for(:couchbase)
|
59
48
|
end
|
60
49
|
|
61
50
|
# After initialization we will warn the user if we can't find a couchbase.yml and
|
@@ -83,12 +72,12 @@ module Rails #:nodoc:
|
|
83
72
|
|
84
73
|
# Check (and upgrade if needed) all design documents
|
85
74
|
config.after_initialize do |app|
|
86
|
-
if config.
|
75
|
+
if config.couchbase_orm.ensure_design_documents
|
87
76
|
begin
|
88
77
|
::CouchbaseOrm::Base.descendants.each do |model|
|
89
78
|
model.ensure_design_document!
|
90
79
|
end
|
91
|
-
rescue ::
|
80
|
+
rescue ::MTLibcouchbase::Error::Timedout, ::MTLibcouchbase::Error::ConnectError, ::MTLibcouchbase::Error::NetworkError
|
92
81
|
# skip connection errors for now
|
93
82
|
end
|
94
83
|
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module Relation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class CouchbaseOrm_Relation
|
6
|
+
def initialize(model:, where: where = nil, order: order = nil, limit: limit = nil, _not: _not = false, strict_loading: strict_loading = false)
|
7
|
+
CouchbaseOrm::logger.debug "CouchbaseOrm_Relation init: #{model} where:#{where.inspect} not:#{_not.inspect} order:#{order.inspect} limit: #{limit} strict_loading: #{strict_loading}"
|
8
|
+
@model = model
|
9
|
+
@limit = limit
|
10
|
+
@where = []
|
11
|
+
@order = {}
|
12
|
+
@order = merge_order(**order) if order
|
13
|
+
@where = merge_where(where, _not) if where
|
14
|
+
@strict_loading = strict_loading
|
15
|
+
CouchbaseOrm::logger.debug "- #{to_s}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"CouchbaseOrm_Relation: #{@model} where:#{@where.inspect} order:#{@order.inspect} limit: #{@limit} strict_loading: #{@strict_loading}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_n1ql
|
23
|
+
bucket_name = @model.bucket.name
|
24
|
+
where = build_where
|
25
|
+
order = build_order
|
26
|
+
limit = build_limit
|
27
|
+
"select raw meta().id from `#{bucket_name}` where #{where} order by #{order} #{limit}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(n1ql_query)
|
31
|
+
result = @model.cluster.query(n1ql_query, Couchbase::Options::Query.new(scan_consistency: CouchbaseOrm::N1ql.config[:scan_consistency]))
|
32
|
+
CouchbaseOrm.logger.debug { "Relation query: #{n1ql_query} return #{result.rows.to_a.length} rows with scan_consistency : #{CouchbaseOrm::N1ql.config[:scan_consistency]}" }
|
33
|
+
N1qlProxy.new(result)
|
34
|
+
end
|
35
|
+
|
36
|
+
def query
|
37
|
+
CouchbaseOrm::logger.debug("Query: #{self}")
|
38
|
+
n1ql_query = to_n1ql
|
39
|
+
execute(n1ql_query)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_all(**cond)
|
43
|
+
bucket_name = @model.bucket.name
|
44
|
+
where = build_where
|
45
|
+
limit = build_limit
|
46
|
+
update = build_update(**cond)
|
47
|
+
n1ql_query = "update `#{bucket_name}` set #{update} where #{where} #{limit}"
|
48
|
+
execute(n1ql_query)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ids
|
52
|
+
query.to_a
|
53
|
+
end
|
54
|
+
|
55
|
+
def strict_loading
|
56
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(strict_loading: true))
|
57
|
+
end
|
58
|
+
|
59
|
+
def strict_loading?
|
60
|
+
!!@strict_loading
|
61
|
+
end
|
62
|
+
|
63
|
+
def first
|
64
|
+
result = @model.cluster.query(self.limit(1).to_n1ql, Couchbase::Options::Query.new(scan_consistency: CouchbaseOrm::N1ql.config[:scan_consistency]))
|
65
|
+
return unless (first_id = result.rows.to_a.first)
|
66
|
+
|
67
|
+
@model.find(first_id, with_strict_loading: @strict_loading)
|
68
|
+
end
|
69
|
+
|
70
|
+
def last
|
71
|
+
result = @model.cluster.query(to_n1ql, Couchbase::Options::Query.new(scan_consistency: CouchbaseOrm::N1ql.config[:scan_consistency]))
|
72
|
+
last_id = result.rows.to_a.last
|
73
|
+
@model.find(last_id, with_strict_loading: @strict_loading) if last_id
|
74
|
+
end
|
75
|
+
|
76
|
+
def count
|
77
|
+
query.count
|
78
|
+
end
|
79
|
+
|
80
|
+
def empty?
|
81
|
+
limit(1).count == 0
|
82
|
+
end
|
83
|
+
|
84
|
+
def pluck(*fields)
|
85
|
+
map do |model|
|
86
|
+
if fields.length == 1
|
87
|
+
model.send(fields.first)
|
88
|
+
else
|
89
|
+
fields.map do |field|
|
90
|
+
model.send(field)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
alias :size :count
|
97
|
+
alias :length :count
|
98
|
+
|
99
|
+
def to_ary
|
100
|
+
ids = query.results
|
101
|
+
return [] if ids.empty?
|
102
|
+
Array(ids && @model.find(ids, with_strict_loading: @strict_loading))
|
103
|
+
end
|
104
|
+
|
105
|
+
alias :to_a :to_ary
|
106
|
+
|
107
|
+
delegate :each, :map, :collect, :find, :filter, :reduce, :to => :to_ary
|
108
|
+
|
109
|
+
def [](*args)
|
110
|
+
to_ary[*args]
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete_all
|
114
|
+
CouchbaseOrm::logger.debug{ "Delete all: #{self}" }
|
115
|
+
ids = query.to_a
|
116
|
+
CouchbaseOrm::Connection.bucket.default_collection.remove_multi(ids) unless ids.empty?
|
117
|
+
end
|
118
|
+
|
119
|
+
def where(string_cond=nil, **conds)
|
120
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(where: merge_where(conds)+string_where(string_cond)))
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_by(**conds)
|
124
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(where: merge_where(conds))).first
|
125
|
+
end
|
126
|
+
|
127
|
+
def not(**conds)
|
128
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(where: merge_where(conds, _not: true)))
|
129
|
+
end
|
130
|
+
|
131
|
+
def order(*lorder, **horder)
|
132
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(order: merge_order(*lorder, **horder)))
|
133
|
+
end
|
134
|
+
|
135
|
+
def limit(limit)
|
136
|
+
CouchbaseOrm_Relation.new(**initializer_arguments.merge(limit: limit))
|
137
|
+
end
|
138
|
+
|
139
|
+
def all
|
140
|
+
CouchbaseOrm_Relation.new(**initializer_arguments)
|
141
|
+
end
|
142
|
+
|
143
|
+
def scoping
|
144
|
+
scopes = (Thread.current[@model.name] ||= [])
|
145
|
+
scopes.push(self)
|
146
|
+
result = yield
|
147
|
+
ensure
|
148
|
+
scopes.pop
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def build_limit
|
155
|
+
@limit ? "limit #{@limit}" : ""
|
156
|
+
end
|
157
|
+
|
158
|
+
def initializer_arguments
|
159
|
+
{ model: @model, order: @order, where: @where, limit: @limit, strict_loading: @strict_loading }
|
160
|
+
end
|
161
|
+
|
162
|
+
def merge_order(*lorder, **horder)
|
163
|
+
raise ArgumentError, "invalid order passed by list: #{lorder.inspect}, must be symbols" unless lorder.all? { |o| o.is_a? Symbol }
|
164
|
+
raise ArgumentError, "Invalid order passed by hash: #{horder.inspect}, must be symbol -> :asc|:desc" unless horder.all? { |k, v| k.is_a?(Symbol) && [:asc, :desc].include?(v) }
|
165
|
+
@order
|
166
|
+
.merge(Array.wrap(lorder).map{ |o| [o, :asc] }.to_h)
|
167
|
+
.merge(horder)
|
168
|
+
end
|
169
|
+
|
170
|
+
def merge_where(conds, _not = false)
|
171
|
+
@where + (_not ? conds.to_a.map{|k,v|[k,v,:not]} : conds.to_a)
|
172
|
+
end
|
173
|
+
|
174
|
+
def string_where(string_cond, _not = false)
|
175
|
+
return [] unless string_cond
|
176
|
+
cond = "(#{string_cond})"
|
177
|
+
[(_not ? [nil, cond, :not] : [nil, cond])]
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_order
|
181
|
+
order = @order.map do |key, value|
|
182
|
+
"#{key} #{value}"
|
183
|
+
end.join(", ")
|
184
|
+
order.empty? ? "meta().id" : order
|
185
|
+
end
|
186
|
+
|
187
|
+
def build_where
|
188
|
+
build_conds([[:type, @model.design_document]] + @where)
|
189
|
+
end
|
190
|
+
|
191
|
+
def build_conds(conds)
|
192
|
+
conds.map do |key, value, opt|
|
193
|
+
if key
|
194
|
+
opt == :not ?
|
195
|
+
@model.build_not_match(key, value) :
|
196
|
+
@model.build_match(key, value)
|
197
|
+
else
|
198
|
+
value
|
199
|
+
end
|
200
|
+
end.join(" AND ")
|
201
|
+
end
|
202
|
+
|
203
|
+
def build_update(**cond)
|
204
|
+
cond.map do |key, value|
|
205
|
+
for_clause=""
|
206
|
+
if value.is_a?(Hash) && value[:_for]
|
207
|
+
path_clause = value.delete(:_for)
|
208
|
+
var_clause = path_clause.to_s.split(".").last.singularize
|
209
|
+
|
210
|
+
_when = value.delete(:_when)
|
211
|
+
when_clause = _when ? build_conds(_when.to_a) : ""
|
212
|
+
|
213
|
+
_set = value.delete(:_set)
|
214
|
+
value = _set if _set
|
215
|
+
|
216
|
+
for_clause = " for #{var_clause} in #{path_clause} when #{when_clause} end"
|
217
|
+
end
|
218
|
+
if value.is_a?(Hash)
|
219
|
+
value.map do |k, v|
|
220
|
+
"#{key}.#{k} = #{v}"
|
221
|
+
end.join(", ") + for_clause
|
222
|
+
else
|
223
|
+
"#{key} = #{@model.quote(value)}#{for_clause}"
|
224
|
+
end
|
225
|
+
end.join(", ")
|
226
|
+
end
|
227
|
+
|
228
|
+
def method_missing(method, *args, &block)
|
229
|
+
if @model.respond_to?(method)
|
230
|
+
scoping {
|
231
|
+
@model.public_send(method, *args, &block)
|
232
|
+
}
|
233
|
+
else
|
234
|
+
super
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
module ClassMethods
|
240
|
+
def relation
|
241
|
+
Thread.current[self.name]&.last || CouchbaseOrm_Relation.new(model: self)
|
242
|
+
end
|
243
|
+
|
244
|
+
delegate :ids, :update_all, :delete_all, :count, :empty?, :filter, :reduce, :find_by, to: :all
|
245
|
+
|
246
|
+
delegate :where, :not, :order, :limit, :all, :strict_loading, :strict_loading?, to: :relation
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module StrictLoading
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
7
|
+
end
|
8
|
+
|
9
|
+
def init_strict_loading
|
10
|
+
@strict_loading = self.class.strict_loading_by_default
|
11
|
+
end
|
12
|
+
|
13
|
+
def strict_loading!
|
14
|
+
@strict_loading = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def strict_loading?
|
18
|
+
!!@strict_loading
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CouchbaseOrm
|
4
|
+
module Timestamps
|
5
|
+
# This module handles the behavior for setting up document created at
|
6
|
+
# timestamp.
|
7
|
+
module Created
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
set_callback :create, :before, -> {
|
12
|
+
return if created_at
|
13
|
+
|
14
|
+
time = Time.current
|
15
|
+
self.created_at = time
|
16
|
+
}, if: -> { attributes.has_key?('created_at')}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CouchbaseOrm
|
4
|
+
module Timestamps
|
5
|
+
# This module handles the behavior for setting up document updated at
|
6
|
+
# timestamp.
|
7
|
+
module Updated
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
set_callback :save, :before, -> {
|
12
|
+
return if frozen?
|
13
|
+
return unless changed? || new_record?
|
14
|
+
|
15
|
+
time = Time.current
|
16
|
+
self.updated_at = time if !updated_at_changed?
|
17
|
+
}, if: -> { attributes.has_key? 'updated_at' }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "couchbase-orm/timestamps/created"
|
4
|
+
require "couchbase-orm/timestamps/updated"
|
5
|
+
|
6
|
+
module CouchbaseOrm
|
7
|
+
|
8
|
+
# This module handles the behavior for setting up document created at and
|
9
|
+
# updated at timestamps.
|
10
|
+
module Timestamps
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
include Created
|
13
|
+
include Updated
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module Types
|
3
|
+
class Array < ActiveModel::Type::Value
|
4
|
+
attr_reader :type_class
|
5
|
+
attr_reader :model_class
|
6
|
+
|
7
|
+
def initialize(type: nil)
|
8
|
+
if type.is_a?(Class) && type < CouchbaseOrm::NestedDocument
|
9
|
+
@model_class = type
|
10
|
+
@type_class = CouchbaseOrm::Types::Nested.new(type: @model_class)
|
11
|
+
else
|
12
|
+
@type_class = ActiveModel::Type.registry.lookup(type)
|
13
|
+
end
|
14
|
+
super()
|
15
|
+
end
|
16
|
+
|
17
|
+
def cast(values)
|
18
|
+
return [] if values.nil?
|
19
|
+
|
20
|
+
raise ArgumentError, "#{values.inspect} must be an array" unless values.is_a?(::Array)
|
21
|
+
|
22
|
+
values.map(&@type_class.method(:cast))
|
23
|
+
end
|
24
|
+
|
25
|
+
def serialize(values)
|
26
|
+
return [] if values.nil?
|
27
|
+
|
28
|
+
values.map(&@type_class.method(:serialize))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module Types
|
3
|
+
class DateTime < ActiveModel::Type::DateTime
|
4
|
+
def cast(value)
|
5
|
+
value = Time.at(value) if value.is_a?(Float) || value.is_a?(Integer)
|
6
|
+
super(value)&.utc
|
7
|
+
end
|
8
|
+
|
9
|
+
def serialize(value)
|
10
|
+
value&.iso8601(@precision)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module Types
|
3
|
+
class Encrypted < ActiveModel::Type::Value
|
4
|
+
attr_reader :alg
|
5
|
+
|
6
|
+
def initialize(alg: "CB_MOBILE_CUSTOM")
|
7
|
+
@alg = alg
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(value)
|
12
|
+
return nil if value.nil?
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class NestedValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
if value.is_a?(Array)
|
4
|
+
record.errors.add attribute, (options[:message] || "is invalid") unless value.map(&:valid?).all?
|
5
|
+
else
|
6
|
+
record.errors.add attribute, (options[:message] || "is invalid") unless
|
7
|
+
value.nil? || value.valid?
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module CouchbaseOrm
|
14
|
+
module Types
|
15
|
+
class Nested < ActiveModel::Type::Value
|
16
|
+
attr_reader :model_class
|
17
|
+
|
18
|
+
def initialize(type:)
|
19
|
+
raise ArgumentError, "type is nil" if type.nil?
|
20
|
+
raise ArgumentError, "type is not a class : #{type.inspect}" unless type.is_a?(Class)
|
21
|
+
|
22
|
+
@model_class = type
|
23
|
+
super()
|
24
|
+
end
|
25
|
+
|
26
|
+
def cast(value)
|
27
|
+
return nil if value.nil?
|
28
|
+
return value if value.is_a?(@model_class)
|
29
|
+
return @model_class.new(value) if value.is_a?(Hash)
|
30
|
+
|
31
|
+
raise ArgumentError, "Nested: #{value.inspect} (#{value.class}) is not supported for cast"
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialize(value)
|
35
|
+
return nil if value.nil?
|
36
|
+
value = @model_class.new(value) if value.is_a?(Hash)
|
37
|
+
return value.send(:serialized_attributes) if value.is_a?(@model_class)
|
38
|
+
|
39
|
+
raise ArgumentError, "Nested: #{value.inspect} (#{value.class}) is not supported for serialization"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CouchbaseOrm
|
2
|
+
module Types
|
3
|
+
class Timestamp < ActiveModel::Type::DateTime
|
4
|
+
def cast(value)
|
5
|
+
return nil if value.nil?
|
6
|
+
return Time.at(value) if value.is_a?(Integer) || value.is_a?(Float)
|
7
|
+
return Time.at(value.to_i) if value.is_a?(String) && value =~ /^[0-9]+$/
|
8
|
+
return value.utc if value.is_a?(Time)
|
9
|
+
super(value).utc
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
value&.to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "couchbase-orm/types/date"
|
2
|
+
require "couchbase-orm/types/date_time"
|
3
|
+
require "couchbase-orm/types/timestamp"
|
4
|
+
require "couchbase-orm/types/array"
|
5
|
+
require "couchbase-orm/types/nested"
|
6
|
+
require "couchbase-orm/types/encrypted"
|
7
|
+
|
8
|
+
if ActiveModel::VERSION::MAJOR < 6
|
9
|
+
# In Rails 5, the type system cannot allow overriding the default types
|
10
|
+
ActiveModel::Type.registry.instance_variable_get(:@registrations).delete_if do |k|
|
11
|
+
k.matches?(:date) || k.matches?(:datetime) || k.matches?(:timestamp)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveModel::Type.register(:date, CouchbaseOrm::Types::Date)
|
16
|
+
ActiveModel::Type.register(:datetime, CouchbaseOrm::Types::DateTime)
|
17
|
+
ActiveModel::Type.register(:timestamp, CouchbaseOrm::Types::Timestamp)
|
18
|
+
ActiveModel::Type.register(:array, CouchbaseOrm::Types::Array)
|
19
|
+
ActiveModel::Type.register(:nested, CouchbaseOrm::Types::Nested)
|
20
|
+
ActiveModel::Type.register(:encrypted, CouchbaseOrm::Types::Encrypted)
|
@@ -27,7 +27,19 @@ module CouchbaseOrm
|
|
27
27
|
else
|
28
28
|
default_value = 1
|
29
29
|
end
|
30
|
-
attribute name, default: default_value
|
30
|
+
attribute name, :integer, default: default_value
|
31
|
+
|
32
|
+
define_method "#{name}=" do |value|
|
33
|
+
unless value.nil?
|
34
|
+
value = case value
|
35
|
+
when Symbol, String
|
36
|
+
self.class.const_get(name.to_s.upcase)[value.to_sym]
|
37
|
+
else
|
38
|
+
Integer(value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
super(value)
|
42
|
+
end
|
31
43
|
|
32
44
|
# keep the attribute's value within bounds
|
33
45
|
before_save do |record|
|