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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +45 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +3 -2
  5. data/CODEOWNERS +1 -0
  6. data/Gemfile +5 -3
  7. data/README.md +237 -31
  8. data/ci/run_couchbase.sh +22 -0
  9. data/couchbase-orm.gemspec +26 -20
  10. data/lib/couchbase-orm/active_record_compat.rb +92 -0
  11. data/lib/couchbase-orm/associations.rb +119 -0
  12. data/lib/couchbase-orm/base.rb +143 -166
  13. data/lib/couchbase-orm/changeable.rb +512 -0
  14. data/lib/couchbase-orm/connection.rb +28 -8
  15. data/lib/couchbase-orm/encrypt.rb +48 -0
  16. data/lib/couchbase-orm/error.rb +17 -2
  17. data/lib/couchbase-orm/inspectable.rb +37 -0
  18. data/lib/couchbase-orm/json_schema/json_validation_error.rb +13 -0
  19. data/lib/couchbase-orm/json_schema/loader.rb +47 -0
  20. data/lib/couchbase-orm/json_schema/validation.rb +18 -0
  21. data/lib/couchbase-orm/json_schema/validator.rb +45 -0
  22. data/lib/couchbase-orm/json_schema.rb +9 -0
  23. data/lib/couchbase-orm/json_transcoder.rb +27 -0
  24. data/lib/couchbase-orm/locale/en.yml +5 -0
  25. data/lib/couchbase-orm/n1ql.rb +133 -0
  26. data/lib/couchbase-orm/persistence.rb +61 -52
  27. data/lib/couchbase-orm/proxies/bucket_proxy.rb +36 -0
  28. data/lib/couchbase-orm/proxies/collection_proxy.rb +52 -0
  29. data/lib/couchbase-orm/proxies/n1ql_proxy.rb +40 -0
  30. data/lib/couchbase-orm/proxies/results_proxy.rb +23 -0
  31. data/lib/couchbase-orm/railtie.rb +6 -17
  32. data/lib/couchbase-orm/relation.rb +249 -0
  33. data/lib/couchbase-orm/strict_loading.rb +21 -0
  34. data/lib/couchbase-orm/timestamps/created.rb +20 -0
  35. data/lib/couchbase-orm/timestamps/updated.rb +21 -0
  36. data/lib/couchbase-orm/timestamps.rb +15 -0
  37. data/lib/couchbase-orm/types/array.rb +32 -0
  38. data/lib/couchbase-orm/types/date.rb +9 -0
  39. data/lib/couchbase-orm/types/date_time.rb +14 -0
  40. data/lib/couchbase-orm/types/encrypted.rb +17 -0
  41. data/lib/couchbase-orm/types/nested.rb +43 -0
  42. data/lib/couchbase-orm/types/timestamp.rb +18 -0
  43. data/lib/couchbase-orm/types.rb +20 -0
  44. data/lib/couchbase-orm/utilities/enum.rb +13 -1
  45. data/lib/couchbase-orm/utilities/has_many.rb +72 -36
  46. data/lib/couchbase-orm/utilities/ignored_properties.rb +15 -0
  47. data/lib/couchbase-orm/utilities/index.rb +18 -20
  48. data/lib/couchbase-orm/utilities/properties_always_exists_in_document.rb +16 -0
  49. data/lib/couchbase-orm/utilities/query_helper.rb +148 -0
  50. data/lib/couchbase-orm/utils.rb +25 -0
  51. data/lib/couchbase-orm/version.rb +1 -1
  52. data/lib/couchbase-orm/views.rb +38 -41
  53. data/lib/couchbase-orm.rb +44 -9
  54. data/lib/ext/query_n1ql.rb +124 -0
  55. data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +3 -2
  56. data/spec/associations_spec.rb +219 -50
  57. data/spec/base_spec.rb +296 -14
  58. data/spec/collection_proxy_spec.rb +29 -0
  59. data/spec/connection_spec.rb +27 -0
  60. data/spec/couchbase-orm/active_record_compat_spec.rb +24 -0
  61. data/spec/couchbase-orm/changeable_spec.rb +16 -0
  62. data/spec/couchbase-orm/json_schema/validation_spec.rb +23 -0
  63. data/spec/couchbase-orm/json_schema/validator_spec.rb +13 -0
  64. data/spec/couchbase-orm/timestamps_spec.rb +85 -0
  65. data/spec/couchbase-orm/timestamps_spec_models.rb +36 -0
  66. data/spec/empty-json-schema/.gitkeep +0 -0
  67. data/spec/enum_spec.rb +34 -0
  68. data/spec/has_many_spec.rb +101 -54
  69. data/spec/index_spec.rb +13 -9
  70. data/spec/json-schema/JsonSchemaBaseTest.json +19 -0
  71. data/spec/json-schema/entity_snakecase.json +20 -0
  72. data/spec/json-schema/loader_spec.rb +42 -0
  73. data/spec/json-schema/specific_path.json +20 -0
  74. data/spec/json_schema_spec.rb +178 -0
  75. data/spec/n1ql_spec.rb +193 -0
  76. data/spec/persistence_spec.rb +49 -9
  77. data/spec/relation_nested_spec.rb +88 -0
  78. data/spec/relation_spec.rb +430 -0
  79. data/spec/support.rb +16 -8
  80. data/spec/type_array_spec.rb +52 -0
  81. data/spec/type_encrypted_spec.rb +114 -0
  82. data/spec/type_nested_spec.rb +191 -0
  83. data/spec/type_spec.rb +317 -0
  84. data/spec/utilities/ignored_properties_spec.rb +20 -0
  85. data/spec/utilities/properties_always_exists_in_document_spec.rb +24 -0
  86. data/spec/views_spec.rb +32 -11
  87. 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.couchbase = ActiveSupport::OrderedOptions.new
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
- # Initialize Couchbase Mode. This will look for a couchbase.yml in the
52
- # config directory and configure Couchbase connection appropriately.
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.couchbase.ensure_design_documents
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 ::Libcouchbase::Error::Timedout, ::Libcouchbase::Error::ConnectError, ::Libcouchbase::Error::NetworkError
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,9 @@
1
+ module CouchbaseOrm
2
+ module Types
3
+ class Date < ActiveModel::Type::Date
4
+ def serialize(value)
5
+ value&.iso8601
6
+ end
7
+ end
8
+ end
9
+ 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|