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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +45 -0
- data/.gitignore +2 -0
- data/.travis.yml +3 -2
- data/CODEOWNERS +1 -0
- data/Gemfile +5 -3
- data/README.md +237 -31
- data/ci/run_couchbase.sh +22 -0
- data/couchbase-orm.gemspec +26 -20
- data/lib/couchbase-orm/active_record_compat.rb +92 -0
- data/lib/couchbase-orm/associations.rb +119 -0
- data/lib/couchbase-orm/base.rb +143 -166
- data/lib/couchbase-orm/changeable.rb +512 -0
- data/lib/couchbase-orm/connection.rb +28 -8
- data/lib/couchbase-orm/encrypt.rb +48 -0
- data/lib/couchbase-orm/error.rb +17 -2
- data/lib/couchbase-orm/inspectable.rb +37 -0
- data/lib/couchbase-orm/json_schema/json_validation_error.rb +13 -0
- data/lib/couchbase-orm/json_schema/loader.rb +47 -0
- data/lib/couchbase-orm/json_schema/validation.rb +18 -0
- data/lib/couchbase-orm/json_schema/validator.rb +45 -0
- data/lib/couchbase-orm/json_schema.rb +9 -0
- data/lib/couchbase-orm/json_transcoder.rb +27 -0
- data/lib/couchbase-orm/locale/en.yml +5 -0
- data/lib/couchbase-orm/n1ql.rb +133 -0
- data/lib/couchbase-orm/persistence.rb +61 -52
- data/lib/couchbase-orm/proxies/bucket_proxy.rb +36 -0
- data/lib/couchbase-orm/proxies/collection_proxy.rb +52 -0
- data/lib/couchbase-orm/proxies/n1ql_proxy.rb +40 -0
- data/lib/couchbase-orm/proxies/results_proxy.rb +23 -0
- data/lib/couchbase-orm/railtie.rb +6 -17
- data/lib/couchbase-orm/relation.rb +249 -0
- data/lib/couchbase-orm/strict_loading.rb +21 -0
- data/lib/couchbase-orm/timestamps/created.rb +20 -0
- data/lib/couchbase-orm/timestamps/updated.rb +21 -0
- data/lib/couchbase-orm/timestamps.rb +15 -0
- data/lib/couchbase-orm/types/array.rb +32 -0
- data/lib/couchbase-orm/types/date.rb +9 -0
- data/lib/couchbase-orm/types/date_time.rb +14 -0
- data/lib/couchbase-orm/types/encrypted.rb +17 -0
- data/lib/couchbase-orm/types/nested.rb +43 -0
- data/lib/couchbase-orm/types/timestamp.rb +18 -0
- data/lib/couchbase-orm/types.rb +20 -0
- data/lib/couchbase-orm/utilities/enum.rb +13 -1
- data/lib/couchbase-orm/utilities/has_many.rb +72 -36
- data/lib/couchbase-orm/utilities/ignored_properties.rb +15 -0
- data/lib/couchbase-orm/utilities/index.rb +18 -20
- data/lib/couchbase-orm/utilities/properties_always_exists_in_document.rb +16 -0
- data/lib/couchbase-orm/utilities/query_helper.rb +148 -0
- data/lib/couchbase-orm/utils.rb +25 -0
- data/lib/couchbase-orm/version.rb +1 -1
- data/lib/couchbase-orm/views.rb +38 -41
- data/lib/couchbase-orm.rb +44 -9
- data/lib/ext/query_n1ql.rb +124 -0
- data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +3 -2
- data/spec/associations_spec.rb +219 -50
- data/spec/base_spec.rb +296 -14
- data/spec/collection_proxy_spec.rb +29 -0
- data/spec/connection_spec.rb +27 -0
- data/spec/couchbase-orm/active_record_compat_spec.rb +24 -0
- data/spec/couchbase-orm/changeable_spec.rb +16 -0
- data/spec/couchbase-orm/json_schema/validation_spec.rb +23 -0
- data/spec/couchbase-orm/json_schema/validator_spec.rb +13 -0
- data/spec/couchbase-orm/timestamps_spec.rb +85 -0
- data/spec/couchbase-orm/timestamps_spec_models.rb +36 -0
- data/spec/empty-json-schema/.gitkeep +0 -0
- data/spec/enum_spec.rb +34 -0
- data/spec/has_many_spec.rb +101 -54
- data/spec/index_spec.rb +13 -9
- data/spec/json-schema/JsonSchemaBaseTest.json +19 -0
- data/spec/json-schema/entity_snakecase.json +20 -0
- data/spec/json-schema/loader_spec.rb +42 -0
- data/spec/json-schema/specific_path.json +20 -0
- data/spec/json_schema_spec.rb +178 -0
- data/spec/n1ql_spec.rb +193 -0
- data/spec/persistence_spec.rb +49 -9
- data/spec/relation_nested_spec.rb +88 -0
- data/spec/relation_spec.rb +430 -0
- data/spec/support.rb +16 -8
- data/spec/type_array_spec.rb +52 -0
- data/spec/type_encrypted_spec.rb +114 -0
- data/spec/type_nested_spec.rb +191 -0
- data/spec/type_spec.rb +317 -0
- data/spec/utilities/ignored_properties_spec.rb +20 -0
- data/spec/utilities/properties_always_exists_in_document_spec.rb +24 -0
- data/spec/views_spec.rb +32 -11
- metadata +192 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65b1d614fe648dace9781d689ac5583108b6acfcf216f6da479a66d710608048
|
4
|
+
data.tar.gz: 4cbb6ff2c0e59445ae03d908c07b2d21bbc12f34f3cf02d68248390b66bea3c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/.travis.yml
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
3
|
- ruby-2.4.2
|
4
|
-
- ruby-2.
|
4
|
+
- ruby-2.5.7
|
5
5
|
- ruby-head
|
6
|
-
- jruby-9.
|
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
data/README.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# Couchbase ORM for Rails
|
2
2
|
|
3
|
-
[](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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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,
|
48
|
-
attribute :body,
|
49
|
-
attribute :draft,
|
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,
|
77
|
-
attribute :body,
|
78
|
-
attribute :draft,
|
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, :
|
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
|
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
|
-
|
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,
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
|
data/ci/run_couchbase.sh
ADDED
@@ -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\`)"
|
data/couchbase-orm.gemspec
CHANGED
@@ -1,27 +1,33 @@
|
|
1
|
-
require File.expand_path(
|
1
|
+
require File.expand_path('../lib/couchbase-orm/version', __FILE__)
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
12
|
+
gem.required_ruby_version = '>= 2.7.0'
|
13
|
+
gem.require_paths = ['lib']
|
15
14
|
|
16
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
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
|