relix 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +8 -0
- data/README.md +38 -1
- data/lib/relix.rb +1 -0
- data/lib/relix/index.rb +22 -2
- data/lib/relix/index_set.rb +9 -8
- data/lib/relix/indexes/multi.rb +4 -0
- data/lib/relix/indexes/ordered.rb +101 -0
- data/lib/relix/indexes/primary_key.rb +8 -3
- data/lib/relix/indexes/unique.rb +1 -1
- data/lib/relix/keyer.rb +7 -7
- data/lib/relix/query.rb +1 -1
- data/lib/relix/version.rb +1 -1
- metadata +8 -7
data/HISTORY.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
### 1.2.0
|
2
|
+
|
3
|
+
* Improved keyer inheritance, including better legacy
|
4
|
+
support. (ntalbott)
|
5
|
+
* Improved memory efficiency by not storing unnecessary
|
6
|
+
data. (myronmarston)
|
7
|
+
* Added Ordered Indexes for easy range queries. (myronmarston)
|
8
|
+
|
1
9
|
### 1.1.1
|
2
10
|
|
3
11
|
* Added keyers to the manifest file. (ntalbott)
|
data/README.md
CHANGED
@@ -143,6 +143,17 @@ When there are multiple attributes, they are specified in a hash:
|
|
143
143
|
end
|
144
144
|
|
145
145
|
|
146
|
+
### Space efficiency
|
147
|
+
|
148
|
+
Model attributes that are indexed on but that never change can be marked as immutable to prevent them being stored (since they don't have to be reindexed). The primary key is marked immutable by default, but other attributes can be as well:
|
149
|
+
|
150
|
+
relix do
|
151
|
+
unique :token, immutable_attribute: true
|
152
|
+
end
|
153
|
+
|
154
|
+
This can also provide concurrency benefits since the keys for the indexes on immutable attributes don't have to be watched for concurrent modification.
|
155
|
+
|
156
|
+
|
146
157
|
## Index Types
|
147
158
|
|
148
159
|
### PrimaryKeyIndex
|
@@ -182,6 +193,30 @@ Unique indexes ignore nil values - they will not be indexed and an error is not
|
|
182
193
|
**Supported Operators**: eq, all
|
183
194
|
**Ordering**: can be ordered on any numeric attribute (default is the to_i of the indexed value)
|
184
195
|
|
196
|
+
### OrderedIndex
|
197
|
+
|
198
|
+
Ordered indexes are specifically designed to support range queries. Like a MultiIndex, they support multiple matching
|
199
|
+
primary keys per indexed value. They are declared using #ordered in the relix block:
|
200
|
+
|
201
|
+
relix do
|
202
|
+
ordered :birthdate
|
203
|
+
end
|
204
|
+
|
205
|
+
**Supported Operators**: eq, lt, lte, gt, gte, order, limit, offset
|
206
|
+
**Ordering**: ordered ascending by the indexed value, but can be queried in
|
207
|
+
reverse order if you use `order(:desc)`.
|
208
|
+
|
209
|
+
Ordered indexes support a flexible fluent interface for specifying the query:
|
210
|
+
|
211
|
+
Person.lookup do |q|
|
212
|
+
q[:birthdate].
|
213
|
+
gte(Date.new(1990, 1, 1)).
|
214
|
+
lt(Date.new(1991, 1, 1).
|
215
|
+
order(:desc).
|
216
|
+
limit(10)
|
217
|
+
end
|
218
|
+
|
219
|
+
This query returns the primary keys of the 10 youngest people born in 1990.
|
185
220
|
|
186
221
|
## Keying
|
187
222
|
|
@@ -195,6 +230,8 @@ You can set the default keyer like so:
|
|
195
230
|
|
196
231
|
Relix.default_keyer(Relix::Keyer::Compact)
|
197
232
|
|
233
|
+
Keyers are inherited, with child classes will use their parent's keyer unless a keyer is explicitly set on the child.
|
234
|
+
|
198
235
|
|
199
236
|
### Standard
|
200
237
|
|
@@ -208,4 +245,4 @@ Keys take up space, and especially since Redis holds the keyset in memory it can
|
|
208
245
|
|
209
246
|
### Legacy
|
210
247
|
|
211
|
-
This (eventually to be deprecated and removed) strategy exactly mirrors the keying supported by Relix when first released.
|
248
|
+
This (eventually to be deprecated and removed) strategy exactly mirrors the keying supported by Relix when first released.
|
data/lib/relix.rb
CHANGED
data/lib/relix/index.rb
CHANGED
@@ -8,10 +8,13 @@ module Relix
|
|
8
8
|
@compact_kind ||= kind[0..0]
|
9
9
|
end
|
10
10
|
|
11
|
+
attr_reader :model_name
|
11
12
|
def initialize(set, base_name, accessor, options={})
|
12
13
|
@set = set
|
13
14
|
@base_name = base_name
|
15
|
+
@model_name = @set.klass.name
|
14
16
|
@accessor = [accessor].flatten.collect{|a| a.to_s}
|
17
|
+
@attribute_immutable = !!options[:immutable_attribute]
|
15
18
|
@options = options
|
16
19
|
end
|
17
20
|
|
@@ -43,7 +46,11 @@ module Relix
|
|
43
46
|
end.join(":")
|
44
47
|
end
|
45
48
|
|
46
|
-
def watch
|
49
|
+
def watch(*values)
|
50
|
+
watch_keys(*values) unless attribute_immutable?
|
51
|
+
end
|
52
|
+
|
53
|
+
def watch_keys(*values)
|
47
54
|
nil
|
48
55
|
end
|
49
56
|
|
@@ -55,6 +62,14 @@ module Relix
|
|
55
62
|
nil
|
56
63
|
end
|
57
64
|
|
65
|
+
def create_query_clause(redis)
|
66
|
+
Query::Clause.new(redis, self)
|
67
|
+
end
|
68
|
+
|
69
|
+
def attribute_immutable?
|
70
|
+
@attribute_immutable
|
71
|
+
end
|
72
|
+
|
58
73
|
module Ordering
|
59
74
|
def initialize(*args)
|
60
75
|
super
|
@@ -65,6 +80,11 @@ module Relix
|
|
65
80
|
if @order
|
66
81
|
value = object.send(@order)
|
67
82
|
end
|
83
|
+
|
84
|
+
score_for_value(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def score_for_value(value)
|
68
88
|
case value
|
69
89
|
when Numeric
|
70
90
|
value
|
@@ -96,4 +116,4 @@ module Relix
|
|
96
116
|
|
97
117
|
class UnorderableValueError < Relix::Error; end
|
98
118
|
class MissingIndexValueError < Relix::Error; end
|
99
|
-
end
|
119
|
+
end
|
data/lib/relix/index_set.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Relix
|
2
2
|
class IndexSet
|
3
3
|
attr_accessor :redis
|
4
|
+
attr_reader :klass
|
4
5
|
def initialize(klass, redis)
|
5
6
|
@klass = klass
|
6
7
|
@redis = redis
|
7
8
|
@indexes = Hash.new
|
8
|
-
@keyer = Keyer.default_for(@klass)
|
9
|
+
@keyer = Keyer.default_for(@klass) unless parent
|
9
10
|
end
|
10
11
|
|
11
12
|
def primary_key(accessor)
|
@@ -28,7 +29,7 @@ module Relix
|
|
28
29
|
if value
|
29
30
|
@keyer = value.new(@klass, options)
|
30
31
|
else
|
31
|
-
@keyer
|
32
|
+
(@keyer || parent.keyer)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -65,13 +66,13 @@ module Relix
|
|
65
66
|
current_values = @redis.hgetall(current_values_name)
|
66
67
|
|
67
68
|
ops = indexes.collect do |name,index|
|
68
|
-
((watch = index.watch) && @redis.watch(*watch))
|
69
|
-
|
70
69
|
value = index.read_normalized(object)
|
71
70
|
old_value = current_values[name]
|
72
71
|
|
72
|
+
((watch = index.watch(value, old_value)) && @redis.watch(*watch))
|
73
|
+
|
73
74
|
next if value == old_value
|
74
|
-
current_values[name] = value
|
75
|
+
current_values[name] = value unless index.attribute_immutable?
|
75
76
|
|
76
77
|
next unless index.filter(@redis, object, value)
|
77
78
|
|
@@ -83,7 +84,7 @@ module Relix
|
|
83
84
|
|
84
85
|
ops << proc do
|
85
86
|
@redis.hmset(current_values_name, *current_values.flatten)
|
86
|
-
end
|
87
|
+
end if current_values.any?
|
87
88
|
|
88
89
|
ops
|
89
90
|
end
|
@@ -105,8 +106,8 @@ module Relix
|
|
105
106
|
current_values = @redis.hgetall(current_values_name)
|
106
107
|
|
107
108
|
indexes.map do |name, index|
|
108
|
-
((watch = index.watch) && @redis.watch(*watch))
|
109
109
|
old_value = current_values[name]
|
110
|
+
((watch = index.watch(old_value)) && @redis.watch(*watch))
|
110
111
|
proc { index.deindex(@redis, pk, object, old_value) }
|
111
112
|
end.tap { |ops| ops << proc { @redis.del current_values_name } }
|
112
113
|
end
|
@@ -125,7 +126,7 @@ module Relix
|
|
125
126
|
end
|
126
127
|
|
127
128
|
def current_values_name(pk)
|
128
|
-
|
129
|
+
keyer.values(pk, @klass)
|
129
130
|
end
|
130
131
|
|
131
132
|
private
|
data/lib/relix/indexes/multi.rb
CHANGED
@@ -2,6 +2,10 @@ module Relix
|
|
2
2
|
class MultiIndex < Index
|
3
3
|
include Ordering
|
4
4
|
|
5
|
+
def watch_keys(*values)
|
6
|
+
values.compact.map { |v| key_for(v) }
|
7
|
+
end
|
8
|
+
|
5
9
|
def index(r, pk, object, value, old_value)
|
6
10
|
r.zadd(key_for(value), score(object, value), pk)
|
7
11
|
r.zrem(key_for(old_value), pk)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Relix
|
2
|
+
class OrderedIndex < Index
|
3
|
+
include Ordering
|
4
|
+
|
5
|
+
def initialize(set, base_name, accessor, options={})
|
6
|
+
super
|
7
|
+
@order = accessor
|
8
|
+
end
|
9
|
+
|
10
|
+
def sorted_set_name
|
11
|
+
name
|
12
|
+
end
|
13
|
+
|
14
|
+
def watch_keys(*values)
|
15
|
+
sorted_set_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def index(r, pk, object, value, old_value)
|
19
|
+
r.zadd(sorted_set_name, score(object, value), pk)
|
20
|
+
end
|
21
|
+
|
22
|
+
def deindex(r, pk, object, old_value)
|
23
|
+
r.zrem(sorted_set_name, pk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_query_clause(redis)
|
27
|
+
QueryClause.new(redis, self)
|
28
|
+
end
|
29
|
+
|
30
|
+
class QueryClause
|
31
|
+
def initialize(redis, index)
|
32
|
+
@redis, @index = redis, index
|
33
|
+
@lt, @gt, @limit, @offset, @order = '+inf', '-inf', nil, nil, :asc
|
34
|
+
end
|
35
|
+
|
36
|
+
def lt(value)
|
37
|
+
@lt = "(#{@index.score_for_value(value)}"
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def lte(value)
|
42
|
+
@lt = @index.score_for_value(value)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def gt(value)
|
47
|
+
@gt = "(#{@index.score_for_value(value)}"
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def gte(value)
|
52
|
+
@gt = @index.score_for_value(value)
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def eq(value)
|
57
|
+
lte(value)
|
58
|
+
gte(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def order(value)
|
62
|
+
unless [:asc, :desc].include?(value)
|
63
|
+
raise InvalidQueryOption.new("order must be :asc or :desc but was #{value.inspect}")
|
64
|
+
end
|
65
|
+
|
66
|
+
@order = value
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def limit(value)
|
71
|
+
@limit = value
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def offset(value)
|
76
|
+
@offset = value
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def zrangebyscore_limit
|
81
|
+
# zrangebyscore uses offset/count rather than start/stop like zrange
|
82
|
+
offset, stop = @index.range_from_options(@redis, offset: @offset, limit: @limit)
|
83
|
+
count = stop == -1 ? -1 : (stop - offset + 1)
|
84
|
+
[offset, count]
|
85
|
+
end
|
86
|
+
|
87
|
+
def lookup
|
88
|
+
command, score_1, score_2 = case @order
|
89
|
+
when :desc then [:zrevrangebyscore, @lt, @gt]
|
90
|
+
when :asc then [:zrangebyscore, @gt, @lt]
|
91
|
+
end
|
92
|
+
|
93
|
+
@redis.send(command, @index.sorted_set_name, score_1, score_2, limit: zrangebyscore_limit)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
register_index OrderedIndex
|
99
|
+
class InvalidQueryOption < Relix::Error; end
|
100
|
+
end
|
101
|
+
|
@@ -2,12 +2,17 @@ module Relix
|
|
2
2
|
class PrimaryKeyIndex < Index
|
3
3
|
include Ordering
|
4
4
|
|
5
|
-
def
|
5
|
+
def initialize(set, base_name, accessor, options={})
|
6
|
+
options[:immutable_attribute] = true unless options.has_key?(:immutable_attribute)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def watch_keys(*values)
|
6
11
|
name
|
7
12
|
end
|
8
13
|
|
9
14
|
def filter(r, object, value)
|
10
|
-
!r.
|
15
|
+
!r.zscore(name, value)
|
11
16
|
end
|
12
17
|
|
13
18
|
def query(r, value)
|
@@ -31,4 +36,4 @@ module Relix
|
|
31
36
|
end
|
32
37
|
end
|
33
38
|
register_index PrimaryKeyIndex
|
34
|
-
end
|
39
|
+
end
|
data/lib/relix/indexes/unique.rb
CHANGED
data/lib/relix/keyer.rb
CHANGED
@@ -18,16 +18,16 @@ module Relix
|
|
18
18
|
@prefix = klass.name
|
19
19
|
end
|
20
20
|
|
21
|
-
def values(pk)
|
22
|
-
"#{
|
21
|
+
def values(pk, klass)
|
22
|
+
"#{klass.name}:current_values:#{pk}"
|
23
23
|
end
|
24
24
|
|
25
25
|
def index(index, name)
|
26
26
|
case index
|
27
27
|
when PrimaryKeyIndex
|
28
|
-
"#{index.class.name}:#{
|
28
|
+
"#{index.class.name}:#{index.model_name}:primary_key"
|
29
29
|
else
|
30
|
-
"#{index.class.name}:#{
|
30
|
+
"#{index.class.name}:#{index.model_name}:#{name}"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -52,12 +52,12 @@ module Relix
|
|
52
52
|
@prefix = klass.name
|
53
53
|
end
|
54
54
|
|
55
|
-
def values(pk)
|
55
|
+
def values(pk, klass)
|
56
56
|
"#{@prefix}:values:#{pk}"
|
57
57
|
end
|
58
58
|
|
59
59
|
def index(index, name)
|
60
|
-
"#{
|
60
|
+
"#{index.model_name}:#{name}:#{index.class.kind}"
|
61
61
|
end
|
62
62
|
|
63
63
|
def component(name, component)
|
@@ -74,7 +74,7 @@ module Relix
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
def values(pk)
|
77
|
+
def values(pk, klass)
|
78
78
|
"#{@prefix}:v:#{pk}"
|
79
79
|
end
|
80
80
|
|
data/lib/relix/query.rb
CHANGED
@@ -8,7 +8,7 @@ module Relix
|
|
8
8
|
def [](index_name)
|
9
9
|
index = @model.indexes[index_name.to_s]
|
10
10
|
raise MissingIndexError.new("No index declared for #{index_name}") unless index
|
11
|
-
@clause =
|
11
|
+
@clause = index.create_query_clause(@model.redis)
|
12
12
|
end
|
13
13
|
|
14
14
|
def run
|
data/lib/relix/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: relix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hiredis
|
16
|
-
requirement: &
|
16
|
+
requirement: &70287488679200 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.4.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70287488679200
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: redis
|
27
|
-
requirement: &
|
27
|
+
requirement: &70287488678700 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 2.2.2
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70287488678700
|
36
36
|
description: ! 'Relix is a layer that can be added on to any model to make all the
|
37
37
|
normal types of querying you want to do: equality, less than/greater than, in set,
|
38
38
|
range, limit, etc., quick and painless. Relix depends on Redis to be awesome at
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- lib/relix/core.rb
|
51
51
|
- lib/relix/index.rb
|
52
52
|
- lib/relix/indexes/multi.rb
|
53
|
+
- lib/relix/indexes/ordered.rb
|
53
54
|
- lib/relix/indexes/primary_key.rb
|
54
55
|
- lib/relix/indexes/unique.rb
|
55
56
|
- lib/relix/index_set.rb
|
@@ -71,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
72
|
version: '0'
|
72
73
|
segments:
|
73
74
|
- 0
|
74
|
-
hash:
|
75
|
+
hash: -969086072538235743
|
75
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
77
|
none: false
|
77
78
|
requirements:
|