couchbase-orm 1.1.0 → 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 +5 -5
  2. data/.github/workflows/test.yml +45 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +5 -4
  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 +67 -50
  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 +23 -8
  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 +55 -8
  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 +34 -13
  87. metadata +192 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f2b06085f930736b6220efb0e13013cc287a7891
4
- data.tar.gz: b078737e87c3d752b317471fdf36b750b3a0d37d
2
+ SHA256:
3
+ metadata.gz: 65b1d614fe648dace9781d689ac5583108b6acfcf216f6da479a66d710608048
4
+ data.tar.gz: 4cbb6ff2c0e59445ae03d908c07b2d21bbc12f34f3cf02d68248390b66bea3c6
5
5
  SHA512:
6
- metadata.gz: e9e09cdc7885a995e5abff1dcd8282f1fda44060bda39d9690a1fc3a7ca909bc074816fecfde8e31cbbfd58ee582ef56de44f13b4d94991dc89fb9aa21a82d3e
7
- data.tar.gz: d4bbe1ca233678304c2e1f811140f970ee796031483d5bdcfe50d228994d5158d6053223b25278ea0bc270acb0f39d4aced787dc6f3b4280230ed24a2bfefce6
6
+ metadata.gz: b12fc3d20ac1acc47bac8b784b9f5de3b5113e916c4b68c3181684bc58cffa143202d122177b62821afea4d3af9e54d9a0c26d5a16a441b757cefcd4a3465435
7
+ data.tar.gz: e1a99aeaa12090047188a5f93b652e29d5e173e6a0f12422e1f42a252bda3229651cc9f550ac33dbeb0784f564e71b620a50c6967b375e882db3d005e2b0358d
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ strategy:
12
+ matrix:
13
+ include:
14
+ - ruby: '3.2'
15
+ gemfile: '7.1.0'
16
+ couchbase: '7.1.1'
17
+ - ruby: '3.0'
18
+ gemfile: '7.0.0'
19
+ couchbase: '6.6.5'
20
+ - ruby: '3.0'
21
+ gemfile: '7.0.0'
22
+ couchbase: '7.1.0'
23
+ - ruby: '2.7'
24
+ gemfile: '7.0.0'
25
+ couchbase: '7.1.0'
26
+ fail-fast: false
27
+ runs-on: ubuntu-20.04
28
+ name: ${{ matrix.ruby }} rails-${{ matrix.gemfile }} couchbase-${{ matrix.couchbase }}
29
+ steps:
30
+ - uses: actions/checkout@v3
31
+ - run: sudo apt-get update && sudo apt-get install libevent-dev libev-dev python-httplib2
32
+ - uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: ${{ matrix.ruby }}
35
+ bundler-cache: true
36
+ - run: sudo ./ci/run_couchbase.sh $COUCHBASE_VERSION $COUCHBASE_BUCKET $COUCHBASE_USER $COUCHBASE_PASSWORD
37
+ - run: bundle exec rspec
38
+ env:
39
+ ACTIVE_MODEL_VERSION: ${{ matrix.gemfile }}
40
+ BUNDLE_JOBS: 4
41
+ BUNDLE_PATH: vendor/bundle
42
+ COUCHBASE_BUCKET: default
43
+ COUCHBASE_USER: tester
44
+ COUCHBASE_PASSWORD: password123
45
+ COUCHBASE_VERSION: ${{ matrix.couchbase }}
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  *.sw?
2
2
  .DS_Store
3
+ .rbenv-vars
3
4
  coverage
4
5
  rdoc
5
6
  html
@@ -14,5 +15,6 @@ Gemfile.lock
14
15
  .yardoc
15
16
  bin
16
17
  Gemfile-custom
18
+ vendor
17
19
 
18
20
  *.gem
data/.travis.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - ruby-2.4.2
4
- - ruby-2.3.5
4
+ - ruby-2.5.7
5
5
  - ruby-head
6
- - jruby-9.1.13.0
6
+ - jruby-9.2.9.0
7
7
  - jruby-head
8
8
  - rubinius
9
9
  - rubinius-3.86
@@ -14,8 +14,8 @@ before_install:
14
14
  - git submodule update --init --recursive
15
15
  - gem install ffi
16
16
  - sudo apt-get install libev-dev python-httplib2
17
- - sudo wget https://packages.couchbase.com/releases/5.0.0/couchbase-server-enterprise_5.0.0-ubuntu14.04_amd64.deb
18
- - sudo dpkg -i couchbase-server-enterprise_5.0.0-ubuntu14.04_amd64.deb
17
+ - sudo wget https://packages.couchbase.com/releases/5.1.0/couchbase-server-enterprise_5.1.0-ubuntu14.04_amd64.deb
18
+ - sudo dpkg -i couchbase-server-enterprise_5.1.0-ubuntu14.04_amd64.deb
19
19
  - sleep 8
20
20
  - sudo service couchbase-server status
21
21
  - /opt/couchbase/bin/couchbase-cli cluster-init -c 127.0.0.1:8091 --cluster-username=admin --cluster-password=password --cluster-ramsize=320 --cluster-index-ramsize=256 --cluster-fts-ramsize=256 --services=data,index,query,fts
@@ -24,6 +24,7 @@ before_install:
24
24
  - /opt/couchbase/bin/couchbase-cli bucket-create -c 127.0.0.1:8091 -u admin -p password --bucket=default --bucket-type=couchbase --bucket-ramsize=160 --bucket-replica=0 --wait
25
25
  - sleep 1
26
26
  - /opt/couchbase/bin/couchbase-cli user-manage -c 127.0.0.1:8091 -u admin -p password --set --rbac-username tester --rbac-password password123 --rbac-name "Auto Tester" --roles admin --auth-domain local
27
+ - curl http://admin:password@localhost:8093/query/service -d 'statement=CREATE INDEX `default_type` ON `default`(`type`)'
27
28
  - export TRAVIS_TEST=true
28
29
  matrix:
29
30
  allow_failures:
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @doctolib/couchbase-guild
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
+ git_source(:github) do |repo_name|
2
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
3
+ "https://github.com/#{repo_name}.git"
4
+ end
1
5
  source "https://rubygems.org"
2
- gemspec
3
-
4
- gem "rubysl", :platform => :rbx
6
+ gemspec
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Couchbase ORM for Rails
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/acaprojects/couchbase-orm.svg)](http://travis-ci.org/acaprojects/couchbase-orm)
4
-
5
3
  ## Rails integration
6
4
 
7
5
  To generate config you can use `rails generate couchbase_orm:config`:
@@ -11,8 +9,9 @@ To generate config you can use `rails generate couchbase_orm:config`:
11
9
 
12
10
  It will generate this `config/couchbase.yml` for you:
13
11
 
12
+ ```yaml
14
13
  common: &common
15
- hosts: localhost
14
+ connection_string: couchbase://localhost
16
15
  username: dev_user
17
16
  password: dev_password
18
17
 
@@ -26,10 +25,25 @@ It will generate this `config/couchbase.yml` for you:
26
25
 
27
26
  # set these environment variables on your production server
28
27
  production:
29
- hosts: <%= ENV['COUCHBASE_HOST'] || ENV['COUCHBASE_HOSTS'] %>
30
- bucket: <%= ENV['COUCHBASE_BUCKET'] %>
31
- username: <%= ENV['COUCHBASE_USER'] %>
32
- password: <%= ENV['COUCHBASE_PASSWORD'] %>
28
+ connection_string: <%= ENV['COUCHBASE_CONNECTION_STRING'] %>
29
+ bucket: <%= ENV['COUCHBASE_BUCKET'] %>
30
+ username: <%= ENV['COUCHBASE_USER'] %>
31
+ password: <%= ENV['COUCHBASE_PASSWORD'] %>
32
+ ```
33
+
34
+ ## Setup without Rails
35
+
36
+ If you are not using Rails, you can configure couchbase-orm with an initializer:
37
+
38
+ ```ruby
39
+ # config/initializers/couchbase_orm.rb
40
+ CouchbaseOrm::Connection.config = {
41
+ connection_string: "couchbase://localhost"
42
+ username: "dev_user"
43
+ password: "dev_password"
44
+ bucket: "dev_bucket"
45
+ }
46
+ ```
33
47
 
34
48
  Views are generated on application load if they don't exist or mismatch.
35
49
  This works fine in production however by default in development models are lazy loaded.
@@ -37,16 +51,15 @@ This works fine in production however by default in development models are lazy
37
51
  # config/environments/development.rb
38
52
  config.eager_load = true
39
53
 
40
-
41
54
  ## Examples
42
55
 
43
56
  ```ruby
44
57
  require 'couchbase-orm'
45
58
 
46
59
  class Post < CouchbaseOrm::Base
47
- attribute :title, type: String
48
- attribute :body, type: String
49
- attribute :draft, type: Boolean
60
+ attribute :title, :string
61
+ attribute :body, :string
62
+ attribute :draft, :boolean
50
63
  end
51
64
 
52
65
  p = Post.new(id: 'hello-world',
@@ -69,16 +82,45 @@ You can also let the library generate the unique identifier for you:
69
82
  p.id #=> "post-abcDE34"
70
83
  ```
71
84
 
72
- You can define connection options on per model basis:
85
+ <!-- You can define connection options on per model basis:
73
86
 
74
87
  ```ruby
75
88
  class Post < CouchbaseOrm::Base
76
- attribute :title, type: String
77
- attribute :body, type: String
78
- attribute :draft, type: Boolean
89
+ attribute :title, :string
90
+ attribute :body, :string
91
+ attribute :draft, :boolean
79
92
 
80
93
  connect bucket: 'blog', password: ENV['BLOG_BUCKET_PASSWORD']
81
94
  end
95
+ ``` -->
96
+
97
+ ## Typing
98
+
99
+ The following types have been tested :
100
+
101
+ - :string
102
+ - :integer
103
+ - :float
104
+ - :boolean
105
+ - :date
106
+ - :datetime (stored as iso8601, use precision: n to store more decimal precision)
107
+ - :timestamp (stored as integer)
108
+ - :encrypted
109
+ - see <https://docs.couchbase.com/couchbase-lite/current/c/field-level-encryption.html>
110
+ - You must store a string that can be encoded in json (not binary data), use base64 if needed
111
+ - :array (see below)
112
+ - :nested (see below)
113
+
114
+ You can register other types in ActiveModel registry :
115
+
116
+ ```ruby
117
+ class DateTimeWith3Decimal < CouchbaseOrm::Types::DateTime
118
+ def serialize(value)
119
+ value&.iso8601(3)
120
+ end
121
+ end
122
+
123
+ ActiveModel::Type.register(:datetime3decimal, DateTimeWith3Decimal)
82
124
  ```
83
125
 
84
126
  ## Validations
@@ -89,7 +131,8 @@ context of rails application. You can also enforce types using ruby
89
131
 
90
132
  ```ruby
91
133
  class Comment < Couchbase::Model
92
- attribute :author, :body, type: String
134
+ attribute :author, :string
135
+ attribute :body, :string
93
136
 
94
137
  validates_presence_of :author, :body
95
138
  end
@@ -102,7 +145,8 @@ can then be used for filtering results or ordering.
102
145
 
103
146
  ```ruby
104
147
  class Comment < CouchbaseOrm::Base
105
- attribute :author, :body, type: String
148
+ attribute :author :string
149
+ attribute :body, :string
106
150
  view :all # => emits :id and will return all comments
107
151
  view :by_author, emit_key: :author
108
152
 
@@ -110,15 +154,12 @@ can then be used for filtering results or ordering.
110
154
  # * the by_author view above
111
155
  # * def find_by_author(author); end
112
156
  index_view :author
113
-
157
+
114
158
  # You can make compound keys by passing an array to :emit_key
115
159
  # this allow to query by read/unread comments
116
160
  view :by_read, emit_key: [:user_id, :read]
117
161
  # this allow to query by view_count
118
162
  view :by_view_count, emit_key: [:user_id, :view_count]
119
-      
120
-
121
-      
122
163
 
123
164
  validates_presence_of :author, :body
124
165
  end
@@ -128,20 +169,129 @@ You can use `Comment.find_by_author('name')` to obtain all the comments by
128
169
  a particular author. The same thing, using the view directly would be:
129
170
  `Comment.by_author(key: 'name')`
130
171
 
131
- When using a compound key, the usage is the same, you just give the full key :
172
+ When using a compound key, the usage is the same, you just give the full key :
132
173
 
133
174
  ```ruby
134
175
  Comment.by_read(key: '["'+user_id+'",false]') # gives all unread comments for one particular user
135
-
176
+
136
177
  # or even a range !
178
+
179
+ Comment.by_view_count(startkey: '["'+user_id+'",10]', endkey: '["'+user_id+'",20]')
137
180
 
138
- Comment.by_view_count(startkey: '["'+user_id+'",10]', endkey: '["'+user_id+'",20]') # gives all comments that have been seen more than 10 times but less than 20
181
+ # gives all comments that have been seen more than 10 times but less than 20
139
182
  ```
140
-
141
- Check this couchbase help page to learn more on what's possible with compound keys : https://developer.couchbase.com/documentation/server/3.x/admin/Views/views-translateSQL.html
183
+ Check this couchbase help page to learn more on what's possible with compound keys : <https://developer.couchbase.com/documentation/server/3.x/admin/Views/views-translateSQL.html>
142
184
 
143
185
  Ex : Compound keys allows to decide the order of the results, and you can reverse it by passing `descending: true`
144
186
 
187
+ ```ruby
188
+ class Comment < CouchbaseOrm::Base19
189
+ self.ignored_properties = [:old_name] # ignore old_name property in the model
190
+ self.properties_always_exists_in_document = true # use is null for nil value instead of not valued for performance purpose, only possible if all properties always exists in document
191
+ end
192
+ ```
193
+ You can specify `properties_always_exists_in_document` to true if all properties always exists in document, this will allow to use `is null` instead of `not valued` for nil value, this will improve performance.
194
+
195
+ WARNING: If a document exists without a property, the query will failed! So you must be sure that all documents have all properties.
196
+
197
+
198
+ ## N1ql
199
+
200
+ Like views, it's possible to use N1QL to process some requests used for filtering results or ordering.
201
+
202
+ ```ruby
203
+ class Comment < CouchbaseOrm::Base
204
+ attribute :author, :string
205
+ attribute :body, :string
206
+ n1ql :by_author, emit_key: :author
207
+
208
+ # Generates two functions:
209
+ # * the by_author view above
210
+ # * def find_by_author(author); end
211
+ index_n1ql :author
212
+
213
+ # You can make compound keys by passing an array to :emit_key
214
+ # this allow to query by read/unread comments
215
+ n1ql :by_read, emit_key: [:user_id, :read]
216
+ # this allow to query by view_count
217
+ n1ql :by_view_count, emit_key: [:user_id, :view_count]
218
+
219
+ validates_presence_of :author, :body
220
+ end
221
+ ```
222
+
223
+ ## Basic Active Record like query engine
224
+
225
+ ```ruby
226
+ class Comment < CouchbaseOrm::Base
227
+ attribute :title, :string
228
+ attribute :author, :string
229
+ attribute :category, :string
230
+ attribute :ratings, :number
231
+ end
232
+
233
+ Comment.where(author: "Anne McCaffrey", category: ['S-F', 'Fantasy']).not(ratings: 0).order(:title).limit(10)
234
+
235
+ # Relation can be composed as in AR:
236
+
237
+ amc_comments = Comment.where(author: "Anne McCaffrey")
238
+
239
+ amc_comments.count
240
+
241
+ amc_sf_comments = amc_comments.where(category: 'S-F')
242
+
243
+ # pluck is available, but will query all object fields first
244
+
245
+ Comment.pluck(:title, :ratings)
246
+
247
+ # To load the ids without loading the models
248
+
249
+ Comment.where(author: "David Eddings").ids
250
+
251
+ # To delete all the models of a relation
252
+
253
+ Comment.where(ratings: 0).delete_all
254
+ ```
255
+
256
+ ## scopes
257
+
258
+ Scopes can be written as class method, scope method is not implemented yet.
259
+ They can be chained as in AR or mixed with relation methods.
260
+
261
+ ```ruby
262
+ class Comment < CouchbaseOrm::Base
263
+ attribute :title, :string
264
+ attribute :author, :string
265
+ attribute :category, :string
266
+ attribute :ratings, :number
267
+
268
+ def self.by_author(author)
269
+ where(author: author)
270
+ end
271
+ end
272
+
273
+ Comment.by_author("Anne McCaffrey").where(category: 'S-F').not(ratings: 0).order(:title).limit(10)
274
+ ```
275
+
276
+ ## Operators
277
+
278
+ Several operators are available to filter numerical results : \_gt, \_lt, \_gte, \_lte, \_ne
279
+
280
+ ```ruby
281
+ Comment.where(ratings: {_gt: 3})
282
+ ```
283
+
284
+ ## Range in the where
285
+
286
+ You can specify a Range of date or intger in the where clause
287
+
288
+ ```ruby
289
+ Person.where(birth_date: DateTime.new(1980, 1, 1)..DateTime.new(1990, 1, 1))
290
+ Person.where(age: 10..20)
291
+
292
+ Person.where(age: 10...20) # to exclude the upper bound
293
+ ```
294
+
145
295
  ## Associations and Indexes
146
296
 
147
297
  There are common active record helpers available for use `belongs_to` and `has_many`
@@ -155,21 +305,77 @@ There are common active record helpers available for use `belongs_to` and `has_m
155
305
  has_many :comments, dependent: :destroy
156
306
 
157
307
  # You can ensure an attribute is unique for this model
158
- attribute :email, type: String
308
+ attribute :email, :string
159
309
  ensure_unique :email
160
310
  end
161
311
  ```
162
312
 
313
+ By default, `has_many` uses a view for association,
314
+ but you can define a `type` option to specify an association using N1QL instead:
315
+
316
+ ```ruby
317
+ class Comment < CouchbaseOrm::Base
318
+ belongs_to :author
319
+ end
320
+
321
+ class Author < CouchbaseOrm::Base
322
+ has_many :comments, type: :n1ql, dependent: :destroy
323
+ end
324
+ ```
325
+
326
+ ## Nested
327
+
328
+ Attributes can be of type nested, they must specify a type of NestedDocument.
329
+ The NestedValidation triggers nested validation on parent validation.
330
+
331
+ ```ruby
332
+ class Address < CouchbaseOrm::NestedDocument
333
+ attribute :road, :string
334
+ attribute :city, :string
335
+ validates :road, :city, presence: true
336
+ end
337
+
338
+ class Author < CouchbaseOrm::Base
339
+ attribute :address, :nested, type: Address
340
+ validates :address, nested: true
341
+ end
342
+ ```
343
+
344
+ Model can be queried using the nested attributes
345
+
346
+ ```ruby
347
+ Author.where(address: {road: '1 rue de la paix', city: 'Paris'})
348
+ ```
349
+
350
+ ## Array
351
+
352
+ Attributes can be of type array, they must contain something that can be serialized and deserialized to/from JSON.
353
+ You can enforce the type of array elements. The type can be a NestedDocument
354
+
355
+ ```ruby
356
+ class Book < CouchbaseOrm::NestedDocument
357
+ attribute :name, :string
358
+ validates :name, presence: true
359
+ end
360
+
361
+ class Author < CouchbaseOrm::Base
362
+ attribute things, :array
363
+ attribute flags, :array, type: :string
364
+ attribute books, :array, type: Book
365
+
366
+ validates :books, nested: true
367
+ end
368
+ ```
163
369
 
164
370
  ## Performance Comparison with Couchbase-Ruby-Model
165
371
 
166
372
  Basically we migrated an application from [Couchbase Ruby Model](https://github.com/couchbase/couchbase-ruby-model)
167
373
  to [Couchbase-ORM](https://github.com/acaprojects/couchbase-orm) (this project)
168
374
 
169
- * Rails 5 production
170
- * Puma as the webserver
171
- * Running on a 2015 Macbook Pro
172
- * Performance test: `siege -c250 -r10 http://localhost:3000/auth/authority`
375
+ - Rails 5 production
376
+ - Puma as the webserver
377
+ - Running on a 2015 Macbook Pro
378
+ - Performance test: `siege -c250 -r10 http://localhost:3000/auth/authority`
173
379
 
174
380
  The request above pulls the same database document each time and returns it. A simple O(1) operation.
175
381
 
@@ -0,0 +1,22 @@
1
+ set -x
2
+ set -e
3
+
4
+ VERSION=$1
5
+ BUCKET=$2
6
+ USER=$3
7
+ PASSWORD=$4
8
+
9
+
10
+ wget https://packages.couchbase.com/releases/$VERSION/couchbase-server-enterprise_$VERSION-ubuntu20.04_amd64.deb
11
+ dpkg -i couchbase-server-enterprise_$VERSION-ubuntu20.04_amd64.deb
12
+ sleep 8
13
+ sudo service couchbase-server status
14
+ /opt/couchbase/bin/couchbase-cli cluster-init -c 127.0.0.1:8091 --cluster-username=admin --cluster-password=password --cluster-ramsize=320 --cluster-index-ramsize=256 --cluster-fts-ramsize=256 --services=data,index,query,fts
15
+ sleep 5
16
+ /opt/couchbase/bin/couchbase-cli server-info -c 127.0.0.1:8091 -u admin -p password
17
+ /opt/couchbase/bin/couchbase-cli bucket-create -c 127.0.0.1:8091 -u admin -p password --bucket=$BUCKET --bucket-type=couchbase --bucket-ramsize=160 --bucket-replica=0 --wait
18
+ sleep 1
19
+ /opt/couchbase/bin/couchbase-cli user-manage -c 127.0.0.1:8091 -u admin -p password --set --rbac-username $USER --rbac-password $PASSWORD --rbac-name "Auto Tester" --roles admin --auth-domain local
20
+ curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_type\` ON \`$BUCKET\`(\`type\`)"
21
+ curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_rating\` ON \`$BUCKET\`(\`rating\`)"
22
+ curl http://admin:password@localhost:8093/query/service -d "statement=CREATE INDEX \`default_name\` ON \`$BUCKET\`(\`name\`)"
@@ -1,27 +1,33 @@
1
- require File.expand_path("../lib/couchbase-orm/version", __FILE__)
1
+ require File.expand_path('../lib/couchbase-orm/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.name = "couchbase-orm"
5
- gem.version = CouchbaseOrm::VERSION
6
- gem.license = 'MIT'
7
- gem.authors = ["Stephen von Takach"]
8
- gem.email = ["steve@cotag.me"]
9
- gem.homepage = "https://github.com/cotag/couchbase-orm"
10
- gem.summary = "Couchbase ORM for Rails"
11
- gem.description = "A Couchbase ORM for Rails"
4
+ gem.name = 'couchbase-orm'
5
+ gem.version = CouchbaseOrm::VERSION
6
+ gem.license = 'MIT'
7
+ gem.authors = ['Stephen von Takach', 'Gauthier Monserand', 'Pierre Merlin', 'Julien Burnet-Fauche']
8
+ gem.homepage = 'https://github.com/doctolib/couchbase-orm'
9
+ gem.summary = 'Couchbase ORM for Rails'
10
+ gem.description = 'A Couchbase ORM for Rails'
12
11
 
13
- gem.required_ruby_version = '>= 2.1.0'
14
- gem.require_paths = ["lib"]
12
+ gem.required_ruby_version = '>= 2.7.0'
13
+ gem.require_paths = ['lib']
15
14
 
16
- gem.add_runtime_dependency 'libcouchbase', '~> 1.2'
17
- gem.add_runtime_dependency 'activemodel', '>= 4.0', '< 6.0'
18
- gem.add_runtime_dependency 'radix', '~> 2.2' # converting numbers to and from any base
15
+ gem.add_runtime_dependency 'activemodel', ENV['ACTIVE_MODEL_VERSION'] || '>= 5.2'
19
16
 
20
- gem.add_development_dependency 'rake', '~> 12.2'
21
- gem.add_development_dependency 'rspec', '~> 3.7'
22
- gem.add_development_dependency 'yard', '~> 0.9'
23
- gem.add_development_dependency 'minitest', '~> 5.10'
17
+ gem.add_runtime_dependency 'couchbase', '>= 3.4.2'
18
+ gem.add_runtime_dependency 'radix', '~> 2.2' # converting numbers to and from any base
19
+ gem.add_runtime_dependency 'json-schema', '>= 3' # validating JSON against a schema
24
20
 
25
- gem.files = `git ls-files`.split("\n")
26
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ gem.add_development_dependency 'rake', '~> 12.2'
22
+ gem.add_development_dependency 'rspec', '~> 3.7'
23
+ gem.add_development_dependency 'yard', '~> 0.9'
24
+ gem.add_development_dependency 'pry'
25
+ gem.add_development_dependency 'pry-stack_explorer'
26
+ gem.add_development_dependency 'simplecov'
27
+ gem.add_development_dependency 'actionpack'
28
+ gem.add_development_dependency 'timecop'
29
+ gem.add_development_dependency 'base64'
30
+
31
+ gem.files = `git ls-files`.split("\n")
32
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
33
  end
@@ -0,0 +1,92 @@
1
+ require 'active_support/concern'
2
+ require 'active_model'
3
+ require 'active_support/hash_with_indifferent_access'
4
+
5
+ module CouchbaseOrm
6
+
7
+ # Set of methods defined in ActiveRecord and needed for CouchbaseOrm
8
+ # try to avoid dependencies on too many active record classes
9
+ # by exemple we don't want to go down to the concept of tables
10
+ module ActiveRecordCompat
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ module ClassMethods
15
+
16
+ def primary_key
17
+ 'id'
18
+ end
19
+
20
+ def base_class?
21
+ true
22
+ end
23
+
24
+ def column_names # can't be an alias for now
25
+ attribute_names
26
+ end
27
+
28
+ def abstract_class?
29
+ false
30
+ end
31
+
32
+ def connected?
33
+ true
34
+ end
35
+
36
+ def table_exists?
37
+ true
38
+ end
39
+
40
+ def _reflect_on_association(_attribute)
41
+ false
42
+ end
43
+
44
+ def type_for_attribute(attribute)
45
+ attribute_types[attribute]
46
+ end
47
+
48
+ if ActiveModel::VERSION::MAJOR < 6
49
+ def attribute_names
50
+ attribute_types.keys
51
+ end
52
+ end
53
+ end
54
+
55
+ def slice(*methods)
56
+ HashWithIndifferentAccess.new(methods.flatten.to_h { |method| [method, public_send(method)] })
57
+ end
58
+
59
+ def values_at(*methods)
60
+ methods.flatten.map! { |method| public_send(method) }
61
+ end
62
+
63
+ def _has_attribute?(attr_name)
64
+ attribute_names.include?(attr_name.to_s)
65
+ end
66
+
67
+ def attribute_for_inspect(attr_name)
68
+ value = send(attr_name)
69
+ value.inspect
70
+ end
71
+
72
+ if ActiveModel::VERSION::MAJOR < 6
73
+ def attribute_names
74
+ self.class.attribute_names
75
+ end
76
+
77
+ def has_attribute?(attr_name)
78
+ @attributes.key?(attr_name.to_s)
79
+ end
80
+
81
+ def attribute_present?(attribute)
82
+ value = send(attribute)
83
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
84
+ end
85
+
86
+ def _write_attribute(attr_name, value)
87
+ @attributes.write_from_user(attr_name.to_s, value)
88
+ value
89
+ end
90
+ end
91
+ end
92
+ end