couchbase-orm 2.0.4 → 2.0.6
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/deploy.yml +45 -0
- data/.github/workflows/test.yml +18 -13
- data/README.md +17 -2
- data/couchbase-orm.gemspec +3 -2
- data/docusaurus/.gitignore +20 -0
- data/docusaurus/README.md +41 -0
- data/docusaurus/babel.config.js +3 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/01-introduction.md +49 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/02-installation.md +108 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/03-defining-models.md +239 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/04-querying.md +154 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/05-persistence.md +93 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/06-associations-and-validations.md +236 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/07-sqlpp-queries.md +180 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/08-views.md +158 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/09-nested-documents.md +138 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/10-enums.md +91 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/11-encryption.md +202 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/12-logging.md +48 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/13-troubleshooting.md +41 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/_category_.json +8 -0
- data/docusaurus/docusaurus.config.ts +122 -0
- data/docusaurus/package-lock.json +14540 -0
- data/docusaurus/package.json +47 -0
- data/docusaurus/sidebars.ts +31 -0
- data/docusaurus/src/components/HomepageFeatures/index.tsx +69 -0
- data/docusaurus/src/components/HomepageFeatures/styles.module.css +11 -0
- data/docusaurus/src/css/custom.css +30 -0
- data/docusaurus/src/pages/index.module.css +23 -0
- data/docusaurus/src/pages/index.tsx +43 -0
- data/docusaurus/src/pages/markdown-page.md +7 -0
- data/docusaurus/static/.nojekyll +0 -0
- data/docusaurus/static/CNAME +1 -0
- data/docusaurus/static/img/familiar.svg +1 -0
- data/docusaurus/static/img/fast.svg +1 -0
- data/docusaurus/static/img/logo.svg +1 -0
- data/docusaurus/static/img/undraw_docusaurus_mountain.svg +171 -0
- data/docusaurus/static/img/undraw_docusaurus_react.svg +170 -0
- data/docusaurus/static/img/undraw_docusaurus_tree.svg +40 -0
- data/docusaurus/tsconfig.json +7 -0
- data/lib/couchbase-orm/base.rb +1 -1
- data/lib/couchbase-orm/types/date_time.rb +2 -1
- data/lib/couchbase-orm/types/nested.rb +17 -2
- data/lib/couchbase-orm/utilities/query_helper.rb +1 -1
- data/lib/couchbase-orm/version.rb +1 -1
- data/spec/type_nested_spec.rb +60 -0
- metadata +57 -10
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Views (aka Map/Reduce indexes)
|
|
2
|
+
|
|
3
|
+
Views are a powerful feature in Couchbase that allow you to define map functions to extract and emit specific data from your documents. The Ruby Couchbase ORM provides a convenient way to define and query views within your Ruby application.
|
|
4
|
+
|
|
5
|
+
## 8.1 Defining Views
|
|
6
|
+
|
|
7
|
+
To define a view in your Ruby Couchbase ORM model, you can use the `view` method followed by the view name and options. Here's an example:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class Factory < CouchbaseOrm::Base
|
|
11
|
+
attribute :name, type: String
|
|
12
|
+
attribute :location, type: String
|
|
13
|
+
attribute :established_year, type: Integer
|
|
14
|
+
attribute :active, type: Boolean
|
|
15
|
+
|
|
16
|
+
view :by_name, emit_key: :name
|
|
17
|
+
view :by_location, emit_key: :location
|
|
18
|
+
view :by_established_year, emit_key: :established_year
|
|
19
|
+
view :by_active, emit_key: :active
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
In this example, we define four views for the `Factory` model:
|
|
24
|
+
- `by_name`: Emits the `name` attribute as the key.
|
|
25
|
+
- `by_location`: Emits the `location` attribute as the key.
|
|
26
|
+
- `by_established_year`: Emits the `established_year` attribute as the key.
|
|
27
|
+
- `by_active`: Emits the `active` attribute as the key.
|
|
28
|
+
|
|
29
|
+
The `emit_key` option specifies the attribute to use as the key for the view.
|
|
30
|
+
|
|
31
|
+
## 8.2 Custom Map Functions
|
|
32
|
+
|
|
33
|
+
You can also define custom map functions for your views using the `map` option. This allows you to emit custom keys and values based on your specific requirements. Here's an example:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
view :by_name_and_location,
|
|
37
|
+
map: %{
|
|
38
|
+
function(doc) {
|
|
39
|
+
if (doc.type === "factory") {
|
|
40
|
+
emit([doc.name, doc.location], null);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
In this example, we define a view named `by_name_and_location` that emits a composite key consisting of the `name` and `location` attributes.
|
|
47
|
+
|
|
48
|
+
## 8.3 Querying Views
|
|
49
|
+
|
|
50
|
+
Once you have defined your views, you can query them using the corresponding view methods. The view methods are automatically generated based on the view names. Here are some examples:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# Query factories by name
|
|
54
|
+
Factory.by_name(key: 'Factory A').each do |factory|
|
|
55
|
+
puts "- #{factory.name}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Query factories by location
|
|
59
|
+
Factory.by_location(key: 'City X').each do |factory|
|
|
60
|
+
puts "- #{factory.name}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Query factories by established year
|
|
64
|
+
Factory.by_established_year(key: 2010).each do |factory|
|
|
65
|
+
puts "- #{factory.name}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Query active factories
|
|
69
|
+
Factory.by_active(key: true).each do |factory|
|
|
70
|
+
puts "- #{factory.name}"
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
These examples demonstrate how to query views based on specific keys using the generated view methods.
|
|
75
|
+
|
|
76
|
+
## 8.4 Indexing Views
|
|
77
|
+
|
|
78
|
+
In this section, let's explore the `index_view` function and the methods it generates for the `location` attribute.
|
|
79
|
+
|
|
80
|
+
Here's an updated version of the `Factory` model using `index_view` for the `location` attribute:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class Factory < CouchbaseOrm::Base
|
|
84
|
+
attribute :name, type: String
|
|
85
|
+
attribute :location, type: String
|
|
86
|
+
attribute :established_year, type: Integer
|
|
87
|
+
attribute :active, type: Boolean
|
|
88
|
+
|
|
89
|
+
view :by_name, emit_key: :name
|
|
90
|
+
index_view :location
|
|
91
|
+
view :by_established_year, emit_key: :established_year
|
|
92
|
+
view :by_active, emit_key: :active
|
|
93
|
+
|
|
94
|
+
view :by_name_and_location,
|
|
95
|
+
map: %{
|
|
96
|
+
function(doc) {
|
|
97
|
+
if (doc.type === "factory") {
|
|
98
|
+
emit([doc.name, doc.location], null);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
view :by_established_year_range,
|
|
104
|
+
map: %{
|
|
105
|
+
function(doc) {
|
|
106
|
+
if (doc.type === "factory" && doc.established_year) {
|
|
107
|
+
emit(doc.established_year, null);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
In this updated code, we replaced `view :by_location, emit_key: :location` with `index_view :location`. The `index_view` function generates two methods for querying based on the `location` attribute:
|
|
115
|
+
|
|
116
|
+
1. `find_by_location(location)`: This method allows you to find factories by a specific location. It returns an array of factories that match the given location.
|
|
117
|
+
|
|
118
|
+
2. `by_location(key: location)`: This method is similar to `find_by_location` but returns a `CouchbaseOrm::ResultsProxy` object, which provides an enumerable interface to access the view results.
|
|
119
|
+
|
|
120
|
+
Now, let's see how we can use these generated methods to query factories by location:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Find factories by location using find_by_location
|
|
124
|
+
factories = Factory.find_by_location('City X')
|
|
125
|
+
factories.each do |factory|
|
|
126
|
+
puts "- #{factory.name} (#{factory.location})"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Find factories by location using by_location
|
|
130
|
+
Factory.by_location(key: 'City X').each do |factory|
|
|
131
|
+
puts "- #{factory.name} (#{factory.location})"
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Both `find_by_location` and `by_location` achieve the same result of finding factories by a specific location. The difference is that `find_by_location` returns an array of factories directly, while `by_location` returns a `CouchbaseOrm::ResultsProxy` object that you can further chain or iterate over.
|
|
136
|
+
|
|
137
|
+
Using `index_view` provides a convenient way to generate commonly used query methods for a specific attribute. It simplifies the process of querying based on that attribute and enhances the readability of your code.
|
|
138
|
+
|
|
139
|
+
<!-- ## 8.5 Range Queries (Experimental)
|
|
140
|
+
|
|
141
|
+
You can perform range queries on views by specifying the `startkey` and `endkey` options. Here's an example:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Query factories established between 2005 and 2015
|
|
145
|
+
Factory.by_established_year_range(startkey: 2005, endkey: 2015).each do |factory|
|
|
146
|
+
puts "- #{factory.name} (#{factory.established_year})"
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
In this example, we query the `by_established_year_range` view to retrieve factories established between 2005 and 2015. -->
|
|
151
|
+
|
|
152
|
+
## 8.5 Conclusion
|
|
153
|
+
|
|
154
|
+
Views in the Ruby Couchbase ORM provide a powerful way to define and query data based on specific attributes and custom map functions. By defining views, you can easily retrieve subsets of your data and perform efficient queries based on your application's requirements.
|
|
155
|
+
|
|
156
|
+
Remember to ensure that your design documents are up to date by calling `ensure_design_document!` on your models before running queries.
|
|
157
|
+
|
|
158
|
+
With the flexibility and ease of use provided by the Ruby Couchbase ORM's view functionality, you can efficiently query and retrieve data from your Couchbase database.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Nested Documents
|
|
2
|
+
|
|
3
|
+
CouchbaseOrm supports nested documents, which allow you to store complex, hierarchical data structures within a single Couchbase document. Nested documents are useful when you have related data that you want to store together with the parent document for performance or consistency reasons.
|
|
4
|
+
|
|
5
|
+
## 9.1. Defining Nested Documents
|
|
6
|
+
|
|
7
|
+
To define an nested document, you create a new class that inherits from `CouchbaseOrm::NestedDocument`.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class Part < CouchbaseOrm::NestedDocument
|
|
11
|
+
attribute :name, :string
|
|
12
|
+
attribute :manufacturer, :string
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Define the Car model with a nested Part document
|
|
16
|
+
class Car < CouchbaseOrm::Base
|
|
17
|
+
attribute :make, :string
|
|
18
|
+
attribute :model, :string
|
|
19
|
+
attribute :year, :integer
|
|
20
|
+
attribute :parts, :array, type: Part
|
|
21
|
+
|
|
22
|
+
validates :make, presence: true
|
|
23
|
+
validates :model, presence: true
|
|
24
|
+
validates :year, presence: true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
In this example, we define a `Car` model with a nested `Part` document. The `Part` class inherits from `CouchbaseOrm::NestedDocument` and defines attributes for `name` and `manufacturer`. The `Car` model has an `parts` attribute that is an array of `Part` nested documents.
|
|
30
|
+
|
|
31
|
+
## 9.2. Embedding Documents
|
|
32
|
+
|
|
33
|
+
To embed a document within a parent document, you can assign an instance of the nested document class to the corresponding attribute.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# Create a new car with nested parts
|
|
37
|
+
car = Car.new(
|
|
38
|
+
make: 'Toyota',
|
|
39
|
+
model: 'Corolla',
|
|
40
|
+
year: 2022,
|
|
41
|
+
parts: [
|
|
42
|
+
Part.new(name: 'Engine', manufacturer: 'Toyota Motors'),
|
|
43
|
+
Part.new(name: 'Transmission', manufacturer: 'Toyota Motors')
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
car.save
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When saving the parent document (`Car`), CouchbaseOrm serializes the nested `Part` documents and stores them within the parent document. This allows you to retrieve the entire data structure with a single query.
|
|
50
|
+
|
|
51
|
+
## 9.3. Accessing Nested Documents
|
|
52
|
+
|
|
53
|
+
To access an nested document, you can simply call the corresponding attribute on the parent document.
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
toyota_cars = Car.where(make: 'Toyota')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
CouchbaseOrm automatically deserializes the nested document and returns an instance of the nested document class.
|
|
60
|
+
|
|
61
|
+
## 9.4. Updating Nested Documents
|
|
62
|
+
|
|
63
|
+
To update an nested document, you can modify the attributes of the nested document instance and save the parent document.
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
engine_part = car.parts.find { |part| part.name == 'Engine' }
|
|
67
|
+
engine_part.manufacturer = 'Toyota Industries'
|
|
68
|
+
car.save
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
CouchbaseOrm will serialize the updated nested document and save it along with the parent document.
|
|
72
|
+
|
|
73
|
+
## 9.5. Embedding Multiple Documents
|
|
74
|
+
|
|
75
|
+
You can also embed multiple documents within a parent document using an array attribute.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
car = Car.new(
|
|
79
|
+
make: 'Toyota',
|
|
80
|
+
model: 'Corolla',
|
|
81
|
+
year: 2022,
|
|
82
|
+
parts: [
|
|
83
|
+
Part.new(name: 'Engine', manufacturer: 'Toyota Motors'),
|
|
84
|
+
Part.new(name: 'Transmission', manufacturer: 'Toyota Motors')
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
car.save
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
In this example, the `Car` model has an `parts` attribute that is an array of `Part` nested documents. You can embed multiple `Part` documents within a single `Car` document, allowing you to store related data together.
|
|
91
|
+
|
|
92
|
+
## 9.6. Querying Nested Documents
|
|
93
|
+
|
|
94
|
+
CouchbaseOrm allows you to query nested documents using dot notation and attribute conditions.
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
cars_by_part_manufacturer = Car.where("ANY part IN parts SATISFIES part.manufacturer = 'Toyota Industries' END")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This query retrieves all the `Car` documents where at least one `Part` document has a `manufacturer` attribute equal to `'Toyota Industries'`. You can use dot notation to access nested document attributes within the query condition.
|
|
101
|
+
|
|
102
|
+
## 9.7. Validating Nested Documents
|
|
103
|
+
|
|
104
|
+
CouchbaseOrm allows you to validate nested documents along with the parent document.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
class Part < CouchbaseOrm::NestedDocument
|
|
108
|
+
attribute :name, :string
|
|
109
|
+
attribute :manufacturer, :string
|
|
110
|
+
|
|
111
|
+
validates :name, presence: true
|
|
112
|
+
validates :manufacturer, presence: true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class Car < CouchbaseOrm::Base
|
|
116
|
+
attribute :make, :string
|
|
117
|
+
attribute :model, :string
|
|
118
|
+
attribute :year, :integer
|
|
119
|
+
attribute :parts, :array, type: Part
|
|
120
|
+
|
|
121
|
+
validates :make, presence: true
|
|
122
|
+
validates :model, presence: true
|
|
123
|
+
validates :year, presence: true
|
|
124
|
+
validates :parts, presence: true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
In this example, we define validations for the `Part` nested document, ensuring that the `name` and `manufacturer` attributes are present. We also add validations to the `Car` model to ensure that the `make`, `model`, `year`, and `parts` attributes are present.
|
|
131
|
+
|
|
132
|
+
When saving a `Car` document, CouchbaseOrm will validate both the parent document and the nested `Part` documents. If any validation fails, the parent document will not be saved, and validation errors will be added to the parent document.
|
|
133
|
+
|
|
134
|
+
Nested documents provide a powerful way to model complex data structures and relationships within a single Couchbase document. They allow you to store related data together, improving performance and reducing the need for separate queries to retrieve associated data.
|
|
135
|
+
|
|
136
|
+
However, it's important to consider the trade-offs when using nested documents. Embedding too much data within a single document can lead to large document sizes and potential performance issues. It's recommended to use nested documents judiciously and to consider the access patterns and data relationships of your application.
|
|
137
|
+
|
|
138
|
+
In the next section, we'll explore enums in CouchbaseOrm and how they can be used to define a fixed set of values for an attribute.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Enums
|
|
2
|
+
|
|
3
|
+
CouchbaseOrm provides support for enums, which allow you to define a fixed set of values for an attribute. Enums are useful when you have a limited number of possible values for a particular attribute and want to ensure data consistency and validity.
|
|
4
|
+
|
|
5
|
+
## 10.1. Defining Enums
|
|
6
|
+
|
|
7
|
+
To define an enum in your model, you can use the `enum` class method provided by CouchbaseOrm.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class User < CouchbaseOrm::Base
|
|
11
|
+
enum status: [:active, :inactive, :suspended]
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
In this example, we define an enum named `status` for the `User` model. The enum has three possible values: `:active`, `:inactive`, and `:suspended`.
|
|
16
|
+
|
|
17
|
+
## 10.2. Using Enums
|
|
18
|
+
|
|
19
|
+
You can assign enum values to an attribute using the generated methods or by directly assigning the value.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
user = User.new
|
|
23
|
+
user.status = :active
|
|
24
|
+
user.save
|
|
25
|
+
|
|
26
|
+
user.suspended!
|
|
27
|
+
user.save
|
|
28
|
+
|
|
29
|
+
puts user.status # Output: "suspended"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
In this example, we create a new `User` instance and set the `status` to `:active` using the direct assignment. We then change the `status` to `:suspended` using the generated `suspended!` method.
|
|
33
|
+
|
|
34
|
+
## 10.3. Querying by Enums
|
|
35
|
+
|
|
36
|
+
CouchbaseOrm allows you to query records based on their enum values.
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
active_users = User.where(status: 1)
|
|
40
|
+
suspended_users = User.where(status: 3)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
These queries retrieve users with the `status` enum set to `:active` and `:suspended`, respectively.
|
|
44
|
+
|
|
45
|
+
## 10.4. Enum Mapping
|
|
46
|
+
|
|
47
|
+
Behind the scenes, CouchbaseOrm maps the enum values to integers for storage in the database. By default, the mapping starts from 0 and increments by 1 for each enum value in the order they are defined.
|
|
48
|
+
|
|
49
|
+
However, you can customize the mapping by providing a hash of enum values and their corresponding integer values.
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class User < CouchbaseOrm::Base
|
|
53
|
+
enum status: { active: 1, inactive: 2, suspended: 3 }
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
In this example, we explicitly define the mapping of enum values to integers. The `:active` value is mapped to 1, `:inactive` to 2, and `:suspended` to 3.
|
|
58
|
+
|
|
59
|
+
## 10.5. Enum Validation
|
|
60
|
+
|
|
61
|
+
CouchbaseOrm automatically validates that the assigned enum value is one of the defined values for the enum.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
user = User.new
|
|
65
|
+
user.status = :invalid
|
|
66
|
+
user.save # Raises an error: "Invalid enum value: :invalid"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you try to assign an invalid value to an enum attribute, CouchbaseOrm will raise an error indicating that the value is not a valid enum value.
|
|
70
|
+
|
|
71
|
+
## 10.6. Enum Defaults
|
|
72
|
+
|
|
73
|
+
You can specify a default value for an enum attribute using the `default` option.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class User < CouchbaseOrm::Base
|
|
77
|
+
enum status: [:active, :inactive, :suspended], default: :active
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
In this example, if no value is assigned to the `status` attribute when creating a new `User` instance, the default value of `:active` will be used.
|
|
82
|
+
|
|
83
|
+
Enums in CouchbaseOrm provide a convenient way to define a fixed set of values for an attribute. They help ensure data consistency, improve code readability, and simplify querying and validation.
|
|
84
|
+
|
|
85
|
+
When using enums, consider the following:
|
|
86
|
+
|
|
87
|
+
- Enums are stored as integers in the database, so be cautious when changing the order or removing enum values, as it may affect existing records.
|
|
88
|
+
- Enums are case-sensitive, so `:active` and `:Active` are considered different values.
|
|
89
|
+
- Enums can be used in combination with other attribute types, such as `default` and `validates`, to further customize the behavior of the attribute.
|
|
90
|
+
|
|
91
|
+
In the next section, we'll explore how to use encryption in CouchbaseOrm to secure sensitive data stored in your Couchbase documents.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Encryption
|
|
2
|
+
|
|
3
|
+
CouchbaseOrm provides built-in support for storing encrypted data in your Couchbase documents using a structured format. The `:encrypted` type provides a standardized storage format compatible with Couchbase Lite's field-level encryption, but **does not perform encryption/decryption itself**. Your application is responsible for encrypting data before storing it and decrypting it after retrieval.
|
|
4
|
+
|
|
5
|
+
## 11.1. Encrypted Attributes
|
|
6
|
+
|
|
7
|
+
To mark an attribute as encrypted, you can use the `:encrypted` type when defining the attribute in your model.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Define the Bank model with encrypted attributes
|
|
11
|
+
class Bank < CouchbaseOrm::Base
|
|
12
|
+
attribute :name, :string
|
|
13
|
+
attribute :account_number, :encrypted
|
|
14
|
+
attribute :routing_number, :encrypted, alg: "3DES"
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
In this example, the `account_number` and `routing_number` attributes are marked as encrypted. The `alg` option specifies the encryption algorithm identifier that will be stored in the document metadata (default is `"CB_MOBILE_CUSTOM"`). This identifier is for documentation purposes and Couchbase Lite compatibility - CouchbaseOrm does not use it for actual encryption.
|
|
19
|
+
|
|
20
|
+
```plaintext
|
|
21
|
+
{
|
|
22
|
+
"name": "Test Bank",
|
|
23
|
+
"encrypted$account_number": {
|
|
24
|
+
"alg": "CB_MOBILE_CUSTOM",
|
|
25
|
+
"ciphertext": "MTIzNDU2Nzg5"
|
|
26
|
+
},
|
|
27
|
+
"encrypted$routing_number": {
|
|
28
|
+
"alg": "3DES",
|
|
29
|
+
"ciphertext": "OTg3NjU0MzIx"
|
|
30
|
+
},
|
|
31
|
+
"type": "bank"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When a document is saved, CouchbaseOrm stores encrypted attributes in the document with a prefix of `encrypted$`. The values are stored as JSON objects containing the encryption algorithm identifier (`alg`) and the ciphertext (`ciphertext`).
|
|
36
|
+
|
|
37
|
+
**Important**: You must provide **pre-encrypted** values to encrypted attributes. CouchbaseOrm stores these values as-is in the `ciphertext` field without performing any encryption.
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# You must encrypt the data BEFORE assigning it to the attribute
|
|
41
|
+
require 'base64'
|
|
42
|
+
|
|
43
|
+
# Assuming you have an encryption method (e.g., AES, Tanker, etc.)
|
|
44
|
+
encrypted_account = MyEncryptor.encrypt('123456789')
|
|
45
|
+
encrypted_routing = MyEncryptor.encrypt('987654321')
|
|
46
|
+
|
|
47
|
+
# Values must be Base64-encoded strings
|
|
48
|
+
bank = Bank.new(
|
|
49
|
+
name: 'My Bank',
|
|
50
|
+
account_number: Base64.strict_encode64(encrypted_account),
|
|
51
|
+
routing_number: Base64.strict_encode64(encrypted_routing)
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 11.2. Complete Example with Encryption
|
|
56
|
+
|
|
57
|
+
Here's a complete example showing how to handle encryption in your application:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
require 'base64'
|
|
61
|
+
require 'openssl'
|
|
62
|
+
|
|
63
|
+
# Example encryption helper (you should use a proper encryption library)
|
|
64
|
+
class SimpleEncryptor
|
|
65
|
+
def self.encrypt(plaintext)
|
|
66
|
+
# This is a simplified example - use a proper encryption library in production
|
|
67
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
68
|
+
cipher.encrypt
|
|
69
|
+
cipher.key = ENV['ENCRYPTION_KEY'] # Store securely, never commit to git
|
|
70
|
+
cipher.iv = iv = cipher.random_iv
|
|
71
|
+
|
|
72
|
+
encrypted = cipher.update(plaintext) + cipher.final
|
|
73
|
+
# Prepend IV for decryption (in real implementation, handle this properly)
|
|
74
|
+
iv + encrypted
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.decrypt(ciphertext_with_iv)
|
|
78
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
79
|
+
cipher.decrypt
|
|
80
|
+
cipher.key = ENV['ENCRYPTION_KEY']
|
|
81
|
+
|
|
82
|
+
# Extract IV and ciphertext
|
|
83
|
+
iv = ciphertext_with_iv[0..15]
|
|
84
|
+
ciphertext = ciphertext_with_iv[16..]
|
|
85
|
+
|
|
86
|
+
cipher.iv = iv
|
|
87
|
+
cipher.update(ciphertext) + cipher.final
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Create a bank record with encrypted attributes
|
|
92
|
+
plaintext_account = "123456789"
|
|
93
|
+
plaintext_routing = "987654321"
|
|
94
|
+
|
|
95
|
+
# 1. Encrypt the sensitive data
|
|
96
|
+
encrypted_account = SimpleEncryptor.encrypt(plaintext_account)
|
|
97
|
+
encrypted_routing = SimpleEncryptor.encrypt(plaintext_routing)
|
|
98
|
+
|
|
99
|
+
# 2. Encode as Base64 for storage
|
|
100
|
+
bank = Bank.new(
|
|
101
|
+
name: "Test Bank",
|
|
102
|
+
account_number: Base64.strict_encode64(encrypted_account),
|
|
103
|
+
routing_number: Base64.strict_encode64(encrypted_routing)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 3. Save to Couchbase
|
|
107
|
+
bank.save!
|
|
108
|
+
|
|
109
|
+
# 4. Retrieve and decrypt
|
|
110
|
+
found_bank = Bank.find(bank.id)
|
|
111
|
+
|
|
112
|
+
# 5. Decode Base64 and decrypt
|
|
113
|
+
account_encrypted = Base64.strict_decode64(found_bank.account_number)
|
|
114
|
+
routing_encrypted = Base64.strict_decode64(found_bank.routing_number)
|
|
115
|
+
|
|
116
|
+
decrypted_account = SimpleEncryptor.decrypt(account_encrypted)
|
|
117
|
+
decrypted_routing = SimpleEncryptor.decrypt(routing_encrypted)
|
|
118
|
+
|
|
119
|
+
puts "Decrypted account: #{decrypted_account}" # => "123456789"
|
|
120
|
+
puts "Decrypted routing: #{decrypted_routing}" # => "987654321"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 11.3. Storage Format
|
|
124
|
+
|
|
125
|
+
CouchbaseOrm handles the storage format for encrypted attributes but does not perform encryption/decryption. Here's what happens:
|
|
126
|
+
|
|
127
|
+
**When saving:**
|
|
128
|
+
1. You assign a Base64-encoded ciphertext to the encrypted attribute
|
|
129
|
+
2. CouchbaseOrm wraps it in the `encrypted$` format with `alg` and `ciphertext` fields
|
|
130
|
+
3. The document is stored in Couchbase with this structure
|
|
131
|
+
|
|
132
|
+
**When loading:**
|
|
133
|
+
1. CouchbaseOrm reads the document from Couchbase
|
|
134
|
+
2. It unwraps the `encrypted$` format and extracts the `ciphertext` value
|
|
135
|
+
3. The Base64-encoded ciphertext is assigned to the attribute
|
|
136
|
+
4. Your application must decode and decrypt the value
|
|
137
|
+
|
|
138
|
+
**Key Points:**
|
|
139
|
+
- CouchbaseOrm does **not** require or use any encryption key
|
|
140
|
+
- The `alg` field is purely informational (for compatibility with Couchbase Lite)
|
|
141
|
+
- All actual encryption/decryption is your application's responsibility
|
|
142
|
+
- Values must be valid Base64-encoded strings
|
|
143
|
+
|
|
144
|
+
## 11.4. Considerations and Best Practices
|
|
145
|
+
|
|
146
|
+
When using encrypted attributes in CouchbaseOrm, consider the following best practices:
|
|
147
|
+
|
|
148
|
+
### Security
|
|
149
|
+
- **Encryption is your responsibility**: CouchbaseOrm only provides the storage format. Choose a robust encryption library (e.g., `rbnacl`, `openssl`, or a service like AWS KMS)
|
|
150
|
+
- **Key management**: Store encryption keys securely using environment variables, secret managers (AWS Secrets Manager, HashiCorp Vault), or key management services
|
|
151
|
+
- **Never commit keys**: Keep encryption keys out of version control systems
|
|
152
|
+
- **Key rotation**: Implement a key rotation strategy and maintain the ability to decrypt data encrypted with old keys
|
|
153
|
+
- **Use authenticated encryption**: Prefer AEAD modes (like AES-GCM) that provide both confidentiality and integrity
|
|
154
|
+
|
|
155
|
+
### Performance and Querying
|
|
156
|
+
- **Cannot query encrypted fields**: Encrypted attributes cannot be used in WHERE clauses or indexed effectively
|
|
157
|
+
- **Consider searchable encryption**: If you need to search encrypted data, investigate specialized solutions like searchable encryption schemes or external encrypted search indexes
|
|
158
|
+
- **Selective encryption**: Only encrypt truly sensitive fields to minimize performance overhead
|
|
159
|
+
|
|
160
|
+
### Implementation Patterns
|
|
161
|
+
- **Wrap in accessors**: Create getter/setter methods that automatically handle encryption/decryption:
|
|
162
|
+
```ruby
|
|
163
|
+
class Bank < CouchbaseOrm::Base
|
|
164
|
+
attribute :account_number, :encrypted
|
|
165
|
+
|
|
166
|
+
def account_number=(plaintext)
|
|
167
|
+
encrypted = MyEncryptor.encrypt(plaintext)
|
|
168
|
+
super(Base64.strict_encode64(encrypted))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def account_number
|
|
172
|
+
encrypted = Base64.strict_decode64(super)
|
|
173
|
+
MyEncryptor.decrypt(encrypted)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **Separate concerns**: Consider using a concern or module to encapsulate encryption logic:
|
|
179
|
+
```ruby
|
|
180
|
+
module EncryptedAttributes
|
|
181
|
+
def encrypted_attribute(name)
|
|
182
|
+
define_method("#{name}=") do |plaintext|
|
|
183
|
+
encrypted = MyEncryptor.encrypt(plaintext)
|
|
184
|
+
super(Base64.strict_encode64(encrypted))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
define_method(name) do
|
|
188
|
+
encrypted = Base64.strict_decode64(super())
|
|
189
|
+
MyEncryptor.decrypt(encrypted)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Compatibility
|
|
196
|
+
- The `encrypted$` format is compatible with Couchbase Lite's field-level encryption
|
|
197
|
+
- The `alg` field helps document which encryption algorithm was used, aiding in key rotation and auditing
|
|
198
|
+
- Ensure your encryption implementation is compatible across all platforms that access the data (web, mobile, etc.)
|
|
199
|
+
|
|
200
|
+
Encryption is a powerful tool for protecting sensitive data, but it should be used judiciously. Focus on encrypting the most sensitive and confidential data while balancing the trade-offs between security, performance, and functionality.
|
|
201
|
+
|
|
202
|
+
In the next section, we'll explore logging in CouchbaseOrm and how you can configure and customize logging to monitor and debug your application.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Logging
|
|
2
|
+
|
|
3
|
+
CouchbaseOrm provides a logging mechanism to help you monitor and debug your application. Logging allows you to capture important events, errors, and information during the execution of your application. CouchbaseOrm integrates with the logging framework used in your Ruby application, such as the built-in `Logger` class or third-party logging libraries.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## 12.1. Log Levels
|
|
7
|
+
|
|
8
|
+
CouchbaseOrm supports different log levels to control the verbosity of the logged messages. The available log levels, in increasing order of severity, are:
|
|
9
|
+
|
|
10
|
+
- `DEBUG`: Detailed information, typically of interest only when diagnosing problems.
|
|
11
|
+
- `INFO`: Confirmation that things are working as expected.
|
|
12
|
+
- `WARN`: An indication that something unexpected happened or indicative of some problem in the near future.
|
|
13
|
+
- `ERROR`: Due to a more serious problem, the software has not been able to perform some function.
|
|
14
|
+
- `FATAL`: A serious error, indicating that the program itself may be unable to continue running.
|
|
15
|
+
|
|
16
|
+
By default, CouchbaseOrm logs messages at the `INFO` level and above. You can change the log level by exporting the `COUCHBASE_ORM_DEBUG` environment variable with the desired log level. For example, to set the log level to `DEBUG`, you can run:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
export COUCHBASE_ORM_DEBUG=Logger::DEBUG
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This command sets the log level to `DEBUG`, which will log detailed information for debugging purposes.
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require_relative "app"
|
|
26
|
+
|
|
27
|
+
# Create a new user
|
|
28
|
+
user = User.new(name: 'John Doe', email: 'john@example.com')
|
|
29
|
+
user.save
|
|
30
|
+
|
|
31
|
+
# Update the user's email
|
|
32
|
+
user.email = 'john.doe@example.com'
|
|
33
|
+
user.save
|
|
34
|
+
|
|
35
|
+
# Log a custom message
|
|
36
|
+
CouchbaseOrm.logger.info "User #{user.id} updated email to #{user.email}"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In this example, we create a new `User` instance, save it to the database, update the user's email, and log a custom message using the `CouchbaseOrm.logger` object. The log message includes the user's ID and the updated email address.
|
|
40
|
+
|
|
41
|
+
Output:
|
|
42
|
+
```
|
|
43
|
+
D, [2024-05-24T11:48:00.071104 #234447] DEBUG -- : Initialize model with {:name=>"John Doe", :email=>"john@example.com"}
|
|
44
|
+
D, [2024-05-24T11:48:00.086972 #234447] DEBUG -- : _create_record - Upsert user-1-vncZNSYZj {"id"=>"user-1-vncZNSYZj", "email"=>"john@example.com", "name"=>"John Doe", "age"=>nil, "height"=>nil, "is_active"=>nil, "birth_date"=>nil, "created_at"=>"2024-05-24T06:18:00Z", "updated_at"=>"2024...
|
|
45
|
+
D, [2024-05-24T11:48:00.113166 #234447] DEBUG -- : _update_record - replace user-1-vncZNSYZj {"id"=>"user-1-vncZNSYZj", "email"=>"john.doe@example.com", "name"=>"John Doe", "age"=>nil, "height"=>nil, "is_active"=>nil, "birth_date"=>nil, "created_at"=>"2024-05-24T06:18:00Z", "updated_at"=>"...
|
|
46
|
+
I, [2024-05-24T11:48:00.115239 #234447] INFO -- : User user-1-vncZNSYZj updated email to john.doe@example.com
|
|
47
|
+
```
|
|
48
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
When working with CouchbaseOrm, you may encounter various issues or errors. In this section, we'll discuss common problems and provide troubleshooting tips to help you identify and resolve these issues effectively.
|
|
4
|
+
|
|
5
|
+
## 15.1. Common Issues
|
|
6
|
+
|
|
7
|
+
Here are some common issues you may encounter while using CouchbaseOrm:
|
|
8
|
+
|
|
9
|
+
1. **Connection Errors**: If you experience connection errors, such as "Failed to connect to Couchbase server," ensure that your Couchbase Server is running and accessible. Verify that the connection details in your configuration file are correct, including the hostname, port, bucket name, username, and password.
|
|
10
|
+
|
|
11
|
+
2. **Document Not Found**: If you receive a "Document not found" error when retrieving a document, double-check that the document ID is correct and exists in the Couchbase bucket. Make sure you haven't accidentally deleted or modified the document.
|
|
12
|
+
|
|
13
|
+
3. **N1QL Query Errors**: If your N1QL queries are not returning the expected results or throwing errors, check the syntax of your queries. Ensure that you are using the correct bucket name, index names, and attribute names. Verify that the appropriate indexes are created and available for the queried attributes.
|
|
14
|
+
|
|
15
|
+
4. **Validation Errors**: If you encounter validation errors when saving a document, review your model's validation rules and make sure the document attributes satisfy those rules. Check for presence, uniqueness, format, and other validation constraints.
|
|
16
|
+
|
|
17
|
+
5. **Unexpected Behavior**: If you experience unexpected behavior or results, double-check your code logic, query conditions, and attribute assignments. Ensure that you are using the correct methods, parameters, and data types.
|
|
18
|
+
|
|
19
|
+
## 15.2. Debugging Tips
|
|
20
|
+
|
|
21
|
+
When troubleshooting issues with CouchbaseOrm, consider the following debugging tips:
|
|
22
|
+
|
|
23
|
+
1. **Enable Logging**: CouchbaseOrm provides logging functionality to help you track the execution flow and identify issues. Enable logging in your application and set the log level to an appropriate verbosity. Review the log output for any error messages, stacktraces, or unexpected behavior.
|
|
24
|
+
|
|
25
|
+
2. **Inspect Couchbase Server**: Use the Couchbase Web Console or command-line tools to inspect your Couchbase Server instance. Check the bucket details, document contents, and index definitions to ensure they align with your application's expectations.
|
|
26
|
+
|
|
27
|
+
3. **Use Debugging Tools**: Utilize debugging tools like `byebug` or `pry` to pause the execution of your code at specific points and inspect variable values, object states, and method invocations. Set breakpoints in your code to step through the execution flow and identify the source of the issue.
|
|
28
|
+
|
|
29
|
+
4. **Test in Isolation**: Isolate the problematic code or query and test it in a separate environment or script. This allows you to focus on the specific issue without the complexity of the entire application. Create a minimal reproducible example that demonstrates the problem.
|
|
30
|
+
|
|
31
|
+
5. **Verify Couchbase Server Version**: Ensure that you are using a compatible version of Couchbase Server with CouchbaseOrm. Check the CouchbaseOrm documentation for version compatibility and make sure your server version meets the requirements.
|
|
32
|
+
|
|
33
|
+
6. **Consult Documentation and Community**: Refer to the CouchbaseOrm documentation, API reference, and guides for detailed information on how to use specific features and resolve common issues. Engage with the CouchbaseOrm community through forums, chat channels, or mailing lists to seek assistance from experienced users and developers.
|
|
34
|
+
|
|
35
|
+
7. **Reproduce and Report**: If you encounter a bug or an issue that seems to be related to CouchbaseOrm itself, try to reproduce the problem in a isolated environment. If the issue persists, consider reporting it to the CouchbaseOrm maintainers or filing an issue on the project's issue tracker, providing detailed information about the problem, steps to reproduce it, and any relevant code snippets or error messages.
|
|
36
|
+
|
|
37
|
+
Remember, troubleshooting is an iterative process. Start by isolating the problem, gathering relevant information, and systematically eliminating possible causes. Break down the issue into smaller components and test each component independently to narrow down the root cause.
|
|
38
|
+
|
|
39
|
+
If you are unable to resolve the issue on your own, don't hesitate to seek help from the CouchbaseOrm community or reach out to the project maintainers for assistance. Providing clear and detailed information about the problem, along with any relevant code snippets and error messages, will help others understand and provide more accurate guidance.
|
|
40
|
+
|
|
41
|
+
By following these troubleshooting tips and leveraging the available resources, you can effectively diagnose and resolve issues encountered while working with CouchbaseOrm.
|