couchbase-orm 1.1.1 → 2.0.0

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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d5a44e4dcc24a7d2e0d4dfdac8ea6cf2657cbf8cd2edf6499ab8ac002b82192
4
- data.tar.gz: ff4554e430770fc9d69f83247e6089ed8ec2874afd2f5cc3543bd21ef7c9ae5c
3
+ metadata.gz: 65b1d614fe648dace9781d689ac5583108b6acfcf216f6da479a66d710608048
4
+ data.tar.gz: 4cbb6ff2c0e59445ae03d908c07b2d21bbc12f34f3cf02d68248390b66bea3c6
5
5
  SHA512:
6
- metadata.gz: 50bf631a6df22796e99a6fc7a6d1c49b1a00dd19fbbab58e4035286b2388fb597168b2cdbfd335f8a6f89028526ac7f47c005d30d49aac19064b6d7bd67d87b4
7
- data.tar.gz: 1b2c61ce24033f54779fa56e5ef51e3c12a9f8c3e603fa8b00c4b7837db5289d032a2041b4e72d45dc032383fe6d8d710c822a1814bac75ad3be03269c36dc87
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
@@ -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