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.
Files changed (88) 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/LICENSE +201 -24
  8. data/README.md +248 -35
  9. data/ci/run_couchbase.sh +22 -0
  10. data/couchbase-orm.gemspec +26 -20
  11. data/lib/couchbase-orm/active_record_compat.rb +92 -0
  12. data/lib/couchbase-orm/associations.rb +119 -0
  13. data/lib/couchbase-orm/base.rb +143 -166
  14. data/lib/couchbase-orm/changeable.rb +512 -0
  15. data/lib/couchbase-orm/connection.rb +28 -8
  16. data/lib/couchbase-orm/encrypt.rb +48 -0
  17. data/lib/couchbase-orm/error.rb +17 -2
  18. data/lib/couchbase-orm/inspectable.rb +37 -0
  19. data/lib/couchbase-orm/json_schema/json_validation_error.rb +13 -0
  20. data/lib/couchbase-orm/json_schema/loader.rb +47 -0
  21. data/lib/couchbase-orm/json_schema/validation.rb +18 -0
  22. data/lib/couchbase-orm/json_schema/validator.rb +45 -0
  23. data/lib/couchbase-orm/json_schema.rb +9 -0
  24. data/lib/couchbase-orm/json_transcoder.rb +27 -0
  25. data/lib/couchbase-orm/locale/en.yml +5 -0
  26. data/lib/couchbase-orm/n1ql.rb +133 -0
  27. data/lib/couchbase-orm/persistence.rb +61 -52
  28. data/lib/couchbase-orm/proxies/bucket_proxy.rb +36 -0
  29. data/lib/couchbase-orm/proxies/collection_proxy.rb +52 -0
  30. data/lib/couchbase-orm/proxies/n1ql_proxy.rb +40 -0
  31. data/lib/couchbase-orm/proxies/results_proxy.rb +23 -0
  32. data/lib/couchbase-orm/railtie.rb +6 -17
  33. data/lib/couchbase-orm/relation.rb +249 -0
  34. data/lib/couchbase-orm/strict_loading.rb +21 -0
  35. data/lib/couchbase-orm/timestamps/created.rb +20 -0
  36. data/lib/couchbase-orm/timestamps/updated.rb +21 -0
  37. data/lib/couchbase-orm/timestamps.rb +15 -0
  38. data/lib/couchbase-orm/types/array.rb +32 -0
  39. data/lib/couchbase-orm/types/date.rb +9 -0
  40. data/lib/couchbase-orm/types/date_time.rb +14 -0
  41. data/lib/couchbase-orm/types/encrypted.rb +17 -0
  42. data/lib/couchbase-orm/types/nested.rb +43 -0
  43. data/lib/couchbase-orm/types/timestamp.rb +18 -0
  44. data/lib/couchbase-orm/types.rb +20 -0
  45. data/lib/couchbase-orm/utilities/enum.rb +13 -1
  46. data/lib/couchbase-orm/utilities/has_many.rb +72 -36
  47. data/lib/couchbase-orm/utilities/ignored_properties.rb +15 -0
  48. data/lib/couchbase-orm/utilities/index.rb +18 -20
  49. data/lib/couchbase-orm/utilities/properties_always_exists_in_document.rb +16 -0
  50. data/lib/couchbase-orm/utilities/query_helper.rb +148 -0
  51. data/lib/couchbase-orm/utils.rb +25 -0
  52. data/lib/couchbase-orm/version.rb +1 -1
  53. data/lib/couchbase-orm/views.rb +38 -41
  54. data/lib/couchbase-orm.rb +44 -9
  55. data/lib/ext/query_n1ql.rb +124 -0
  56. data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +3 -2
  57. data/spec/associations_spec.rb +219 -50
  58. data/spec/base_spec.rb +296 -14
  59. data/spec/collection_proxy_spec.rb +29 -0
  60. data/spec/connection_spec.rb +27 -0
  61. data/spec/couchbase-orm/active_record_compat_spec.rb +24 -0
  62. data/spec/couchbase-orm/changeable_spec.rb +16 -0
  63. data/spec/couchbase-orm/json_schema/validation_spec.rb +23 -0
  64. data/spec/couchbase-orm/json_schema/validator_spec.rb +13 -0
  65. data/spec/couchbase-orm/timestamps_spec.rb +85 -0
  66. data/spec/couchbase-orm/timestamps_spec_models.rb +36 -0
  67. data/spec/empty-json-schema/.gitkeep +0 -0
  68. data/spec/enum_spec.rb +34 -0
  69. data/spec/has_many_spec.rb +101 -54
  70. data/spec/index_spec.rb +13 -9
  71. data/spec/json-schema/JsonSchemaBaseTest.json +19 -0
  72. data/spec/json-schema/entity_snakecase.json +20 -0
  73. data/spec/json-schema/loader_spec.rb +42 -0
  74. data/spec/json-schema/specific_path.json +20 -0
  75. data/spec/json_schema_spec.rb +178 -0
  76. data/spec/n1ql_spec.rb +193 -0
  77. data/spec/persistence_spec.rb +49 -9
  78. data/spec/relation_nested_spec.rb +88 -0
  79. data/spec/relation_spec.rb +430 -0
  80. data/spec/support.rb +16 -8
  81. data/spec/type_array_spec.rb +52 -0
  82. data/spec/type_encrypted_spec.rb +114 -0
  83. data/spec/type_nested_spec.rb +191 -0
  84. data/spec/type_spec.rb +317 -0
  85. data/spec/utilities/ignored_properties_spec.rb +20 -0
  86. data/spec/utilities/properties_always_exists_in_document_spec.rb +24 -0
  87. data/spec/views_spec.rb +32 -11
  88. 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.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|