cuprum-collections 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +59 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +25 -0
- data/LICENSE +22 -0
- data/README.md +950 -0
- data/lib/cuprum/collections/base.rb +11 -0
- data/lib/cuprum/collections/basic/collection.rb +135 -0
- data/lib/cuprum/collections/basic/command.rb +112 -0
- data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
- data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
- data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
- data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
- data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
- data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
- data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
- data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
- data/lib/cuprum/collections/basic/commands.rb +18 -0
- data/lib/cuprum/collections/basic/query.rb +160 -0
- data/lib/cuprum/collections/basic/query_builder.rb +69 -0
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
- data/lib/cuprum/collections/basic/rspec.rb +8 -0
- data/lib/cuprum/collections/basic.rb +22 -0
- data/lib/cuprum/collections/command.rb +26 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
- data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
- data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
- data/lib/cuprum/collections/commands.rb +8 -0
- data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
- data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
- data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
- data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
- data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
- data/lib/cuprum/collections/constraints/order.rb +8 -0
- data/lib/cuprum/collections/constraints/ordering.rb +114 -0
- data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
- data/lib/cuprum/collections/constraints.rb +8 -0
- data/lib/cuprum/collections/errors/already_exists.rb +86 -0
- data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
- data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
- data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
- data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
- data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
- data/lib/cuprum/collections/errors/not_found.rb +81 -0
- data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
- data/lib/cuprum/collections/errors.rb +8 -0
- data/lib/cuprum/collections/queries/ordering.rb +74 -0
- data/lib/cuprum/collections/queries/parse.rb +22 -0
- data/lib/cuprum/collections/queries/parse_block.rb +206 -0
- data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
- data/lib/cuprum/collections/queries.rb +25 -0
- data/lib/cuprum/collections/query.rb +247 -0
- data/lib/cuprum/collections/query_builder.rb +61 -0
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
- data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
- data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
- data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
- data/lib/cuprum/collections/rspec.rb +8 -0
- data/lib/cuprum/collections/version.rb +59 -0
- data/lib/cuprum/collections.rb +26 -0
- metadata +219 -0
data/README.md
ADDED
@@ -0,0 +1,950 @@
|
|
1
|
+
# Cuprum::Collections
|
2
|
+
|
3
|
+
A data abstraction layer based on the Cuprum library.
|
4
|
+
|
5
|
+
Cuprum::Collections defines the following objects:
|
6
|
+
|
7
|
+
- [Collections](#collections): A standard interface for interacting with a datastore.
|
8
|
+
- [Commands](#commands): Each collection is comprised of `Cuprum` commands, which implement common collection operations such as inserting or querying data.
|
9
|
+
- [Queries](#queries): A low-level interface for performing query operations on a datastore.
|
10
|
+
|
11
|
+
## About
|
12
|
+
|
13
|
+
Cuprum::Collections provides a standard interface for interacting with a datastore, whether the data is in a relational database, a document-based datastore, a directory of files, or simply an array of in-memory objects. It leverages the `Cuprum` and `Stannum` gems to define a set of commands with built-in parameter validation and error handling.
|
14
|
+
|
15
|
+
Currently, the Cuprum::Collections gem itself provides the `Basic` collection, which stores and queries data to and from an in-memory `Array` of `Hash`es data structure. Additional datastores are supported via other gems:
|
16
|
+
|
17
|
+
- [Cuprum::Rails](https://github.com/sleepingkingstudios/cuprum-rails/): The `Cuprum::Rails::Collection` implement the collection interface for `ActiveRecord` models.
|
18
|
+
|
19
|
+
### Why Cuprum::Collections?
|
20
|
+
|
21
|
+
The Ruby ecosystem has a wide variety of tools and libraries for managing data and persistence - ORMs like [ActiveRecord](https://rubyonrails.org/) and [Mongoid](https://mongoid.github.io/), object mapping tools like [Ruby Object Mapper](https://rom-rb.org/), and low-level libraries like [Sequel](http://sequel.jeremyevans.net/) and [Mongo](https://docs.mongodb.com/ruby-driver/current/). Why take the time to learn and apply a new tool?
|
22
|
+
|
23
|
+
- **Flexibility:** Using a consistent interface allows an application to be flexible in how it persists and queries data. For example, an application could use the same interface to manage both a relational database and a document-based datastore, or use a fast in-memory data store to back its unit tests.
|
24
|
+
- **Command Pattern:** Leverages the [Cuprum](https://github.com/sleepingkingstudios/cuprum) gem and the [Command pattern](https://en.wikipedia.org/wiki/Command_pattern) to define encapsulated, composable, and reusable components for persisting and querying data. In addition, the [Stannum](https://github.com/sleepingkingstudios/stannum/) gem provides data and parameter validation.
|
25
|
+
- **Data Mapping:** The `Cuprum::Collections` approach to data is much closer to the [Data Mapper pattern](https://en.wikipedia.org/wiki/Data_mapper_pattern) than the [Active Record pattern](https://en.wikipedia.org/wiki/Active_record_pattern). This isolates the persistence and validation logic from how the data is defined and how it is stored.
|
26
|
+
|
27
|
+
### Compatibility
|
28
|
+
|
29
|
+
Cuprum::Collections is tested against Ruby (MRI) 2.6 through 2.7.
|
30
|
+
|
31
|
+
### Documentation
|
32
|
+
|
33
|
+
Documentation is generated using [YARD](https://yardoc.org/), and can be generated locally using the `yard` gem.
|
34
|
+
|
35
|
+
### License
|
36
|
+
|
37
|
+
Copyright (c) 2020-2021 Rob Smith
|
38
|
+
|
39
|
+
Stannum is released under the [MIT License](https://opensource.org/licenses/MIT).
|
40
|
+
|
41
|
+
### Contribute
|
42
|
+
|
43
|
+
The canonical repository for this gem is located at https://github.com/sleepingkingstudios/cuprum-collections.
|
44
|
+
|
45
|
+
To report a bug or submit a feature request, please use the [Issue Tracker](https://github.com/sleepingkingstudios/cuprum-collections/issues).
|
46
|
+
|
47
|
+
To contribute code, please fork the repository, make the desired updates, and then provide a [Pull Request](https://github.com/sleepingkingstudios/cuprum-collections/pulls). Pull requests must include appropriate tests for consideration, and all code must be properly formatted.
|
48
|
+
|
49
|
+
### Code of Conduct
|
50
|
+
|
51
|
+
Please note that the `Cuprum::Collections` project is released with a [Contributor Code of Conduct](https://github.com/sleepingkingstudios/cuprum-collections/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms.
|
52
|
+
|
53
|
+
<!-- ## Getting Started -->
|
54
|
+
|
55
|
+
## Reference
|
56
|
+
|
57
|
+
<a id="collections"></a>
|
58
|
+
|
59
|
+
### Collections
|
60
|
+
|
61
|
+
A `Cuprum::Collection` provides an interface for persisting and querying data to and from a data source.
|
62
|
+
|
63
|
+
Each collection provides three features:
|
64
|
+
|
65
|
+
- A constructor that initializes the collection with the necessary parameters.
|
66
|
+
- A set of commands that implement persistence and querying operations.
|
67
|
+
- A `#query` method to directly perform queries on the data.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
collection = Cuprum::Collections::Basic.new(
|
71
|
+
collection_name: 'books',
|
72
|
+
data: book_data,
|
73
|
+
)
|
74
|
+
|
75
|
+
# Add an item to the collection.
|
76
|
+
steps do
|
77
|
+
# Build the book from attributes.
|
78
|
+
book = step do
|
79
|
+
collection.build_one.call(
|
80
|
+
attributes: { id: 10, title: 'Gideon the Ninth', author: 'Tammsyn Muir' }
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validate the book using its default validations.
|
85
|
+
step { collection.validate_one.call(entity: book) }
|
86
|
+
|
87
|
+
# Insert the validated book to the collection.
|
88
|
+
step { collection.insert_one.call(entity: book) }
|
89
|
+
end
|
90
|
+
|
91
|
+
# Find an item by primary key.
|
92
|
+
book = step { collection.find_one.call(primary_key: 10) }
|
93
|
+
|
94
|
+
# Find items matching a filter.
|
95
|
+
books = step do
|
96
|
+
collection.find_matching.call(
|
97
|
+
limit: 10,
|
98
|
+
order: [:author, { title: :descending }],
|
99
|
+
where: lambda do
|
100
|
+
published_at: greater_than('1950-01-01')
|
101
|
+
end
|
102
|
+
)
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Because a collection can represent any sort of data, from a raw Ruby Hash to an ORM record, the term used to indicate "one item in the collection" is an *entity*. Likewise, the class of the items in the collection is the *entity_class*. In our example above, our entities are books, and the entity class is Hash.
|
107
|
+
|
108
|
+
<a id="commands"></a>
|
109
|
+
|
110
|
+
#### Commands
|
111
|
+
|
112
|
+
Structurally, a collection is a set of commands, which are instances of `Cuprum::Command` that implement a persistence or querying operation and wrap that operation with parameter validation and error handling. For more information on `Cuprum` commands, see the [Cuprum gem](github.com/sleepingkingstudios/cuprum).
|
113
|
+
|
114
|
+
##### Assign One
|
115
|
+
|
116
|
+
The `AssignOne` command takes an attributes hash and an entity, and returns an instance of the entity class whose attributes are equal to the attributes hash merged into original entities attributes. Depending on the collection, `#assign_one` may or may not modify or return the original entity.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
|
120
|
+
attributes = { 'title' => 'Harrow the Ninth', 'published_at' => '2020-08-04' }
|
121
|
+
result = collection.assign_one.call(attributes: attributes, entity: entity)
|
122
|
+
|
123
|
+
result.value
|
124
|
+
#=> {
|
125
|
+
# 'id' => 10,
|
126
|
+
# 'title' => 'Harrow the Ninth',
|
127
|
+
# 'author' => 'Tammsyn Muir',
|
128
|
+
# 'published_at' => '2020-08-04'
|
129
|
+
# }
|
130
|
+
```
|
131
|
+
|
132
|
+
If the entity class specifies a set of attributes (such as the defined columns in a relational table), the `#assign_one` command can return a failing result with an `ExtraAttributes` error (see [Errors](#errors), below) if the attributes hash includes one or more attributes that are not defined for that entity class.
|
133
|
+
|
134
|
+
##### Build One
|
135
|
+
|
136
|
+
The `BuildOne` command takes an attributes hash and returns a new instance of the entity class whose attributes are equal to the given attributes. This does not validate or persist the entity; it is equivalent to calling `entity_class.new` with the attributes.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
attributes = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
|
140
|
+
result = collection.build_one.call(attributes: attributes, entity: entity)
|
141
|
+
|
142
|
+
result.value
|
143
|
+
#=> {
|
144
|
+
# 'id' => 10,
|
145
|
+
# 'title' => 'Gideon the Ninth',
|
146
|
+
# 'author' => 'Tammsyn Muir'
|
147
|
+
# }
|
148
|
+
```
|
149
|
+
|
150
|
+
If the entity class specifies a set of attributes (such as the defined columns in a relational table), the `#build_one` command can return a failing result with an `ExtraAttributes` error (see [Errors](#errors), below) if the attributes hash includes one or more attributes that are not defined for that entity class.
|
151
|
+
|
152
|
+
##### Destroy One
|
153
|
+
|
154
|
+
The `DestroyOne` command takes a primary key value and removes the entity with the specified primary key from the collection.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
result = collection.destroy_one.call(primary_key: 0)
|
158
|
+
|
159
|
+
collection.query.where(id: 0).exists?
|
160
|
+
#=> false
|
161
|
+
```
|
162
|
+
|
163
|
+
If the collection does not include an entity with the specified primary key, the `#destroy_one` command will return a failing result with a `NotFound` error (see [Errors](#errors), below).
|
164
|
+
|
165
|
+
##### Find Many
|
166
|
+
|
167
|
+
The `FindMany` command takes an array of primary key values and returns the entities with the specified primary keys. The entities are returned in the order of the specified primary keys.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
result = collection.find_many.call(primary_keys: [0, 1, 2])
|
171
|
+
result.value
|
172
|
+
#=> [
|
173
|
+
# {
|
174
|
+
# 'id' => 0,
|
175
|
+
# 'title' => 'The Hobbit',
|
176
|
+
# 'author' => 'J.R.R. Tolkien',
|
177
|
+
# 'series' => nil,
|
178
|
+
# 'category' => 'Science Fiction and Fantasy',
|
179
|
+
# 'published_at' => '1937-09-21'
|
180
|
+
# },
|
181
|
+
# {
|
182
|
+
# 'id' => 1,
|
183
|
+
# 'title' => 'The Silmarillion',
|
184
|
+
# 'author' => 'J.R.R. Tolkien',
|
185
|
+
# 'series' => nil,
|
186
|
+
# 'category' => 'Science Fiction and Fantasy',
|
187
|
+
# 'published_at' => '1977-09-15'
|
188
|
+
# },
|
189
|
+
# {
|
190
|
+
# 'id' => 2,
|
191
|
+
# 'title' => 'The Fellowship of the Ring',
|
192
|
+
# 'author' => 'J.R.R. Tolkien',
|
193
|
+
# 'series' => 'The Lord of the Rings',
|
194
|
+
# 'category' => 'Science Fiction and Fantasy',
|
195
|
+
# 'published_at' => '1954-07-29'
|
196
|
+
# }
|
197
|
+
# ]
|
198
|
+
```
|
199
|
+
|
200
|
+
The `FindMany` command has several options:
|
201
|
+
|
202
|
+
- The `:allow_partial` keyword allows the command to return a passing result if at least one of the entities is found. By default, the command will return a failing result unless an entity is found for each primary key value.
|
203
|
+
- The `:envelope` keyword wraps the result value in an envelope hash, with a key equal to the name of the collection and whose value is the returned entities array.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
result = collection.find_many.call(primary_keys: [0, 1, 2], envelope: true)
|
207
|
+
result.value
|
208
|
+
#=> { books: [{ ... }, { ... }, { ... }] }
|
209
|
+
```
|
210
|
+
|
211
|
+
- The `:scope` keyword allows you to pass a query to the command. Only entities that match the given scope will be found and returned by `#find_many`.
|
212
|
+
|
213
|
+
If the collection does not include an entity with each of the specified primary keys, the `#find_many` command will return a failing result with a `NotFound` error (see [Errors](#errors), below).
|
214
|
+
|
215
|
+
##### Find Matching
|
216
|
+
|
217
|
+
The `FindMatching` command takes a set of query parameters and queries data from the collection. You can specify filters using the `:where` keyword or by passing a block, sort the results using the `:order` keyword, or return a subset of the results using the `:limit` and `:offset` keywords. For full details on performing queries, see [Queries](#queries), below.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
result =
|
221
|
+
collection
|
222
|
+
.find_matching
|
223
|
+
.call(order: :published_at, where: { series: 'Earthsea' })
|
224
|
+
result.value
|
225
|
+
#=> [
|
226
|
+
# {
|
227
|
+
# 'id' => 7,
|
228
|
+
# 'title' => 'A Wizard of Earthsea',
|
229
|
+
# 'author' => 'Ursula K. LeGuin',
|
230
|
+
# 'series' => 'Earthsea',
|
231
|
+
# 'category' => 'Science Fiction and Fantasy',
|
232
|
+
# 'published_at' => '1968-11-01'
|
233
|
+
# },
|
234
|
+
# {
|
235
|
+
# 'id' => 8,
|
236
|
+
# 'title' => 'The Tombs of Atuan',
|
237
|
+
# 'author' => 'Ursula K. LeGuin',
|
238
|
+
# 'series' => 'Earthsea',
|
239
|
+
# 'category' => 'Science Fiction and Fantasy',
|
240
|
+
# 'published_at' => '1970-12-01'
|
241
|
+
# },
|
242
|
+
# {
|
243
|
+
# 'id' => 9,
|
244
|
+
# 'title' => 'The Farthest Shore',
|
245
|
+
# 'author' => 'Ursula K. LeGuin',
|
246
|
+
# 'series' => 'Earthsea',
|
247
|
+
# 'category' => 'Science Fiction and Fantasy',
|
248
|
+
# 'published_at' => '1972-09-01'
|
249
|
+
# }
|
250
|
+
# ]
|
251
|
+
```
|
252
|
+
|
253
|
+
The `FindMatching` command has several options:
|
254
|
+
|
255
|
+
- The `:envelope` keyword wraps the result value in an envelope hash, with a key equal to the name of the collection and whose value is the returned entities array.
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
result = collection.find_matching.call(where: { series: 'Earthsea' }, envelope: true)
|
259
|
+
result.value
|
260
|
+
#=> { books: [{ ... }, { ... }, { ... }] }
|
261
|
+
```
|
262
|
+
|
263
|
+
- The `:scope` keyword allows you to pass a query to the command. Only entities that match the given scope will be found and returned by `#find_matching`.
|
264
|
+
|
265
|
+
##### Find One
|
266
|
+
|
267
|
+
The `FindOne` command takes a primary key value and returns the entity with the specified primary key.
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
result = collection.find_one.call(primary_key: 1)
|
271
|
+
result.value
|
272
|
+
#=> {
|
273
|
+
# 'id' => 1,
|
274
|
+
# 'title' => 'The Silmarillion',
|
275
|
+
# 'author' => 'J.R.R. Tolkien',
|
276
|
+
# 'series' => nil,
|
277
|
+
# 'category' => 'Science Fiction and Fantasy',
|
278
|
+
# 'published_at' => '1977-09-15'
|
279
|
+
# }
|
280
|
+
```
|
281
|
+
|
282
|
+
The `FindOne` command has several options:
|
283
|
+
|
284
|
+
- The `:envelope` keyword wraps the result value in an envelope hash, with a key equal to the singular name of the collection and whose value is the returned entity.
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
result = collection.find_one.call(primary_key: 1, envelope: true)
|
288
|
+
result.value
|
289
|
+
#=> { book: {} }
|
290
|
+
```
|
291
|
+
|
292
|
+
- The `:scope` keyword allows you to pass a query to the command. Only an entity that match the given scope will be found and returned by `#find_one`.
|
293
|
+
|
294
|
+
If the collection does not include an entity with the specified primary key, the `#find_one` command will return a failing result with a `NotFound` error (see [Errors](#errors), below).
|
295
|
+
|
296
|
+
##### Insert One
|
297
|
+
|
298
|
+
The `InsertOne` command takes an entity and inserts that entity into the collection.
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
|
302
|
+
result = collection.insert_one.call(entity: entity)
|
303
|
+
|
304
|
+
result.value
|
305
|
+
#=> {
|
306
|
+
# 'id' => 10,
|
307
|
+
# 'title' => 'Gideon the Ninth',
|
308
|
+
# 'author' => 'Tammsyn Muir'
|
309
|
+
# }
|
310
|
+
|
311
|
+
collection.query.where(id: 10).exists?
|
312
|
+
#=> true
|
313
|
+
```
|
314
|
+
|
315
|
+
If the collection already includes an entity with the specified primary key, the `#insert_one` command will return a failing result with an `AlreadyExists` error (see [Errors](#errors), below).
|
316
|
+
|
317
|
+
##### Update One
|
318
|
+
|
319
|
+
The `UpdateOne` command takes an entity and updates the corresponding entity in the collection.
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
book = collection.find_one.call(1).value
|
323
|
+
book = book.merge('author' => 'John Ronald Reuel Tolkien')
|
324
|
+
result = collection.update_one(entity: book)
|
325
|
+
|
326
|
+
result.value
|
327
|
+
#=> {
|
328
|
+
# 'id' => 1,
|
329
|
+
# 'title' => 'The Silmarillion',
|
330
|
+
# 'author' => 'J.R.R. Tolkien',
|
331
|
+
# 'series' => nil,
|
332
|
+
# 'category' => 'Science Fiction and Fantasy',
|
333
|
+
# 'published_at' => '1977-09-15'
|
334
|
+
# }
|
335
|
+
|
336
|
+
collection
|
337
|
+
.query
|
338
|
+
.where(title: 'The Silmarillion', author: 'John Ronald Reuel Tolkien')
|
339
|
+
.exists?
|
340
|
+
#=> true
|
341
|
+
```
|
342
|
+
|
343
|
+
If the collection does not include an entity with the specified entity's primary key, the `#update_one` command will return a failing result with a `NotFound` error (see [Errors](#errors), below).
|
344
|
+
|
345
|
+
##### Validate One
|
346
|
+
|
347
|
+
The `ValidateOne` command takes an entity and a `Stannum` contract and matches the entity to the contract. Some implementations allow specifying a default contract, either as a parameter on the collection or as a class property on the entity class; if the collection has a default contract, then the `:contract` keyword is optional.
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
contract = Stannum::Contract.new do
|
351
|
+
property :title, Stannum::Constraints::Presence.new
|
352
|
+
end
|
353
|
+
|
354
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
|
355
|
+
result = collection.validate_one.call(contract: contract, entity: book)
|
356
|
+
result.success?
|
357
|
+
#=> true
|
358
|
+
```
|
359
|
+
|
360
|
+
If the contract does not match the entity, the `#validate_one` command will return a failing result with a `ValidationFailed` error (see [Errors](#errors), below).
|
361
|
+
|
362
|
+
If the collection does not specify a default contract and no `:contract` keyword is provided, the `#validate_one` command will return a failing result with a `MissingDefaultContract` error.
|
363
|
+
|
364
|
+
#### Basic Collection
|
365
|
+
|
366
|
+
```
|
367
|
+
require 'cuprum/collections/basic'
|
368
|
+
```
|
369
|
+
|
370
|
+
The `Cuprum::Basic::Collection` provides a reference implementation of a collection. It uses an in-memory `Array` to store `Hash`es with `String` keys. All of the command examples above use a basic collection as an example.
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
collection = Cuprum::Collections::Basic.new(
|
374
|
+
collection_name: 'books',
|
375
|
+
data: book_data,
|
376
|
+
)
|
377
|
+
```
|
378
|
+
|
379
|
+
Initializing a basic collection requires, at a minumum, the following keywords:
|
380
|
+
|
381
|
+
- The `:collection_name` parameter sets the name of the collection. It is used to create an envelope for query commands, such as the `FindMany`, `FindMatching` and `FindOne` commands.
|
382
|
+
- The `:data` parameter initializes the collection with existing data. The data must be either an empty array or an `Array` of `Hash`es with `String` keys.
|
383
|
+
|
384
|
+
You can also specify some optional keywords:
|
385
|
+
|
386
|
+
- The `:default_contract` parameter sets a default contract for validating collection entities. If no `:contract` keyword is passed to the `ValidateOne` command, it will use the default contract to validate the entity.
|
387
|
+
- The `:member_name` parameter is used to create an envelope for singular query commands such as the `FindOne` command. If not given, the member name will be generated automatically as a singular form of the collection name.
|
388
|
+
- The `:primary_key_name` parameter specifies the attribute that serves as the primary key for the collection entities. The default value is `:id`.
|
389
|
+
- The `:primary_key_type` parameter specifies the type of the primary key attribute. The default value is `Integer`.
|
390
|
+
|
391
|
+
<a id="constraints"></a>
|
392
|
+
|
393
|
+
### Constraints
|
394
|
+
|
395
|
+
`Cuprum::Collections` defines a small number of `Stannum` constraints for validating command parameters.
|
396
|
+
|
397
|
+
**Attribute Name**
|
398
|
+
|
399
|
+
A `Cuprum::Collections::Constraints::AttributeName` constraint validates that the object is a valid attribute name. Specifically, that the object either a `String` or a `Symbol` and that it is not `#empty?`.
|
400
|
+
|
401
|
+
**Ordering**
|
402
|
+
|
403
|
+
A `Cuprum::Collections::Constraints::Ordering` constraint validates that the object is a valid sort ordering. An ordering must be one of the following:
|
404
|
+
|
405
|
+
- `nil`
|
406
|
+
- A valid attribute name, e.g. `title` or `:author`
|
407
|
+
- An array of valid attribute names, e.g. `['title', 'author']` or `[:series, :publisher]`
|
408
|
+
- A hash of valid attribute names and sort directions, e.g. `{ title: :descending }`
|
409
|
+
- An array of valid attribute names, with the last item of the array a hash of valid attribute names and sort directions, e.g. `[:author, :series, { published_at: :ascending }]`
|
410
|
+
|
411
|
+
**Sort Direction**
|
412
|
+
|
413
|
+
A `Cuprum::Collections::Constraints::Order::SortDirection` constraint validates that the object is a valid sort direction. Specifically, that the object is either a `String` or a `Symbol` and that is has a value of `'asc'`, `'ascending'`, `'desc'`, or `'descending'`.
|
414
|
+
|
415
|
+
<a id="errors"></a>
|
416
|
+
|
417
|
+
### Errors
|
418
|
+
|
419
|
+
`Cuprum::Collections` defines a set of errors to be used in failed command results.
|
420
|
+
|
421
|
+
**AlreadyExists**
|
422
|
+
|
423
|
+
A `Cuprum::Collections::Errors::AlreadyExists` error is used when an entity already exists in the collection with the given primary key, e.g. in an `InsertOne` command.
|
424
|
+
|
425
|
+
It has the following properties:
|
426
|
+
|
427
|
+
- `#collection_name`: The name of the collection used in the command.
|
428
|
+
- `#primary_key_name`: The name of the primary key attribute, e.g. `'id'`.
|
429
|
+
- `#primary_key_values`: The values of the duplicate primary keys, e.g. `[1]`.
|
430
|
+
|
431
|
+
**Extra Attributes**
|
432
|
+
|
433
|
+
A `Cuprum::Collections::Errors::ExtraAttributes` error is used when attempting to set attributes on an entity that are not defined for that entity class.
|
434
|
+
|
435
|
+
It has the following properties:
|
436
|
+
|
437
|
+
- `#entity_class`: The class of the entity used in the command.
|
438
|
+
- `#extra_attributes`: The names of the invalid attributes that the command attempted to set, as an `Array` of `String`s.
|
439
|
+
- `#valid_attributes`: The names of the valid attributes for the entity class, as an `Array` of `String`s.
|
440
|
+
|
441
|
+
**Failed Validation**
|
442
|
+
|
443
|
+
A `Cuprum::Collections::Errors::FailedValidation` error is used when an entity fails validation in a command.
|
444
|
+
|
445
|
+
It has the following properties:
|
446
|
+
|
447
|
+
- `#entity_class`: The class of the entity used in the command.
|
448
|
+
- `#errors`: The validation error messages, grouped by the error path.
|
449
|
+
|
450
|
+
**Invalid Parameters**
|
451
|
+
|
452
|
+
A `Cuprum::Collections::Errors::InvalidParameters` error is used when attempting to call a command with invalid parameters for that command.
|
453
|
+
|
454
|
+
It has the following properties:
|
455
|
+
|
456
|
+
- `#command`: The command that was called.
|
457
|
+
- `#errors`: The validation errors for the parameters, as an `Array` of error `Hash`es.
|
458
|
+
|
459
|
+
**Invalid Query**
|
460
|
+
|
461
|
+
A `Cuprum::Collections::Errors::InvalidQuery` error is used when attempting to call a `FindMatching` command with invalid parameters for the query filter.
|
462
|
+
|
463
|
+
It has the following properties:
|
464
|
+
|
465
|
+
- `#errors`: The validation error from the parsing strategy, as an `Array` of error `Hash`es.
|
466
|
+
- `#strategy`: The name of the attempted parsing strategy.
|
467
|
+
|
468
|
+
**Missing Default Contract**
|
469
|
+
|
470
|
+
A `Cuprum::Collections::Errors::MissingDefaultContract`error is used when attempting to call a validation command without a contract and the collection does not define a default contract.
|
471
|
+
|
472
|
+
It has the following properties:
|
473
|
+
|
474
|
+
- `#entity_class`: The class of the entity used in the command.
|
475
|
+
|
476
|
+
**Not Found**
|
477
|
+
|
478
|
+
A `Cuprum::Collections::Errors::NotFound` error is used when an entity with the requested primary key does not exist in the collection.
|
479
|
+
|
480
|
+
- `#collection_name`: The name of the collection used in the command.
|
481
|
+
- `#primary_key_name`: The name of the primary key attribute, e.g. `'id'`.
|
482
|
+
- `#primary_key_values`: The values of the missing primary keys, e.g. `[1]`.
|
483
|
+
|
484
|
+
**Unknown Operator**
|
485
|
+
|
486
|
+
A `Cuprum::Collections::Errors::UnknownOperator` error is used when attempting to perform a filter operation with an operator that is either invalid or not implemented by the collection.
|
487
|
+
|
488
|
+
It has the following properties:
|
489
|
+
|
490
|
+
- `#operator`: The name of the unrecognized operator.
|
491
|
+
|
492
|
+
<a id="queries"></a>
|
493
|
+
|
494
|
+
### Queries
|
495
|
+
|
496
|
+
A `Cuprum::Collections::Query` provides a low-level interface for performing query operations on a collection's data.
|
497
|
+
|
498
|
+
```ruby
|
499
|
+
collection = Cuprum::Collections::Basic.new(
|
500
|
+
collection_name: 'books',
|
501
|
+
data: book_data,
|
502
|
+
)
|
503
|
+
query = collection.query
|
504
|
+
|
505
|
+
query.class
|
506
|
+
#=> Cuprum::Collections::Basic::Query
|
507
|
+
query.count
|
508
|
+
#=> 10
|
509
|
+
query.limit(3).to_a
|
510
|
+
#=> [
|
511
|
+
# {
|
512
|
+
# 'id' => 0,
|
513
|
+
# 'title' => 'The Hobbit',
|
514
|
+
# 'author' => 'J.R.R. Tolkien',
|
515
|
+
# 'series' => nil,
|
516
|
+
# 'category' => 'Science Fiction and Fantasy',
|
517
|
+
# 'published_at' => '1937-09-21'
|
518
|
+
# },
|
519
|
+
# {
|
520
|
+
# 'id' => 1,
|
521
|
+
# 'title' => 'The Silmarillion',
|
522
|
+
# 'author' => 'J.R.R. Tolkien',
|
523
|
+
# 'series' => nil,
|
524
|
+
# 'category' => 'Science Fiction and Fantasy',
|
525
|
+
# 'published_at' => '1977-09-15'
|
526
|
+
# },
|
527
|
+
# {
|
528
|
+
# 'id' => 2,
|
529
|
+
# 'title' => 'The Fellowship of the Ring',
|
530
|
+
# 'author' => 'J.R.R. Tolkien',
|
531
|
+
# 'series' => 'The Lord of the Rings',
|
532
|
+
# 'category' => 'Science Fiction and Fantasy',
|
533
|
+
# 'published_at' => '1954-07-29'
|
534
|
+
# }
|
535
|
+
# ]
|
536
|
+
```
|
537
|
+
|
538
|
+
Each collection defines its own `Query` implementation, but the interface should be identical except for the class of the yielded or returned entities.
|
539
|
+
|
540
|
+
#### Query Methods
|
541
|
+
|
542
|
+
Every `Cuprum::Collections::Query` implementation defines the following methods.
|
543
|
+
|
544
|
+
**#count**
|
545
|
+
|
546
|
+
The `#count` method takes no parameters and returns the number of items in the collection that match the given criteria.
|
547
|
+
|
548
|
+
```ruby
|
549
|
+
query.count
|
550
|
+
#=> 10
|
551
|
+
```
|
552
|
+
|
553
|
+
**#each**
|
554
|
+
|
555
|
+
The `#each` method takes a block and yields to the block each item in the collection that matches the given criteria, in the given order.
|
556
|
+
|
557
|
+
```ruby
|
558
|
+
query.each do |book|
|
559
|
+
puts book.title if book.series == 'Earthsea'
|
560
|
+
end
|
561
|
+
#=> prints "A Wizard of Earthsea", "The Tombs of Atuan", "The Farthest Shore"
|
562
|
+
```
|
563
|
+
|
564
|
+
**#exists**
|
565
|
+
|
566
|
+
The `#exists?` method takes no parameters and returns `true` if there are any items in the collection that match the given criteria, or `false` if there are no matching items.
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
query.exists?
|
570
|
+
#=> true
|
571
|
+
query.where({ series: 'The Wheel of Time' }).exists?
|
572
|
+
#=> false
|
573
|
+
```
|
574
|
+
|
575
|
+
**#limit**
|
576
|
+
|
577
|
+
The `#limit` method takes a count of items and returns a copy of the query. The copied query has a limit constraint, and will yield or return up to the requested number of items when called with `#each` or `#to_a`.
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
query.limit(3).to_a
|
581
|
+
#=> [
|
582
|
+
# {
|
583
|
+
# 'id' => 0,
|
584
|
+
# 'title' => 'The Hobbit',
|
585
|
+
# 'author' => 'J.R.R. Tolkien',
|
586
|
+
# 'series' => nil,
|
587
|
+
# 'category' => 'Science Fiction and Fantasy',
|
588
|
+
# 'published_at' => '1937-09-21'
|
589
|
+
# },
|
590
|
+
# {
|
591
|
+
# 'id' => 1,
|
592
|
+
# 'title' => 'The Silmarillion',
|
593
|
+
# 'author' => 'J.R.R. Tolkien',
|
594
|
+
# 'series' => nil,
|
595
|
+
# 'category' => 'Science Fiction and Fantasy',
|
596
|
+
# 'published_at' => '1977-09-15'
|
597
|
+
# },
|
598
|
+
# {
|
599
|
+
# 'id' => 2,
|
600
|
+
# 'title' => 'The Fellowship of the Ring',
|
601
|
+
# 'author' => 'J.R.R. Tolkien',
|
602
|
+
# 'series' => 'The Lord of the Rings',
|
603
|
+
# 'category' => 'Science Fiction and Fantasy',
|
604
|
+
# 'published_at' => '1954-07-29'
|
605
|
+
# }
|
606
|
+
# ]
|
607
|
+
```
|
608
|
+
|
609
|
+
*Note:* Not all collections provide a guarantee of a default ordering - for consistent results using `#limit` and `#offset`, specify an explicit order for the query.
|
610
|
+
|
611
|
+
**#offset**
|
612
|
+
|
613
|
+
The `#offset` method takes a count of items and returns a copy of the query. The copied query has an offset constraint, and will skip the requested number of items when called with `#each` or `#to_a`.
|
614
|
+
|
615
|
+
```ruby
|
616
|
+
query.offset(7)
|
617
|
+
#=> [
|
618
|
+
# {
|
619
|
+
# 'id' => 7,
|
620
|
+
# 'title' => 'A Wizard of Earthsea',
|
621
|
+
# 'author' => 'Ursula K. LeGuin',
|
622
|
+
# 'series' => 'Earthsea',
|
623
|
+
# 'category' => 'Science Fiction and Fantasy',
|
624
|
+
# 'published_at' => '1968-11-01'
|
625
|
+
# },
|
626
|
+
# {
|
627
|
+
# 'id' => 8,
|
628
|
+
# 'title' => 'The Tombs of Atuan',
|
629
|
+
# 'author' => 'Ursula K. LeGuin',
|
630
|
+
# 'series' => 'Earthsea',
|
631
|
+
# 'category' => 'Science Fiction and Fantasy',
|
632
|
+
# 'published_at' => '1970-12-01'
|
633
|
+
# },
|
634
|
+
# {
|
635
|
+
# 'id' => 9,
|
636
|
+
# 'title' => 'The Farthest Shore',
|
637
|
+
# 'author' => 'Ursula K. LeGuin',
|
638
|
+
# 'series' => 'Earthsea',
|
639
|
+
# 'category' => 'Science Fiction and Fantasy',
|
640
|
+
# 'published_at' => '1972-09-01'
|
641
|
+
# }
|
642
|
+
# ]
|
643
|
+
```
|
644
|
+
|
645
|
+
*Note:* Not all collections provide a guarantee of a default ordering - for consistent results using `#limit` and `#offset`, specify an explicit order for the query.
|
646
|
+
|
647
|
+
**#order**
|
648
|
+
|
649
|
+
The `#order` method takes a valid sort ordering and returns a copy of the query. The copied query uses the specified order, and will yield or return items in that order when called with `#each` or `#to_a`. For details on specifying a sort order, see [Query Ordering](#queries-ordering), below.
|
650
|
+
|
651
|
+
```ruby
|
652
|
+
query.where(series: 'The Lord of the Rings').order({ title: 'desc' })
|
653
|
+
#=> [
|
654
|
+
# {
|
655
|
+
# 'id' => 3,
|
656
|
+
# 'title' => 'The Two Towers',
|
657
|
+
# 'author' => 'J.R.R. Tolkien',
|
658
|
+
# 'series' => 'The Lord of the Rings',
|
659
|
+
# 'category' => 'Science Fiction and Fantasy',
|
660
|
+
# 'published_at' => '1954-11-11'
|
661
|
+
# },
|
662
|
+
# {
|
663
|
+
# 'id' => 4,
|
664
|
+
# 'title' => 'The Return of the King',
|
665
|
+
# 'author' => 'J.R.R. Tolkien',
|
666
|
+
# 'series' => 'The Lord of the Rings',
|
667
|
+
# 'category' => 'Science Fiction and Fantasy',
|
668
|
+
# 'published_at' => '1955-10-20'
|
669
|
+
# },
|
670
|
+
# {
|
671
|
+
# 'id' => 2,
|
672
|
+
# 'title' => 'The Fellowship of the Ring',
|
673
|
+
# 'author' => 'J.R.R. Tolkien',
|
674
|
+
# 'series' => 'The Lord of the Rings',
|
675
|
+
# 'category' => 'Science Fiction and Fantasy',
|
676
|
+
# 'published_at' => '1954-07-29'
|
677
|
+
# }
|
678
|
+
# ]
|
679
|
+
```
|
680
|
+
|
681
|
+
**#reset**
|
682
|
+
|
683
|
+
The `#reset` method takes no parameters and returns the query. By default, a `Query` will cache the results when calling `#each` or `#to_a`. The `#reset` method clears this cache and forces the query to perform another query on the underlying data.
|
684
|
+
|
685
|
+
```ruby
|
686
|
+
query.count
|
687
|
+
#=> 10
|
688
|
+
|
689
|
+
book = { id: 10, title: 'Gideon the Ninth', author: 'Tammsyn Muir' }
|
690
|
+
collection.insert_one.call(entity: book)
|
691
|
+
|
692
|
+
query.count
|
693
|
+
#=> 10
|
694
|
+
query.reset.count
|
695
|
+
#=> 11
|
696
|
+
```
|
697
|
+
|
698
|
+
**#to_a**
|
699
|
+
|
700
|
+
The `#to_a` method takes no parameters and returns an `Array` containing the itmes in the collection that match the given criteria, in the given order.
|
701
|
+
|
702
|
+
```ruby
|
703
|
+
query.to_a.map { |book| book['title'] }
|
704
|
+
#=> [
|
705
|
+
# 'The Hobbit',
|
706
|
+
# 'The Silmarillion',
|
707
|
+
# 'The Fellowship of the Ring',
|
708
|
+
# 'The Two Towers',
|
709
|
+
# 'The Return of the King',
|
710
|
+
# 'The Word for World is Forest',
|
711
|
+
# 'The Ones Who Walk Away From Omelas',
|
712
|
+
# 'A Wizard of Earthsea',
|
713
|
+
# 'The Tombs of Atuan',
|
714
|
+
# 'The Farthest Shore'
|
715
|
+
# ]
|
716
|
+
```
|
717
|
+
|
718
|
+
**#where**
|
719
|
+
|
720
|
+
The `#where` method takes a Hash argument or a block and returns a copy of the query. The copied query applies the given filters, and will yield or return only items that match the given criteria when called with `#each` or `#to_a`.
|
721
|
+
|
722
|
+
```ruby
|
723
|
+
query.where(series: 'Earthsea').to_a
|
724
|
+
#=> [
|
725
|
+
# {
|
726
|
+
# 'id' => 7,
|
727
|
+
# 'title' => 'A Wizard of Earthsea',
|
728
|
+
# 'author' => 'Ursula K. LeGuin',
|
729
|
+
# 'series' => 'Earthsea',
|
730
|
+
# 'category' => 'Science Fiction and Fantasy',
|
731
|
+
# 'published_at' => '1968-11-01'
|
732
|
+
# },
|
733
|
+
# {
|
734
|
+
# 'id' => 8,
|
735
|
+
# 'title' => 'The Tombs of Atuan',
|
736
|
+
# 'author' => 'Ursula K. LeGuin',
|
737
|
+
# 'series' => 'Earthsea',
|
738
|
+
# 'category' => 'Science Fiction and Fantasy',
|
739
|
+
# 'published_at' => '1970-12-01'
|
740
|
+
# },
|
741
|
+
# {
|
742
|
+
# 'id' => 9,
|
743
|
+
# 'title' => 'The Farthest Shore',
|
744
|
+
# 'author' => 'Ursula K. LeGuin',
|
745
|
+
# 'series' => 'Earthsea',
|
746
|
+
# 'category' => 'Science Fiction and Fantasy',
|
747
|
+
# 'published_at' => '1972-09-01'
|
748
|
+
# }
|
749
|
+
# ]
|
750
|
+
```
|
751
|
+
|
752
|
+
<a id="queries-ordering"></a>
|
753
|
+
|
754
|
+
#### Query Ordering
|
755
|
+
|
756
|
+
You can set the sort order of returned or yielded query results by passing a valid ordering to the query. For a `FindMatching` command, pass an `:order` keyword to `#call`. When using a query directly, use the `#order` method.
|
757
|
+
|
758
|
+
Any of the following is a valid ordering:
|
759
|
+
|
760
|
+
- `nil`
|
761
|
+
- A valid attribute name, e.g. `title` or `:author`
|
762
|
+
- An array of valid attribute names, e.g. `['title', 'author']` or `[:series, :publisher]`
|
763
|
+
- A hash of valid attribute names and sort directions, e.g. `{ title: :descending }`
|
764
|
+
- An array of valid attribute names, with the last item of the array a hash of valid attribute names and sort directions, e.g. `[:author, :series, { published_at: :ascending }]`
|
765
|
+
|
766
|
+
Internally, the sort order is converted to an ordered `Hash` with attribute name keys and sort direction values. The query results will be sorted by the given attributes in the specified order.
|
767
|
+
|
768
|
+
For example, a order of `{ author: :asc, title: :descending }` will sort the results by `:author` in ascending order. For each author, the results are then sorted by `:title` in descending order.
|
769
|
+
|
770
|
+
<a id="queries-filtering"></a>
|
771
|
+
|
772
|
+
#### Query Filtering
|
773
|
+
|
774
|
+
You can filter the results returned or yielded by a query by passing a valid criteria object to the query. For a `FindMatching` command, pass a `:where` keyword to `#call`, or use the block form to use the query builder to apply advanced operators. When using a query directly, use the `#where` method.
|
775
|
+
|
776
|
+
```ruby
|
777
|
+
query = collection.query.where({ author: 'Ursula K. LeGuin' })
|
778
|
+
query.count
|
779
|
+
#=> 5
|
780
|
+
query.each.map(&:author).uniq
|
781
|
+
#=> ['Ursula K. LeGuin']
|
782
|
+
```
|
783
|
+
|
784
|
+
The simplest way to filter results is by passing a `Hash` to `#where`. The keys of the Hash should be the names of the attributes to filter by, and the values the expected value of that attribute. However, passing a Hash directly only supports equality comparisons. To use advanced operators, use the block form:
|
785
|
+
|
786
|
+
```ruby
|
787
|
+
query = collection.query.where do
|
788
|
+
{
|
789
|
+
author: 'Ursula K. LeGuin',
|
790
|
+
series: equal('Earthsea'),
|
791
|
+
published_at: greater_than('1970-01-01')
|
792
|
+
}
|
793
|
+
end
|
794
|
+
query.count
|
795
|
+
#=> 2
|
796
|
+
query.each.map(&:title)
|
797
|
+
#=> [
|
798
|
+
# 'The Tombs of Atuan',
|
799
|
+
# 'The Farthest Shore'
|
800
|
+
# ]
|
801
|
+
```
|
802
|
+
|
803
|
+
Instead of passing a `Hash` directly, we pass a block to the `#where` method (or `#call` for a command) that *returns* a `Hash`. This allows us to use a Domain-Specific Language to generate our criteria. In the example above, we are using an exact value for the author - this is automatically converted to an `#equal` criterion, just as it is when passing a Hash. We are also using the `#greater_than` operator to filter our results.
|
804
|
+
|
805
|
+
##### Operators
|
806
|
+
|
807
|
+
Each query implementation defines the following operators:
|
808
|
+
|
809
|
+
**#equal**
|
810
|
+
|
811
|
+
The `#equal` operator asserts that the attribute value is equal to the expected value.
|
812
|
+
|
813
|
+
```ruby
|
814
|
+
query = collection.query.where do
|
815
|
+
{ title: equal('The Hobbit') }
|
816
|
+
end
|
817
|
+
query.count
|
818
|
+
#=> 1
|
819
|
+
query.each.map(&:title)
|
820
|
+
#=> ['The Hobbit']
|
821
|
+
```
|
822
|
+
|
823
|
+
**#greater_than**
|
824
|
+
|
825
|
+
The `#greater_than` operator asserts that the attribute value is strictly greater than the expected value. It is primarily used with numeric or date/time attributes.
|
826
|
+
|
827
|
+
```ruby
|
828
|
+
query = collection.query.where do
|
829
|
+
{
|
830
|
+
series: 'The Lord of the Rings',
|
831
|
+
published_at: greater_than('1954-11-11')
|
832
|
+
}
|
833
|
+
end
|
834
|
+
query.count
|
835
|
+
#=> 1
|
836
|
+
query.each.map(&:title)
|
837
|
+
#=> ['The Return of the King']
|
838
|
+
```
|
839
|
+
|
840
|
+
**#greater_than_or_equal_to**
|
841
|
+
|
842
|
+
The `#greater_than_or_equal_to` operator asserts that the attribute value is greater than or equal to the expected value. It is primarily used with numeric or date/time attributes.
|
843
|
+
|
844
|
+
```ruby
|
845
|
+
query = collection.query.where do
|
846
|
+
{
|
847
|
+
series: 'The Lord of the Rings',
|
848
|
+
published_at: greater_than_or_equal_to('1954-11-11')
|
849
|
+
}
|
850
|
+
end
|
851
|
+
query.count
|
852
|
+
#=> 2
|
853
|
+
query.each.map(&:title)
|
854
|
+
#=> ['The Two Towers', 'The Return of the King']
|
855
|
+
```
|
856
|
+
|
857
|
+
**#less_than**
|
858
|
+
|
859
|
+
The `#less_than` operator asserts that the attribute value is strictly greater than the expected value. It is primarily used with numeric or date/time attributes.
|
860
|
+
|
861
|
+
```ruby
|
862
|
+
query = collection.query.where do
|
863
|
+
{
|
864
|
+
series: 'The Lord of the Rings',
|
865
|
+
published_at: less_than('1954-11-11')
|
866
|
+
}
|
867
|
+
end
|
868
|
+
query.count
|
869
|
+
#=> 1
|
870
|
+
query.each.map(&:title)
|
871
|
+
#=> ['The Fellowship of the Ring']
|
872
|
+
```
|
873
|
+
|
874
|
+
**#less_than_or_equal_to**
|
875
|
+
|
876
|
+
The `#less_than_or_equal_to` operator asserts that the attribute value is strictly greater than the expected value. It is primarily used with numeric or date/time attributes.
|
877
|
+
|
878
|
+
```ruby
|
879
|
+
query = collection.query.where do
|
880
|
+
{
|
881
|
+
series: 'The Lord of the Rings',
|
882
|
+
published_at: less_than_or_equal_to('1954-11-11')
|
883
|
+
}
|
884
|
+
end
|
885
|
+
query.count
|
886
|
+
#=> 2
|
887
|
+
query.each.map(&:title)
|
888
|
+
#=> ['The Fellowship of the Ring', 'The Two Towers']
|
889
|
+
```
|
890
|
+
|
891
|
+
**#not_equal**
|
892
|
+
|
893
|
+
The `#not_equal` operator asserts that the attribute value is not equal to the expected value. It is the inverse of the `#equal` operator.
|
894
|
+
|
895
|
+
```ruby
|
896
|
+
query = collection.query.where do
|
897
|
+
{
|
898
|
+
author: 'J.R.R. Tolkien',
|
899
|
+
series: not_equal('The Lord of the Rings')
|
900
|
+
}
|
901
|
+
end
|
902
|
+
query.count
|
903
|
+
#=> 2
|
904
|
+
query.each.map(&:title)
|
905
|
+
#=> ['The Hobbit', 'The Silmarillion']
|
906
|
+
```
|
907
|
+
|
908
|
+
**#not_one_of**
|
909
|
+
|
910
|
+
The `#one_of` operator asserts that the attribute value is not equal to any of the expected values. It is the inverse of the `#one_of` operator.
|
911
|
+
|
912
|
+
```ruby
|
913
|
+
query = collection.query.where do
|
914
|
+
{
|
915
|
+
series: not_one_of(['Earthsea', 'The Lord of the Rings'])
|
916
|
+
}
|
917
|
+
end
|
918
|
+
query.count
|
919
|
+
#=> 4
|
920
|
+
query.each.map(&:title)
|
921
|
+
#=> [
|
922
|
+
# 'The Hobbit',
|
923
|
+
# 'The Silmarillion',
|
924
|
+
# 'The Word for World is Forest',
|
925
|
+
# 'The Ones Who Walk Away From Omelas'
|
926
|
+
# ]
|
927
|
+
```
|
928
|
+
|
929
|
+
**#one_of**
|
930
|
+
|
931
|
+
The `#one_of` operator asserts that the attribute value is equal to one of the expected values.
|
932
|
+
|
933
|
+
```ruby
|
934
|
+
query = collection.query.where do
|
935
|
+
{
|
936
|
+
series: one_of(['Earthsea', 'The Lord of the Rings'])
|
937
|
+
}
|
938
|
+
end
|
939
|
+
query.count
|
940
|
+
#=> 6
|
941
|
+
query.each.map(&:title)
|
942
|
+
#=> [
|
943
|
+
# 'The Fellowship of the Ring',
|
944
|
+
# 'The Two Towers',
|
945
|
+
# 'The Return of the King',
|
946
|
+
# 'A Wizard of Earthsea',
|
947
|
+
# 'The Tombs of Atuan',
|
948
|
+
# 'The Farthest Shore'
|
949
|
+
# ]
|
950
|
+
```
|