audrey 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +610 -0
  3. data/lib/audrey.rb +2503 -0
  4. metadata +74 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 61cf118da49fbae7bf7dbc82dbf03f1460fe26d9925b135374e97a930039d198
4
+ data.tar.gz: ffc33814130bfcd6b24f70d3d4aebd5c71aff75922fa1cf9fb090789948d6fc9
5
+ SHA512:
6
+ metadata.gz: b8e4a31b17b175e5c038ba1063bddafca7c9e68e59655a764fade27d6459b43232336fc263d32040e7b8e8f75f7c58066af6a433711b3a020efdffe111f9963f
7
+ data.tar.gz: 8f015943ce4204efc60ba516bc6e259044d2be8b6b10d73c5873e042eaaecf1165425f5871afa2d251ea71e1deb3939528b9366f99e24ce11b0ec744a30e76a6
@@ -0,0 +1,610 @@
1
+ # Audrey
2
+
3
+ PLEASE NOTE: Audrey is in the very early stages of development. You're welcome
4
+ to try it out, but it's not ready for prime time yet.
5
+
6
+ Audrey is an easy, yet powerful database system. With Audrey you can create your
7
+ database and start using it in a few lines of code.
8
+
9
+ ## Install
10
+
11
+ ```ruby
12
+ <!-- gem install audrey -->
13
+ ```
14
+
15
+ ## Basic usage
16
+
17
+ By default, Audrey uses SQLite for storage, so the only dependencies are SQLite,
18
+ which is standard on most Unixish systems, and this gem. To create and start
19
+ using a Audrey database, all you have to do is open it with `Audrey.connect`,
20
+ giving a path to a database file and a read/write mode - `'rw'`, `'r'`, or
21
+ `'w'`. The file doesn't need to already exist; it will be created automatically
22
+ as needed.
23
+
24
+ ```ruby
25
+ require 'audrey'
26
+ path = '/tmp/my.db'
27
+
28
+ Audrey.connect(path, 'rw') do |db|
29
+ end
30
+ ```
31
+
32
+ It its simplest use, use the `db` object as a hash to store information:
33
+
34
+ ```text
35
+ Audrey.connect(path, 'rw') do |db|
36
+ db['hero'] = 'Thor'
37
+ db['antagonist'] = 'Loki'
38
+ db['score'] = 1.3
39
+ db['ready'] = true
40
+
41
+ db.each do |k, v|
42
+ puts k + ': ' + v.to_s
43
+ end
44
+ end
45
+ ```
46
+
47
+ That gives us this output:
48
+
49
+ ```text
50
+ hero: Thor
51
+ antagonist: Loki
52
+ score: 1.3
53
+ ready: true
54
+ ```
55
+
56
+ Audrey can store complex structures such as nested arrays and hashes.
57
+
58
+ ```ruby
59
+ Audrey.connect(path, 'rw') do |db|
60
+ db['people'] = {}
61
+ people = db['people']
62
+
63
+ people['fred'] = {'name'=>'Fred'}
64
+ people['mary'] = {'name'=>'Mary', 'towns'=>['Blacksburg', 'Seattle']}
65
+
66
+ people['mary']['friend'] = people['fred']
67
+
68
+ puts db['people']['mary']['name']
69
+ puts db['people']['mary']['towns'].join(', ')
70
+ puts db['people']['mary']['friend']['name']
71
+ end
72
+ ```
73
+
74
+ Take note of the line that reads
75
+ `people['mary']['friend'] = people['fred']`. Audrey doesn't use foreign
76
+ keys. Instead, you simply link objects directly to each other. So in this case,
77
+ `fred` is an element in the people hash, but it is *also* an element
78
+ in Mary's hash with the key `friend`. Objects can be linked in this
79
+ free form manner without the need for setting up lookup tables or defining
80
+ foreign keys.
81
+
82
+ ## Custom classes
83
+
84
+ Audrey provides a system for defining your own classes. In this way, you can
85
+ define your own classes, using their properties and methods as usual. Objects of
86
+ those classes are automatically stored in the Audrey database and can be
87
+ retrieved, as objects, for use.
88
+
89
+ Consider, for example, this simple class:
90
+
91
+ ```text
92
+ class Person < Audrey::Object::Custom
93
+ self.fco = true
94
+ field 'first'
95
+ field 'middle'
96
+ field 'surname'
97
+ end
98
+ ```
99
+
100
+ This class inherits `Audrey::Object::Custom`, which almost all of your
101
+ Audrey classes should do.
102
+
103
+ The next line,
104
+ `self.fco = true`, is important. Every Audrey class must be set at
105
+ `self.fco = true` or `self.fco = false`. `fco` mean "first class object". When
106
+ the database is closed, objects that are not first class objects, and are not
107
+ *descended* from first class objects, are purged. Every custom class must be
108
+ explicitly set with `fco=true` or `fco=false`. Think of clases with`fco=false`
109
+ as being "cascade delete".
110
+
111
+ The next few lines define fields for a person record, first, middle and surname.
112
+
113
+ So we can use our class like this:
114
+
115
+ ```text
116
+ Audrey.connect(path, 'rw') do |db|
117
+ mary = Person.new()
118
+ mary.first = 'Mary'
119
+ mary.middle = 'F.'
120
+ mary.surname = 'Sullivan'
121
+
122
+ fred = Person.new()
123
+ fred.first = 'Fred'
124
+ fred.middle = 'C.'
125
+ fred.surname = 'Murray'
126
+ end
127
+ ```
128
+
129
+ Later we're going to need to find the Person records. The simplest
130
+ way to get to them is to `each` them with the `Person` class
131
+ itself:
132
+
133
+ ```text
134
+ Audrey.connect(path, 'rw') do |db|
135
+ Person.each do |person|
136
+ puts person.first
137
+ end
138
+ end
139
+ ```
140
+
141
+ Notice that there is no need to reinstantiate the objects using data from the
142
+ database. Audrey automatically creates the objects from the stored data, and
143
+ makes them available for use as they were originally created.
144
+
145
+ The example above produces this output:
146
+
147
+ ```text
148
+ Fred
149
+ Mary
150
+ ```
151
+
152
+ Under the hood, `each` uses a type of query called q0. Later we'll look at more
153
+ details about how you can use Q0.
154
+
155
+ ### Subclassing
156
+
157
+ Like any Ruby class, you can subclass your Audrey classes. For example, let's
158
+ subclass `Person` with `Guest`, and subclass `Guest` with `Preferred`:
159
+
160
+ ```text
161
+ class Guest < Person
162
+ field 'stays'
163
+ end
164
+
165
+ class Preferred < Guest
166
+ field 'rating'
167
+ end
168
+ ```
169
+
170
+ Notice that we didn't bother to set these subclasses as first class objects --
171
+ they inherited that property from `Person`. We also added a field to each of
172
+ our new subclasses. Now we can add `Guest` and `Preferred` objects to the
173
+ database:
174
+
175
+ ```text
176
+ Audrey.connect(path, 'rw') do |db|
177
+ dan = Guest.new()
178
+ dan.first = 'Dan'
179
+ dan.stays = 10
180
+
181
+ pete = Preferred.new()
182
+ pete.first = 'Pete'
183
+ pete.stays = 12
184
+ pete.rating = 'gold'
185
+ end
186
+ ```
187
+
188
+ Because `Guest` and `Preferred` derive from `Person`, we can iterate through
189
+ all of the records using `Person`, including objects from its derived classes.
190
+
191
+ ```text
192
+ Audrey.connect(path, 'rw') do |db|
193
+ Person.each do |person|
194
+ puts person.first
195
+ end
196
+ end
197
+ ```
198
+
199
+ which produces this output:
200
+
201
+ ```text
202
+ Fred
203
+ Pete
204
+ Mary
205
+ Dan
206
+ ```
207
+
208
+
209
+ ## Autocommit and transactions
210
+
211
+ In all the examples so far, data has been written to the Audrey database
212
+ automatically as it has been produced. However, you might want to only
213
+ atomically commit data at specified points. You can do this using the
214
+ autocommit feature, or transactions.
215
+
216
+ ### autocommit
217
+
218
+ To keep Audrey from automatically writing data, use the `autocommit` option in
219
+ `connect`, like this:
220
+
221
+ ```ruby
222
+ Audrey.connect(path, 'rw', 'immediate_commit'=>false) do |db|
223
+ db['hero'] = 'Thor'
224
+ end
225
+ ```
226
+
227
+ In the example above, the data is never committed by the end of the session, so
228
+ if we try to retrieve the data:
229
+
230
+ ```ruby
231
+ Audrey.connect(path, 'rw') do |db|
232
+ puts 'hero: ', db['hero']
233
+ end
234
+ ```
235
+
236
+ \.\.\. we get this disappointing output:
237
+
238
+ ```text
239
+ hero:
240
+
241
+ ```
242
+
243
+ To commit, simply use the `commit` method:
244
+
245
+ ```ruby
246
+ Audrey.connect(path, 'rw', 'immediate_commit'=>false) do |db|
247
+ db['hero'] = 'Thor'
248
+ db.commit
249
+ end
250
+ ```
251
+
252
+ Which will give us more fulfilling results:
253
+
254
+ ```text
255
+ hero:
256
+ Thor
257
+ ```
258
+
259
+ Any time during the session you can use `rollback` to rollback to the previous
260
+ commit or to the state of the database when it was opened. So this code:
261
+
262
+ ```ruby
263
+ Audrey.connect(path, 'rw', 'immediate_commit'=>false) do |db|
264
+ db['antagonist'] = 'Loki'
265
+ db.rollback
266
+ puts 'antagonist: ', db['antagonist']
267
+ end
268
+ ```
269
+
270
+ \.\.\. will output without Loki:
271
+
272
+ ```text
273
+ antagonist:
274
+
275
+ ```
276
+
277
+ ### transaction
278
+
279
+ For more fine-grained control of when data is commited, you might prefer to use
280
+ `transaction`. Any time during a database session you can start a transaction
281
+ block. Changes to the database inside that block are not committed without an
282
+ explicit commit command. For example, consider this code::
283
+
284
+ ```text
285
+ Audrey.connect(path, 'rw') do |db|
286
+ db['hero'] = 'Thor'
287
+
288
+ db.transaction do |tr|
289
+ db['antagonist'] = 'Loki'
290
+ end
291
+
292
+ db.each do |k, v|
293
+ puts k + ': ' + v
294
+ end
295
+ end
296
+ ```
297
+
298
+ The database session is set to automatically commit data as it is created
299
+ (because `autocommit` defaults to true). However, when we use `db.transaction`
300
+ to start a transaction block. Within that block, data is not automatically
301
+ committed. So the output for this code will look like this:
302
+
303
+ ```text
304
+ hero: Thor
305
+ ```
306
+
307
+ Inside a transaction block, you can commit by calling the transaction's `commit`
308
+ method:
309
+
310
+ ```text
311
+ Audrey.connect(path, 'rw') do |db|
312
+ db['hero'] = 'Thor'
313
+
314
+ db.transaction do |tr|
315
+ db['antagonist'] = 'Loki'
316
+ tr.commit
317
+ end
318
+
319
+ db.each do |k, v|
320
+ puts k + ': ' + v
321
+ end
322
+ end
323
+ ```
324
+
325
+ That gives us this output:
326
+
327
+ ```text
328
+ hero: Thor
329
+ antagonist: Loki
330
+ ```
331
+
332
+ Rollback transactions with the `rollback` method. For example, in this code we
333
+ use both `rollback` and `commit`:
334
+
335
+ ```text
336
+ Audrey.connect(path, 'rw') do |db|
337
+ db['hero'] = 'Thor'
338
+
339
+ db.transaction do |tr|
340
+ db['antagonist'] = 'Loki'
341
+ tr.rollback
342
+ db['ally'] = 'Captain America'
343
+ tr.commit
344
+ end
345
+
346
+ db.each do |k, v|
347
+ puts k + ': ' + v
348
+ end
349
+ end
350
+ ```
351
+
352
+ In that example, we set
353
+ `db['antagonist'] = 'Loki'`. But in the next line we roll it back. Then in the next two lines we set
354
+ `db['ally'] = 'Captain America'` and commit it. The result is that `antagonist` is never committed but `ally`
355
+ is, producing this output:
356
+
357
+ ```text
358
+ hero: Thor
359
+ ally: Captain America
360
+ ```
361
+
362
+ Transactions can be nested. For example, in this code, the outer transaction is
363
+ committed, but not the inner transaction:
364
+
365
+ ```text
366
+ Audrey.connect(path, 'rw') do |db|
367
+ db['hero'] = 'Thor'
368
+
369
+ db.transaction do |tr1|
370
+ db['antagonist'] = 'Loki'
371
+
372
+ db.transaction do |tr2|
373
+ db['ally'] = 'Captain America'
374
+ end
375
+
376
+ tr1.commit
377
+ end
378
+
379
+ db.each do |k, v|
380
+ puts k + ': ' + v
381
+ end
382
+ end
383
+ ```
384
+
385
+ That gives us this output
386
+
387
+ ```text
388
+ hero: Thor
389
+ antagonist: Loki
390
+ ```
391
+
392
+ If an inner transaction is committed, but not the outer transaction, then
393
+ nothing in the inner transaction is finally committed. So, for example, consider
394
+ this code:
395
+
396
+ ```text
397
+ Audrey.connect(path, 'rw') do |db|
398
+ db['hero'] = 'Thor'
399
+
400
+ db.transaction do |tr1|
401
+ db['antagonist'] = 'Loki'
402
+
403
+ db.transaction do |tr2|
404
+ db['ally'] = 'Captain America'
405
+ tr2.commit
406
+ end
407
+ end
408
+
409
+ db.each do |k, v|
410
+ puts k + ': ' + v
411
+ end
412
+ end
413
+ ```
414
+
415
+ In that example we commit `tr2`. But `tr2` is nested inside `tr1`, which is
416
+ never committed. Therefore everything inside `tr1` is rolled back, giving us
417
+ this output:
418
+
419
+ ```text
420
+ hero: Thor
421
+ ```
422
+
423
+ ### Exiting database connections and transactions
424
+
425
+ You might find that in some situations you don't need to continue a transaction,
426
+ or even an entire database connection, if certain conditions are met. For example,
427
+ suppose you want to add a record using web parameters, but only if the surname
428
+ is given. You might do that like this:
429
+
430
+ ```ruby
431
+ Audrey.connect(path, 'rw') do |db|
432
+ db.transaction do |tr|
433
+ person = Person.new()
434
+ person.surname = cgi['surname']
435
+
436
+ if not person.surname
437
+ tr.exit
438
+ end
439
+
440
+ person.first = cgi['first']
441
+ person.middle= cgi['middle']
442
+ puts 'commit'
443
+ tr.commit
444
+ end
445
+ end
446
+ ```
447
+
448
+ In this example, if no surname is given, the transaction stops when it hits
449
+ `tr.exit`. Nothing else in the transaction after that line is run.
450
+
451
+ You can also exit an entire database session with `db.exit`. So, using the same
452
+ business rules as above, you might code your database connection like this:
453
+
454
+ ```ruby
455
+ Audrey.connect(path, 'rw') do |db|
456
+ if not cgi['surname']
457
+ db.exit
458
+ end
459
+
460
+ person = Person.new()
461
+ person.surname = cgi['surname']
462
+ person.first = cgi['first']
463
+ person.middle= cgi['middle']
464
+ end
465
+ ```
466
+
467
+ ## Queries with Q0
468
+
469
+ Audrey provides a query system called Q0. With Q0 you can perform basic queries
470
+ on objects, searching for by class and by field value.
471
+
472
+ ### Q0 will not be the only query language
473
+
474
+ Before we go further, though, it's important to understand what Q0 is *not*: it
475
+ is not the only query language that Audrey will ever have. One of the problems
476
+ that database systems often have is that their query languages becomes more and
477
+ more convoluted as needs evolve. SQL is a good example. What started as a simple
478
+ system with English-like syntax evolved into a bizarre language with all manner
479
+ of join and function syntaxes. So, instead of assuming that we can invent a query
480
+ language that will always provide all needs, Audrey is designed to allow for
481
+ multiple query languages. As needs evolve and Q0 becomes unsuitable for advanced
482
+ needs, new query languages can be invented and added into the Audrey system.
483
+
484
+ Audrey will always have Q0. As long as they don't create problems with backward
485
+ compatibility, new features can be added to Q0.
486
+
487
+ ### Search by class
488
+
489
+ Let's start with a simple example. In the following code, we create a Q0 object
490
+ with `db.q0`. We tell the query to look for everything in the `Person` class.
491
+ Then we `each` through the results:
492
+
493
+ ```text
494
+ Audrey.connect(path, 'rw') do |db|
495
+ query = db.q0
496
+ query.fclass = Person
497
+
498
+ query.each do |person|
499
+ puts person.surname
500
+ end
501
+ end
502
+ ```
503
+
504
+ Note that to search by class we use `fclass` (for *Audrey class*), not just
505
+ `class`. There are several reasons for this. First, the `class` property is
506
+ already taken. Second, as Audrey grows and is implemented by languages besides
507
+ Ruby, it will be necessary to distinguish because a Ruby class and a Audrey
508
+ class. Audrey classes have the same names as Ruby classes, but other languages,
509
+ such as Python, use a different naming scheme for classes.
510
+
511
+ The example above is functionally identical to one of the earlier examples in
512
+ which we use the `Person` class itself to search for records:
513
+
514
+ ```text
515
+ Audrey.connect(path, 'rw') do |db|
516
+ Person.each do |person|
517
+ puts person.first
518
+ end
519
+ end
520
+ ```
521
+
522
+ ### Search by field values
523
+
524
+ Now we're going to filter by the values of fields. In this example, we set the
525
+ query to look for records in which the object's `first` field is "Mary".
526
+
527
+ ```text
528
+ Audrey.connect(path, 'rw') do |db|
529
+ query = db.q0
530
+ query.fclass = Person
531
+ query.fields['first'] = 'Mary'
532
+
533
+ query.each do |person|
534
+ puts person.surname
535
+ end
536
+ end
537
+ ```
538
+
539
+ To search for multiple possible values of a field, use an array that contains
540
+ all possible values:
541
+
542
+ ```text
543
+ query.fields['first'] = ['Mary', 'Fred']
544
+ ```
545
+
546
+ To search for records in which the field is nil or missing, use `nil`:
547
+
548
+ ```text
549
+ query.fields['first'] = nil
550
+ ```
551
+
552
+ To search for any value, as long as it is not nil, use the query's `defined`
553
+ method:
554
+
555
+ ```text
556
+ query.fields['first'] = query.defined
557
+ ```
558
+
559
+ You can mix and match these options in an array:
560
+
561
+ ```text
562
+ query.fields['first'] = ['Mary', nil]
563
+ ```
564
+
565
+ ### Count
566
+
567
+ In addition to looping through query results, you can also get just a count of
568
+ how many objects are found. Simply use the query's count method:
569
+
570
+ ```text
571
+ puts query.count()
572
+ ```
573
+
574
+ ### Other query filters
575
+
576
+ Currently, Q0 only allows you to search by fclass and field values. More filters
577
+ will be added as Audrey develops.
578
+
579
+ ## Speed
580
+
581
+ I haven't done any benchmark tests on Audrey yet. I would be very interested to
582
+ see some if anybody would like to contribute in that way.
583
+
584
+ That being said, Audrey is probably not currently very fast. Keep in mind,
585
+ though, that it's worthwhile to balance execution speed against development
586
+ speed. Audrey, in its current implementation, probably isn't particularly fast
587
+ in execution, but it allows you go from no project to working project faster
588
+ than most database systems. Consider the tradeoff.
589
+
590
+
591
+ ## The name and logo
592
+
593
+ I chose the name *Audrey* because I like it\.\.\. there's no fancy reason beyond
594
+ that. Apparently in Irish, *Audrey* once meant "vine", so I decided to use the
595
+ leaf of the
596
+ [Lonicera periclymenum](https://en.wikipedia.org/wiki/Lonicera_periclymenum),
597
+ a vine common in Ireland, for the logo. The logo was designed by
598
+ [Inventicstudios](https://www.fiverr.com/inventicstudios).
599
+
600
+
601
+ ## Author
602
+
603
+ Mike O'Sullivan
604
+ mike@idocs.com
605
+
606
+ ## History
607
+
608
+ | version | date | notes |
609
+ |---------|-------------|--------------------------|
610
+ | 0.1 | Jan 7, 2020 | Initial upload. |