zermelo 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +76 -52
- data/lib/zermelo/associations/association_data.rb +4 -3
- data/lib/zermelo/associations/class_methods.rb +37 -50
- data/lib/zermelo/associations/index.rb +3 -1
- data/lib/zermelo/associations/multiple.rb +247 -0
- data/lib/zermelo/associations/range_index.rb +44 -0
- data/lib/zermelo/associations/singular.rb +193 -0
- data/lib/zermelo/associations/unique_index.rb +4 -3
- data/lib/zermelo/backend.rb +120 -0
- data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
- data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
- data/lib/zermelo/backends/stub.rb +43 -0
- data/lib/zermelo/filter.rb +194 -0
- data/lib/zermelo/filters/index_range.rb +22 -0
- data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
- data/lib/zermelo/filters/redis.rb +173 -0
- data/lib/zermelo/filters/steps/list_step.rb +48 -30
- data/lib/zermelo/filters/steps/set_step.rb +148 -89
- data/lib/zermelo/filters/steps/sort_step.rb +2 -2
- data/lib/zermelo/record.rb +53 -0
- data/lib/zermelo/records/attributes.rb +32 -0
- data/lib/zermelo/records/class_methods.rb +12 -25
- data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
- data/lib/zermelo/records/instance_methods.rb +9 -8
- data/lib/zermelo/records/key.rb +3 -1
- data/lib/zermelo/records/redis.rb +17 -0
- data/lib/zermelo/records/stub.rb +17 -0
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/index_spec.rb +70 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
- data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
- data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
- data/spec/lib/zermelo/filter_spec.rb +363 -0
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
- data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mock_logger.rb +48 -0
- metadata +31 -46
- data/lib/zermelo/associations/belongs_to.rb +0 -115
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
- data/lib/zermelo/associations/has_many.rb +0 -120
- data/lib/zermelo/associations/has_one.rb +0 -109
- data/lib/zermelo/associations/has_sorted_set.rb +0 -124
- data/lib/zermelo/backends/base.rb +0 -115
- data/lib/zermelo/filters/base.rb +0 -212
- data/lib/zermelo/filters/redis_filter.rb +0 -111
- data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
- data/lib/zermelo/records/base.rb +0 -62
- data/lib/zermelo/records/redis_record.rb +0 -27
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
- data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
- data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
- data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
- data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
- data/spec/lib/zermelo/records/key_spec.rb +0 -6
- data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
- data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
- data/spec/lib/zermelo/version_spec.rb +0 -6
- data/spec/lib/zermelo_spec.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc193e2da0a7a65f2a3643520bd99663172a3d2e
|
4
|
+
data.tar.gz: ee336568cf9e99aae87ac54bbe6ce0db00500e42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46fea7c0eee8e85846323a42ce4a83caee6761e48ad672c7ba70fa0a2ddd862b5d5aac849a8ce7d7d0b9c4c7deebec9bf90d1c918878c9ec73fe8b08a130cb9d
|
7
|
+
data.tar.gz: 0257355f3e92135da783b321db21850adb806758ba1089bf6a3837bf661b93925a66771ad760ad6796914fbeb269a3495720f604dad5977c1d103144143d5756
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## Zermelo Changelog
|
2
2
|
|
3
|
+
# 1.2.0 - 2015-06-24
|
4
|
+
|
5
|
+
* spec cleanup, apply to multiple backends if not backend-specific
|
6
|
+
* fix sorted_sets to compose with other queries
|
7
|
+
* range queries against sorted sets
|
8
|
+
* improve setting association from its inverse side; all callbacks now called properly
|
9
|
+
* renamed .delete on associations to .remove
|
10
|
+
* also support .remove_ids on multiple associations, so record need not be loaded
|
11
|
+
* stub record type
|
12
|
+
|
3
13
|
# 1.1.0 - 2015-04-09
|
4
14
|
|
5
15
|
* refactored query builder to improve composability
|
data/README.md
CHANGED
@@ -38,11 +38,11 @@ You can optionally set `Zermelo.logger` to an instance of a Ruby `Logger` class,
|
|
38
38
|
|
39
39
|
### Class ids
|
40
40
|
|
41
|
-
Include **zermelo**'s
|
41
|
+
Include **zermelo**'s `Zermelo::Records::Redis` module in the class you want to persist data from:
|
42
42
|
|
43
43
|
```ruby
|
44
44
|
class Post
|
45
|
-
include Zermelo::
|
45
|
+
include Zermelo::Records::Redis
|
46
46
|
end
|
47
47
|
```
|
48
48
|
|
@@ -67,7 +67,7 @@ A data record without any actual data isn't very useful, so let's add a few simp
|
|
67
67
|
|
68
68
|
```ruby
|
69
69
|
class Post
|
70
|
-
include Zermelo::
|
70
|
+
include Zermelo::Records::Redis
|
71
71
|
define_attributes :title => :string,
|
72
72
|
:score => :integer,
|
73
73
|
:timestamp => :timestamp,
|
@@ -96,8 +96,7 @@ which can then be verified by inspection of the object's attributes, e.g.:
|
|
96
96
|
post.attributes.inpsect # == {:id => '03c839ac-24af-432e-aa58-fd1d4bf73f24', :title => 'Introduction to Zermelo', :score => 100, :timestamp => '2000-01-01 00:00:00 UTC', :published => false}
|
97
97
|
```
|
98
98
|
|
99
|
-
Zermelo supports the following simple attribute types, and automatically
|
100
|
-
validates that the values are of the correct class, casting if possible:
|
99
|
+
Zermelo supports the following simple attribute types, and automatically validates that the values are of the correct class, casting if possible:
|
101
100
|
|
102
101
|
| Type | Ruby class | Notes |
|
103
102
|
|------------|-------------------------------|-------|
|
@@ -116,7 +115,7 @@ So if we add tags to the Post data definition:
|
|
116
115
|
|
117
116
|
```ruby
|
118
117
|
class Post
|
119
|
-
include Zermelo::
|
118
|
+
include Zermelo::Records::Redis
|
120
119
|
define_attributes :title => :string,
|
121
120
|
:score => :integer,
|
122
121
|
:timestamp => :timestamp,
|
@@ -125,7 +124,7 @@ class Post
|
|
125
124
|
end
|
126
125
|
```
|
127
126
|
|
128
|
-
and then create another
|
127
|
+
and then create another `Post` instance:
|
129
128
|
|
130
129
|
```ruby
|
131
130
|
post = Post.new(:id => 1, :tags => Set.new(['database', 'ORM']))
|
@@ -139,19 +138,18 @@ SADD post:1:attrs:tags 'database' 'ORM'
|
|
139
138
|
SADD post::attrs:ids 1
|
140
139
|
```
|
141
140
|
|
142
|
-
Zermelo supports the following complex attribute types, and automatically
|
143
|
-
validates that the values are of the correct class, casting if possible:
|
141
|
+
Zermelo supports the following complex attribute types, and automatically validates that the values are of the correct class, casting if possible:
|
144
142
|
|
145
|
-
| Type
|
146
|
-
|
147
|
-
| :list
|
148
|
-
| :set
|
149
|
-
| :hash
|
143
|
+
| Type | Ruby class | Notes |
|
144
|
+
|-------------|---------------|---------------------------------------------------------|
|
145
|
+
| :list | Enumerable | Stored as a Redis [LIST](http://redis.io/commands#list) |
|
146
|
+
| :set | Array or Set | Stored as a Redis [SET](http://redis.io/commands#set) |
|
147
|
+
| :hash | Hash | Stored as a Redis [HASH](http://redis.io/commands#hash) |
|
148
|
+
| :sorted_set | Enumerable | Stored as a Redis [ZSET](http://redis.io/commands#zset) |
|
150
149
|
|
151
|
-
Structure data members must be primitives that will cast OK to and from Redis via the
|
152
|
-
driver, thus String, Integer and Float.
|
150
|
+
Structure data members must be primitives that will cast OK to and from Redis via the driver, thus String, Integer and Float.
|
153
151
|
|
154
|
-
Redis [sorted sets](http://redis.io/commands#sorted_set) are
|
152
|
+
Redis [sorted sets](http://redis.io/commands#sorted_set) are also supported through **zermelo**'s associations (recommended due to the fact that queries can be constructed against them).
|
155
153
|
|
156
154
|
### Validations
|
157
155
|
|
@@ -161,9 +159,9 @@ So an attribute which should be present:
|
|
161
159
|
|
162
160
|
```ruby
|
163
161
|
class Post
|
164
|
-
include Zermelo::
|
165
|
-
define_attributes :title
|
166
|
-
:score
|
162
|
+
include Zermelo::Records::Redis
|
163
|
+
define_attributes :title => :string,
|
164
|
+
:score => :integer
|
167
165
|
validates :title, :presence => true
|
168
166
|
end
|
169
167
|
```
|
@@ -202,15 +200,15 @@ Another feature added by ActiveModel is the ability to detect changed data in re
|
|
202
200
|
|
203
201
|
```ruby
|
204
202
|
class Author
|
205
|
-
include Zermelo::
|
203
|
+
include Zermelo::Records::Redis
|
206
204
|
end
|
207
205
|
|
208
206
|
class Post
|
209
|
-
include Zermelo::
|
207
|
+
include Zermelo::Records::Redis
|
210
208
|
end
|
211
209
|
|
212
210
|
class Comment
|
213
|
-
include Zermelo::
|
211
|
+
include Zermelo::Records::Redis
|
214
212
|
end
|
215
213
|
|
216
214
|
Author.lock(Post, Comment) do
|
@@ -224,7 +222,7 @@ Assuming a saved `Post` instance has been created:
|
|
224
222
|
|
225
223
|
```ruby
|
226
224
|
class Post
|
227
|
-
include Zermelo::
|
225
|
+
include Zermelo::Records::Redis
|
228
226
|
define_attributes :title => :string,
|
229
227
|
:score => :integer,
|
230
228
|
:timestamp => :timestamp,
|
@@ -296,19 +294,19 @@ Instances also have attribute accessors and the various methods included from th
|
|
296
294
|
|Name | Type | Redis data structure | Notes |
|
297
295
|
|---------------------------|---------------------------|----------------------|-------|
|
298
296
|
| `has_many` | one-to-many | [SET](http://redis.io/commands#set) | |
|
299
|
-
| `has_sorted_set` | one-to-many | [ZSET](http://redis.io/commands#sorted_set) | |
|
297
|
+
| `has_sorted_set` | one-to-many | [ZSET](http://redis.io/commands#sorted_set) | Arguments: `:key` (required), `:order` (optional, `:asc` or `:desc`) |
|
300
298
|
| `has_one` | one-to-one | [HASH](http://redis.io/commands#hash) | |
|
301
299
|
| `belongs_to` | many-to-one or one-to-one | [HASH](http://redis.io/commands#hash) or [STRING](http://redis.io/commands#string) | Inverse of any of the above three |
|
302
300
|
| `has_and_belongs_to_many` | many-to-many | 2 [SET](http://redis.io/commands#set)s | Mirrored by an inverse HaBtM association on the other side. |
|
303
301
|
|
304
302
|
```ruby
|
305
303
|
class Post
|
306
|
-
include Zermelo::
|
304
|
+
include Zermelo::Records::Redis
|
307
305
|
has_many :comments, :class_name => 'Comment', :inverse_of => :post
|
308
306
|
end
|
309
307
|
|
310
308
|
class Comment
|
311
|
-
include Zermelo::
|
309
|
+
include Zermelo::Records::Redis
|
312
310
|
belongs_to :post, :class_name => 'Post', :inverse_of => :comments
|
313
311
|
end
|
314
312
|
```
|
@@ -319,24 +317,35 @@ Records are added and removed from their parent one-to-many or many-to-many asso
|
|
319
317
|
|
320
318
|
```ruby
|
321
319
|
post.comments.add(comment) # or post.comments << comment
|
320
|
+
post.comments.remove(comment)
|
322
321
|
```
|
323
322
|
|
324
|
-
Associations' `.add` can also take more than one argument:
|
323
|
+
Associations' `.add`/`.remove` can also take more than one argument:
|
325
324
|
|
326
325
|
```ruby
|
327
326
|
post.comments.add(comment1, comment2, comment3)
|
327
|
+
post.comments.remove(comment1, comment2, comment3)
|
328
|
+
```
|
329
|
+
|
330
|
+
If you only have ids available, you don't need to `.load` the respective objects, you can instead use `.add_ids`/`.remove_ids`:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
post.comments.add_ids("comment_id")
|
334
|
+
post.comments.remove_ids("comment_id")
|
335
|
+
post.comments.add_ids("comment1_id", "comment2_id", "comment3_id")
|
336
|
+
post.comments.remove_ids("comment1_id", "comment2_id", "comment3_id")
|
328
337
|
```
|
329
338
|
|
330
339
|
`has_one` associations are simply set with an `=` method on the association:
|
331
340
|
|
332
341
|
```ruby
|
333
342
|
class User
|
334
|
-
include Zermelo::
|
343
|
+
include Zermelo::Records::Redis
|
335
344
|
has_one :preferences, :class_name => 'Preferences', :inverse_of => :user
|
336
345
|
end
|
337
346
|
|
338
347
|
class Preferences
|
339
|
-
include Zermelo::
|
348
|
+
include Zermelo::Records::Redis
|
340
349
|
belongs_to :user, :class_name => 'User', :inverse_of => :preferences
|
341
350
|
end
|
342
351
|
|
@@ -348,10 +357,16 @@ prefs.save
|
|
348
357
|
user.preferences = prefs
|
349
358
|
```
|
350
359
|
|
360
|
+
and cleared by assigning the association to nil:
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
user.preferences = nil
|
364
|
+
```
|
365
|
+
|
351
366
|
The class methods defined above can be applied to associations references as well, so the resulting data will be filtered by the data relationships applying in the association, e.g.
|
352
367
|
|
353
368
|
```ruby
|
354
|
-
post
|
369
|
+
post = Post.new(:id => 'a')
|
355
370
|
post.save
|
356
371
|
comment1 = Comment.new(:id => '1')
|
357
372
|
comment1.save
|
@@ -364,12 +379,12 @@ post.comments << comment1
|
|
364
379
|
p post.comments.ids # == [1]
|
365
380
|
```
|
366
381
|
|
367
|
-
|
382
|
+
`.associated_ids_for` is somewhat of a special case; it uses the simplest queries possible to get the ids of the associated records of a set of records, e.g. for the data directly above:
|
368
383
|
|
369
384
|
```ruby
|
370
385
|
Post.associated_ids_for(:comments) # => {'a' => ['1']}
|
371
386
|
|
372
|
-
post_b
|
387
|
+
post_b = Post.new(:id => 'b')
|
373
388
|
post_b.save
|
374
389
|
post_b.comments << comment2
|
375
390
|
comment3 = Comment.new(:id => '3')
|
@@ -377,7 +392,6 @@ comment3.save
|
|
377
392
|
post.comments << comment3
|
378
393
|
|
379
394
|
Post.associated_ids_for(:comments) # => {'a' => ['1', '3'], 'b' => ['2']}
|
380
|
-
Post.intersect(:id => 'a').associated_ids_for(:comments) # => {'a' => ['1', '3']}
|
381
395
|
```
|
382
396
|
|
383
397
|
For `belongs to` associations, you may pass an extra option to `associated_ids_for`, `:inversed => true`, and you'll get the data back as if it were applied from the inverse side; however the data will only cover that used as the query root. Again, assuming the data from the last two code blocks, e.g.
|
@@ -385,9 +399,6 @@ For `belongs to` associations, you may pass an extra option to `associated_ids_f
|
|
385
399
|
```ruby
|
386
400
|
Comment.associated_ids_for(:post) # => {'1' => 'a', '2' => 'b', '3' => 'a'}
|
387
401
|
Comment.associated_ids_for(:post, :inversed => true) # => {'a' => ['1', '3'], 'b' => ['2']}
|
388
|
-
|
389
|
-
Comment.intersect(:id => ['1', '2']).associated_ids_for(:post) # => {'1' => 'a', '2' => 'b'}
|
390
|
-
Comment.intersect(:id => ['1', '2']).associated_ids_for(:post, :inversed => true) # => {'a' => ['1'], 'b' => ['2']}
|
391
402
|
```
|
392
403
|
|
393
404
|
### Class data indexing
|
@@ -398,7 +409,7 @@ Using the code from the instance attributes section, and adding indexing:
|
|
398
409
|
|
399
410
|
```ruby
|
400
411
|
class Post
|
401
|
-
include Zermelo::
|
412
|
+
include Zermelo::Records::Redis
|
402
413
|
define_attributes :title => :string,
|
403
414
|
:score => :integer,
|
404
415
|
:timestamp => :timestamp,
|
@@ -436,15 +447,12 @@ SADD post::indices:by_published:boolean:false 03c839ac-24af-432e-aa58-fd1d4bf73f
|
|
436
447
|
|
437
448
|
| Name | Input | Output | Arguments | Options |
|
438
449
|
|-----------------|-----------------------|--------------|---------------------------------------|------------------------------------------|
|
439
|
-
| intersect | `set`
|
440
|
-
| union | `set`
|
441
|
-
| diff | `set`
|
442
|
-
| intersect_range | `sorted_set` | `sorted_set` | start (`Integer`), finish (`Integer`) | :desc (`Boolean`), :by_score (`Boolean`) |
|
443
|
-
| union_range | `sorted_set` | `sorted_set` | start (`Integer`), finish (`Integer`) | :desc (`Boolean`), :by_score (`Boolean`) |
|
444
|
-
| diff_range | `sorted_set` | `sorted_set` | start (`Integer`), finish (`Integer`) | :desc (`Boolean`), :by_score (`Boolean`) |
|
450
|
+
| intersect | `set` / `sorted_set` | (as input) | Query hash | |
|
451
|
+
| union | `set` / `sorted_set` | (as input) | Query hash | |
|
452
|
+
| diff | `set` / `sorted_set` | (as input) | Query hash | |
|
445
453
|
| sort | `set` or `sorted_set` | `list` | keys (Symbol or Array of Symbols) | :limit (`Integer`), :offset (`Integer`) |
|
446
|
-
| offset | `list`
|
447
|
-
|
|
454
|
+
| offset | `list` / `sorted_set` | `list` | amount (`Integer`) | :limit (`Integer`) |
|
455
|
+
| page | `list` / `sorted_set` | `list` | page_number (`Integer`) | :per_page (`Integer`) |
|
448
456
|
|
449
457
|
These queries can be applied against all instances of a class, or against associations belonging to an instance, e.g.
|
450
458
|
|
@@ -481,21 +489,37 @@ DEL comment::tmp:fe8dd59e4a1197f62d19c8aa942c4ff9
|
|
481
489
|
|
482
490
|
(where the name of the temporary Redis `SET` will of course change every time)
|
483
491
|
|
484
|
-
|
492
|
+
---
|
493
|
+
|
494
|
+
`has_sorted_set` queries can take exact values, or a range bounded in no, one or both directions. (Regular Ruby `Range` objects can't be used as they don't easily support timestamps, so there's a `Zermelo::Filters::IndexRange` class which can be used as a query value instead.)
|
485
495
|
|
486
|
-
|
487
|
-
|
496
|
+
```ruby
|
497
|
+
class Comment
|
498
|
+
include Zermelo::Records::Redis
|
499
|
+
define_attributes :created_at => :timestamp
|
500
|
+
end
|
488
501
|
|
489
|
-
|
502
|
+
t = Time.now
|
503
|
+
|
504
|
+
comment1 = Comment.new(:id => '1', :created_at => t - 120)
|
505
|
+
comment1.save
|
506
|
+
comment2 = Comment.new(:id => '2', :created_at => t - 60)
|
507
|
+
comment2.save
|
508
|
+
|
509
|
+
range = Zermelo::Filters::IndexRange.new(t - 90, t, :by_score => true)
|
510
|
+
Comment.ids # ['1', '2']
|
511
|
+
Comment.intersect(:created_at => range).ids # ['2']
|
512
|
+
|
513
|
+
```
|
490
514
|
|
491
515
|
### Future
|
492
516
|
|
493
517
|
Some possible changes:
|
494
518
|
|
495
|
-
* pluggable key naming strategies
|
496
519
|
* pluggable id generation strategies
|
520
|
+
* pluggable key naming strategies
|
497
521
|
* instrumentation for benchmarking etc.
|
498
|
-
* multiple data backends; there's currently an experimental InfluxDB backend
|
522
|
+
* multiple data backends; there's currently an experimental InfluxDB 0.8 backend (waiting for the Ruby driver to update for 0.9 support).
|
499
523
|
|
500
524
|
## License
|
501
525
|
|
@@ -2,11 +2,12 @@ module Zermelo
|
|
2
2
|
module Associations
|
3
3
|
class AssociationData
|
4
4
|
attr_writer :data_klass_name, :related_klass_names
|
5
|
-
attr_accessor :name, :type_klass, :inverse, :sort_key,
|
5
|
+
attr_accessor :name, :type_klass, :data_type, :inverse, :sort_key,
|
6
|
+
:sort_order, :callbacks
|
6
7
|
|
7
8
|
def initialize(opts = {})
|
8
|
-
[:name, :type_klass, :
|
9
|
-
:related_klass_names].each do |a|
|
9
|
+
[:name, :type_klass, :data_type, :inverse, :sort_key, :sort_order,
|
10
|
+
:callbacks, :data_klass_name, :related_klass_names].each do |a|
|
10
11
|
|
11
12
|
send("#{a}=".to_sym, opts[a])
|
12
13
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
require 'zermelo/associations/association_data'
|
2
2
|
require 'zermelo/associations/index_data'
|
3
3
|
|
4
|
-
require 'zermelo/associations/
|
5
|
-
require 'zermelo/associations/
|
6
|
-
require 'zermelo/associations/has_many'
|
7
|
-
require 'zermelo/associations/has_one'
|
8
|
-
require 'zermelo/associations/has_sorted_set'
|
4
|
+
require 'zermelo/associations/singular'
|
5
|
+
require 'zermelo/associations/multiple'
|
9
6
|
require 'zermelo/associations/index'
|
7
|
+
require 'zermelo/associations/range_index'
|
10
8
|
require 'zermelo/associations/unique_index'
|
11
9
|
|
12
10
|
# NB: this module gets mixed in to Zermelo::Record as class methods
|
@@ -30,6 +28,14 @@ module Zermelo
|
|
30
28
|
nil
|
31
29
|
end
|
32
30
|
|
31
|
+
def range_index_by(*args)
|
32
|
+
att_types = attribute_types
|
33
|
+
args.each do |arg|
|
34
|
+
index(::Zermelo::Associations::RangeIndex, arg, :type => att_types[arg])
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
33
39
|
def unique_index_by(*args)
|
34
40
|
att_types = attribute_types
|
35
41
|
args.each do |arg|
|
@@ -39,31 +45,33 @@ module Zermelo
|
|
39
45
|
end
|
40
46
|
|
41
47
|
def has_many(name, args = {})
|
42
|
-
associate(::Zermelo::Associations::
|
48
|
+
associate(::Zermelo::Associations::Multiple, :has_many, name, args)
|
43
49
|
nil
|
44
50
|
end
|
45
51
|
|
46
|
-
def
|
47
|
-
associate(::Zermelo::Associations::
|
52
|
+
def has_sorted_set(name, args = {})
|
53
|
+
associate(::Zermelo::Associations::Multiple, :has_sorted_set, name, args)
|
48
54
|
nil
|
49
55
|
end
|
50
56
|
|
51
|
-
def
|
52
|
-
associate(::Zermelo::Associations::
|
57
|
+
def has_and_belongs_to_many(name, args = {})
|
58
|
+
associate(::Zermelo::Associations::Multiple, :has_and_belongs_to_many, name, args)
|
53
59
|
nil
|
54
60
|
end
|
55
61
|
|
56
|
-
def
|
57
|
-
associate(::Zermelo::Associations::
|
62
|
+
def has_one(name, args = {})
|
63
|
+
associate(::Zermelo::Associations::Singular, :has_one, name, args)
|
58
64
|
nil
|
59
65
|
end
|
60
66
|
|
61
67
|
def belongs_to(name, args = {})
|
62
|
-
associate(::Zermelo::Associations::
|
68
|
+
associate(::Zermelo::Associations::Singular, :belongs_to, name, args)
|
63
69
|
nil
|
64
70
|
end
|
65
71
|
# end used by client classes
|
66
72
|
|
73
|
+
private
|
74
|
+
|
67
75
|
# used internally by other parts of Zermelo to implement the above
|
68
76
|
# configuration
|
69
77
|
|
@@ -77,15 +85,12 @@ module Zermelo
|
|
77
85
|
@association_data.values.each do |data|
|
78
86
|
klass = data.data_klass
|
79
87
|
next if visited.include?(klass)
|
80
|
-
visited |= klass.associated_classes
|
88
|
+
visited |= klass.send(:associated_classes, visited, false)
|
81
89
|
end
|
82
90
|
end
|
83
91
|
visited
|
84
92
|
end
|
85
93
|
|
86
|
-
# TODO for each association: check whether it has changed
|
87
|
-
# would need an instance-level hash with association name as key,
|
88
|
-
# boolean 'changed' value
|
89
94
|
def with_associations(record)
|
90
95
|
@lock.synchronize do
|
91
96
|
@association_data ||= {}
|
@@ -112,14 +117,6 @@ module Zermelo
|
|
112
117
|
end
|
113
118
|
# end used internally within Zermelo
|
114
119
|
|
115
|
-
# # TODO can remove need for some of the inverse mapping
|
116
|
-
# # was inverse_of(source, klass)
|
117
|
-
# with_association_data do |d|
|
118
|
-
# d.detect {|name, data| data.klass == klass && data.inverse == source}
|
119
|
-
# end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
120
|
def add_index_data(klass, name, args = {})
|
124
121
|
return if name.nil?
|
125
122
|
|
@@ -151,12 +148,7 @@ module Zermelo
|
|
151
148
|
instance_eval idx, __FILE__, __LINE__
|
152
149
|
end
|
153
150
|
|
154
|
-
def add_association_data(klass, name, args = {})
|
155
|
-
|
156
|
-
# TODO have inverse be a reference (or copy?) of the association data
|
157
|
-
# record for that inverse association; would need to defer lookup until
|
158
|
-
# all data in place for all assocs, so might be best if looked up and
|
159
|
-
# cached on first use
|
151
|
+
def add_association_data(klass, type, name, args = {})
|
160
152
|
inverse = if args[:inverse_of].nil? || args[:inverse_of].to_s.empty?
|
161
153
|
nil
|
162
154
|
else
|
@@ -164,13 +156,10 @@ module Zermelo
|
|
164
156
|
end
|
165
157
|
|
166
158
|
callbacks = case klass.name
|
167
|
-
when ::Zermelo::Associations::
|
168
|
-
|
169
|
-
|
170
|
-
[:
|
171
|
-
when ::Zermelo::Associations::HasOne.name,
|
172
|
-
::Zermelo::Associations::BelongsTo.name
|
173
|
-
[:before_set, :after_set, :before_clear, :after_clear]
|
159
|
+
when ::Zermelo::Associations::Multiple.name
|
160
|
+
[:before_add, :after_add, :before_remove, :after_remove, :before_read, :after_read]
|
161
|
+
when ::Zermelo::Associations::Singular.name
|
162
|
+
[:before_set, :after_set, :before_clear, :after_clear, :before_read, :after_read]
|
174
163
|
else
|
175
164
|
[]
|
176
165
|
end
|
@@ -178,6 +167,7 @@ module Zermelo
|
|
178
167
|
data = Zermelo::Associations::AssociationData.new(
|
179
168
|
:name => name,
|
180
169
|
:data_klass_name => args[:class_name],
|
170
|
+
:data_type => type,
|
181
171
|
:type_klass => klass,
|
182
172
|
:inverse => inverse,
|
183
173
|
:related_klass_names => args[:related_class_names],
|
@@ -186,8 +176,10 @@ module Zermelo
|
|
186
176
|
}
|
187
177
|
)
|
188
178
|
|
189
|
-
if
|
190
|
-
data.sort_key
|
179
|
+
if :has_sorted_set.eql?(type)
|
180
|
+
data.sort_key = args[:key]
|
181
|
+
data.sort_order =
|
182
|
+
!args[:order].nil? && :desc.eql?(args[:order].to_sym) ? :desc : :asc
|
191
183
|
end
|
192
184
|
|
193
185
|
@lock.synchronize do
|
@@ -196,25 +188,20 @@ module Zermelo
|
|
196
188
|
end
|
197
189
|
end
|
198
190
|
|
199
|
-
def associate(klass, name, args = {})
|
191
|
+
def associate(klass, type, name, args = {})
|
200
192
|
return if name.nil?
|
201
193
|
|
202
|
-
add_association_data(klass, name, args)
|
194
|
+
add_association_data(klass, type, name, args)
|
203
195
|
|
204
196
|
assoc = case klass.name
|
205
|
-
when ::Zermelo::Associations::
|
206
|
-
::Zermelo::Associations::HasSortedSet.name,
|
207
|
-
::Zermelo::Associations::HasAndBelongsToMany.name
|
208
|
-
|
197
|
+
when ::Zermelo::Associations::Multiple.name
|
209
198
|
%Q{
|
210
199
|
def #{name}
|
211
200
|
#{name}_proxy
|
212
201
|
end
|
213
202
|
}
|
214
203
|
|
215
|
-
when ::Zermelo::Associations::
|
216
|
-
::Zermelo::Associations::BelongsTo.name
|
217
|
-
|
204
|
+
when ::Zermelo::Associations::Singular.name
|
218
205
|
%Q{
|
219
206
|
def #{name}
|
220
207
|
#{name}_proxy.value
|
@@ -232,7 +219,7 @@ module Zermelo
|
|
232
219
|
def #{name}_proxy
|
233
220
|
raise "Associations cannot be invoked for records without an id" if self.id.nil?
|
234
221
|
|
235
|
-
@#{name}_proxy ||= #{klass.name}.new(self, '#{name}')
|
222
|
+
@#{name}_proxy ||= #{klass.name}.new(:#{type}, self.class, self.id, '#{name}')
|
236
223
|
end
|
237
224
|
private :#{name}_proxy
|
238
225
|
}
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# NB index instances are all internal to zermelo, not user-accessible
|
2
2
|
|
3
|
+
require 'zermelo/records/key'
|
4
|
+
|
3
5
|
module Zermelo
|
4
6
|
module Associations
|
5
7
|
class Index
|
@@ -29,7 +31,7 @@ module Zermelo
|
|
29
31
|
|
30
32
|
def move_id(id, value_from, indexer_to, value_to)
|
31
33
|
return unless indexer = key(value_from)
|
32
|
-
@backend.move(indexer, id, indexer_to.key(value_to))
|
34
|
+
@backend.move(indexer, id, indexer_to.key(value_to), id)
|
33
35
|
end
|
34
36
|
|
35
37
|
def key(value)
|