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
@@ -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|
|