zermelo 1.3.0 → 1.4.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 +6 -0
- data/lib/zermelo/associations/class_methods.rb +1 -1
- data/lib/zermelo/associations/multiple.rb +17 -12
- data/lib/zermelo/backends/redis.rb +73 -29
- data/lib/zermelo/filter.rb +2 -2
- data/lib/zermelo/filters/steps/list_step.rb +3 -3
- data/lib/zermelo/filters/steps/set_step.rb +58 -11
- data/lib/zermelo/filters/steps/sort_step.rb +1 -1
- data/lib/zermelo/record.rb +2 -6
- data/lib/zermelo/records/class_methods.rb +53 -21
- data/lib/zermelo/records/influxdb.rb +1 -0
- data/lib/zermelo/records/instance_methods.rb +18 -2
- data/lib/zermelo/records/redis.rb +13 -1
- data/lib/zermelo/records/type_validator.rb +2 -2
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/index_spec.rb +1 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +12 -17
- data/spec/lib/zermelo/associations/range_index_spec.rb +2 -2
- data/spec/lib/zermelo/associations/singular_spec.rb +2 -2
- data/spec/lib/zermelo/associations/unique_index_spec.rb +1 -1
- data/spec/lib/zermelo/filter_spec.rb +1 -1
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +2 -2
- data/spec/lib/zermelo/records/instance_methods_spec.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41344d467203608ea5d05ba4e7e50ba3d0ba08c5
|
4
|
+
data.tar.gz: 62498b43b9556a7de290d83fc118ca0aa8ed6c48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 475b586c7cca398659befa76233ff27eda95ead07db02b7ac56c3fdd8fbb4ef6ea724708da35ca9d3e471fa4f54023ea18129f145ec141053d6ea7bf601ee010
|
7
|
+
data.tar.gz: d3b7c8da76210b3d6f0c350bbc3601ec4d3f78c336864a1d885e5a9ac1682db0bd8575a16d68641869eb791a3799ee5e5ed92fdaeca7d0c936ccdc8df23c057c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## Zermelo Changelog
|
2
2
|
|
3
|
+
# 1.4.0 - 2015-10-07
|
4
|
+
|
5
|
+
* store sorted_set ids in zset, makes some queries combine properly that
|
6
|
+
previously resulted in errors
|
7
|
+
* bugfix transactions, weren't applied properly
|
8
|
+
|
3
9
|
# 1.3.0 - 2015-08-15
|
4
10
|
|
5
11
|
* handle some Zermelo objects as query values, less data back-and-forth
|
@@ -82,7 +82,7 @@ module Zermelo
|
|
82
82
|
def add_ids(*record_ids)
|
83
83
|
raise 'No record ids to add' if record_ids.empty?
|
84
84
|
@parent_klass.lock(*@lock_klasses) do
|
85
|
-
|
85
|
+
@associated_class.find_by_ids!(*record_ids) # ensure they exist
|
86
86
|
_add_ids({:callbacks => true}, *record_ids)
|
87
87
|
end
|
88
88
|
end
|
@@ -155,20 +155,23 @@ module Zermelo
|
|
155
155
|
when :has_many
|
156
156
|
# inverse is belongs_to
|
157
157
|
record_ids.each do |record_id|
|
158
|
-
|
159
|
-
|
158
|
+
_inverse_copy = _inverse.clone
|
159
|
+
_inverse_copy.id = record_id
|
160
|
+
@backend.add(_inverse_copy, "#{@inverse}_id" => @parent_id)
|
160
161
|
end
|
161
162
|
when :has_sorted_set
|
162
163
|
# inverse is belongs_to
|
163
|
-
record_ids.each do |
|
164
|
-
|
165
|
-
|
164
|
+
record_ids.each do |score_and_record_id|
|
165
|
+
_inverse_copy = _inverse.clone
|
166
|
+
_inverse_copy.id = score_and_record_id.last
|
167
|
+
@backend.add(_inverse_copy, "#{@inverse}_id" => @parent_id)
|
166
168
|
end
|
167
169
|
when :has_and_belongs_to_many
|
168
170
|
# inverse is has_and_belongs_to_many
|
169
171
|
record_ids.each do |record_id|
|
170
|
-
|
171
|
-
|
172
|
+
_inverse_copy = _inverse.clone
|
173
|
+
_inverse_copy.id = record_id
|
174
|
+
@backend.add(_inverse_copy, @parent_id)
|
172
175
|
end
|
173
176
|
end
|
174
177
|
|
@@ -194,14 +197,16 @@ module Zermelo
|
|
194
197
|
when :has_many, :has_sorted_set
|
195
198
|
# inverse is belongs_to
|
196
199
|
record_ids.each do |record_id|
|
197
|
-
|
198
|
-
|
200
|
+
_inverse_copy = _inverse.clone
|
201
|
+
_inverse_copy.id = record_id
|
202
|
+
@backend.delete(_inverse_copy, "#{@inverse}_id")
|
199
203
|
end
|
200
204
|
when :has_and_belongs_to_many
|
201
205
|
# inverse is has_and_belongs_to_many
|
202
206
|
record_ids.each do |record_id|
|
203
|
-
|
204
|
-
|
207
|
+
_inverse_copy = _inverse.clone
|
208
|
+
_inverse_copy.id = record_id
|
209
|
+
@backend.delete(_inverse_copy, @parent_id)
|
205
210
|
end
|
206
211
|
end
|
207
212
|
|
@@ -12,6 +12,11 @@ module Zermelo
|
|
12
12
|
|
13
13
|
include Zermelo::Backend
|
14
14
|
|
15
|
+
def initialize
|
16
|
+
@transaction_redis = nil
|
17
|
+
@changes = nil
|
18
|
+
end
|
19
|
+
|
15
20
|
# def default_sorted_set_key
|
16
21
|
# :timestamp
|
17
22
|
# end
|
@@ -153,9 +158,9 @@ module Zermelo
|
|
153
158
|
end
|
154
159
|
end
|
155
160
|
|
156
|
-
|
161
|
+
def index_lookup(att, associated_class, type, idx_class, value,
|
162
|
+
attr_type, temp_keys)
|
157
163
|
|
158
|
-
def index_lookup(att, associated_class, idx_class, value, attr_type, temp_keys)
|
159
164
|
if (idx_class == Zermelo::Associations::RangeIndex) && !value.is_a?(Zermelo::Filters::IndexRange)
|
160
165
|
raise "Range index must be passed a range"
|
161
166
|
end
|
@@ -164,7 +169,7 @@ module Zermelo
|
|
164
169
|
when Regexp
|
165
170
|
raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
|
166
171
|
|
167
|
-
idx_key = associated_class.send(:temp_key,
|
172
|
+
idx_key = associated_class.send(:temp_key, type)
|
168
173
|
temp_keys << idx_key
|
169
174
|
idx_result = key_to_redis_key(idx_key)
|
170
175
|
|
@@ -182,7 +187,15 @@ module Zermelo
|
|
182
187
|
(starts_with_string_re === k) &&
|
183
188
|
(value === unescape_key_name(k.sub(starts_with_string_re, '')))
|
184
189
|
})
|
185
|
-
|
190
|
+
|
191
|
+
unless matching_ids.empty?
|
192
|
+
case type
|
193
|
+
when :set
|
194
|
+
Zermelo.redis.sadd(idx_result, matching_ids)
|
195
|
+
when :sorted_set
|
196
|
+
Zermelo.redis.zadd(idx_result, matching_ids.map {|m| [1, m]})
|
197
|
+
end
|
198
|
+
end
|
186
199
|
when 'Zermelo::Associations::Index'
|
187
200
|
key_root = key_to_redis_key(Zermelo::Records::Key.new(
|
188
201
|
:klass => associated_class,
|
@@ -209,7 +222,14 @@ module Zermelo
|
|
209
222
|
value === $1
|
210
223
|
end
|
211
224
|
|
212
|
-
|
225
|
+
unless matching_sets.empty?
|
226
|
+
case type
|
227
|
+
when :set
|
228
|
+
Zermelo.redis.sunionstore(idx_result, matching_sets)
|
229
|
+
when :sorted_set
|
230
|
+
Zermelo.redis.zunionstore(idx_result, matching_sets)
|
231
|
+
end
|
232
|
+
end
|
213
233
|
end
|
214
234
|
idx_key
|
215
235
|
else
|
@@ -217,30 +237,20 @@ module Zermelo
|
|
217
237
|
|
218
238
|
case index
|
219
239
|
when Zermelo::Associations::RangeIndex
|
220
|
-
|
221
|
-
range = if value.by_score
|
222
|
-
range_start = value.start.nil? ? '-inf' : safe_value(attr_type, value.start)
|
223
|
-
range_finish = value.finish.nil? ? '+inf' : safe_value(attr_type, value.finish)
|
224
|
-
Zermelo.redis.zrangebyscore(r_index_key, range_start, range_finish)
|
225
|
-
else
|
226
|
-
range_start = value.start || 0
|
227
|
-
range_finish = value.finish || -1
|
228
|
-
Zermelo.redis.zrange(r_index_key, range_start, range_finish)
|
229
|
-
end
|
230
|
-
|
231
|
-
# TODO another way for index_lookup to indicate 'empty result', rather
|
232
|
-
# than creating & returning an empty key
|
233
|
-
idx_key = associated_class.send(:temp_key, :set)
|
234
|
-
temp_keys << idx_key
|
235
|
-
Zermelo.redis.sadd(key_to_redis_key(idx_key), range) unless range.empty?
|
236
|
-
idx_key
|
240
|
+
range_lookup(index.key, value, type, attr_type, associated_class, temp_keys)
|
237
241
|
when Zermelo::Associations::UniqueIndex
|
238
|
-
idx_key = associated_class.send(:temp_key,
|
242
|
+
idx_key = associated_class.send(:temp_key, type)
|
239
243
|
temp_keys << idx_key
|
240
244
|
|
241
|
-
Zermelo.redis.
|
242
|
-
|
243
|
-
|
245
|
+
val = Zermelo.redis.hget(key_to_redis_key(index.key),
|
246
|
+
index_keys(attr_type, value).join(':'))
|
247
|
+
|
248
|
+
case type
|
249
|
+
when :set
|
250
|
+
Zermelo.redis.sadd(key_to_redis_key(idx_key), val)
|
251
|
+
when :sorted_set
|
252
|
+
Zermelo.redis.zadd(key_to_redis_key(idx_key), [1, val])
|
253
|
+
end
|
244
254
|
idx_key
|
245
255
|
when Zermelo::Associations::Index
|
246
256
|
index.key(value)
|
@@ -248,15 +258,49 @@ module Zermelo
|
|
248
258
|
end
|
249
259
|
end
|
250
260
|
|
261
|
+
def range_lookup(key, range, type, attr_type, associated_class, temp_keys)
|
262
|
+
r_key = key_to_redis_key(key)
|
263
|
+
opts = case type
|
264
|
+
when :set
|
265
|
+
{}
|
266
|
+
when :sorted_set
|
267
|
+
{:with_scores => true}
|
268
|
+
end
|
269
|
+
result = if range.by_score
|
270
|
+
range_start = range.start.nil? ? '-inf' : safe_value(attr_type, range.start)
|
271
|
+
range_finish = range.finish.nil? ? '+inf' : safe_value(attr_type, range.finish)
|
272
|
+
Zermelo.redis.zrangebyscore(r_key, range_start, range_finish, opts)
|
273
|
+
else
|
274
|
+
range_start = range.start || 0
|
275
|
+
range_finish = range.finish || -1
|
276
|
+
Zermelo.redis.zrange(r_key, range_start, range_finish, opts)
|
277
|
+
end
|
278
|
+
|
279
|
+
# TODO another way for index_lookup to indicate 'empty result', rather
|
280
|
+
# than creating & returning an empty key
|
281
|
+
ret_key = associated_class.send(:temp_key, key.type)
|
282
|
+
temp_keys << ret_key
|
283
|
+
unless result.empty?
|
284
|
+
r_key = key_to_redis_key(ret_key)
|
285
|
+
case type
|
286
|
+
when :set
|
287
|
+
Zermelo.redis.sadd(r_key, result)
|
288
|
+
when :sorted_set
|
289
|
+
Zermelo.redis.zadd(r_key, result.map {|r| [r.last, r.first]})
|
290
|
+
end
|
291
|
+
end
|
292
|
+
ret_key
|
293
|
+
end
|
294
|
+
|
251
295
|
private
|
252
296
|
|
253
297
|
def change(op, key, value = nil, key_to = nil, value_to = nil)
|
254
298
|
ch = [op, key, value, key_to, value_to]
|
255
|
-
if @
|
256
|
-
@changes << ch
|
257
|
-
else
|
299
|
+
if @transaction_redis.nil?
|
258
300
|
apply_changes([ch])
|
301
|
+
return
|
259
302
|
end
|
303
|
+
@changes << ch
|
260
304
|
end
|
261
305
|
|
262
306
|
def apply_changes(changes)
|
data/lib/zermelo/filter.rb
CHANGED
@@ -169,8 +169,8 @@ module Zermelo
|
|
169
169
|
end
|
170
170
|
|
171
171
|
def associations_for(name)
|
172
|
-
data_type
|
173
|
-
|
172
|
+
data_type = @associated_class.send(:with_association_data, name.to_sym) do |data|
|
173
|
+
data.data_type
|
174
174
|
end
|
175
175
|
|
176
176
|
lock {
|
@@ -25,8 +25,8 @@ module Zermelo
|
|
25
25
|
when Zermelo::Backends::Redis
|
26
26
|
|
27
27
|
source = opts[:source]
|
28
|
-
idx_attrs = opts[:index_attrs]
|
29
|
-
attr_types = opts[:attr_types]
|
28
|
+
# idx_attrs = opts[:index_attrs]
|
29
|
+
# attr_types = opts[:attr_types]
|
30
30
|
temp_keys = opts[:temp_keys]
|
31
31
|
|
32
32
|
# TODO apply these transformations via a subset?
|
@@ -46,7 +46,7 @@ module Zermelo
|
|
46
46
|
r_source, o, (o + l), :with_scores => true
|
47
47
|
)
|
48
48
|
|
49
|
-
Zermelo.redis.zadd(r_limited, lim.collect {|
|
49
|
+
Zermelo.redis.zadd(r_limited, lim.collect {|li| [li[1], li[0]]} )
|
50
50
|
|
51
51
|
[limited, r_limited]
|
52
52
|
when :list
|
@@ -26,9 +26,14 @@ module Zermelo
|
|
26
26
|
|
27
27
|
source_keys = @attributes.each_with_object([]) do |(att, value), memo|
|
28
28
|
idx_class = nil
|
29
|
+
use_sort_attr = false
|
29
30
|
unless :id.eql?(att)
|
30
31
|
idx_class = idx_attrs[att.to_s]
|
31
|
-
|
32
|
+
if idx_class.nil?
|
33
|
+
use_sort_attr = :sorted_set.eql?(source.type) &&
|
34
|
+
att.eql?(associated_class.instance_variable_get('@sort_attribute'))
|
35
|
+
raise "'#{att}' property is not indexed" unless use_sort_attr
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
if [Set, Array].any? {|t| value.is_a?(t) }
|
@@ -37,7 +42,21 @@ module Zermelo
|
|
37
42
|
r_conditions_set = backend.key_to_redis_key(conditions_set)
|
38
43
|
|
39
44
|
backend.temp_key_wrap do |conditions_temp_keys|
|
40
|
-
if
|
45
|
+
if use_sort_attr
|
46
|
+
range_keys = value.collect {|v|
|
47
|
+
rl = backend.range_lookup(associated_class.ids_key, v,
|
48
|
+
source_type, attr_types[att], associated_class, conditions_temp_keys)
|
49
|
+
backend.key_to_redis_key(rl)
|
50
|
+
}
|
51
|
+
|
52
|
+
case source.type
|
53
|
+
when :set
|
54
|
+
Zermelo.redis.sunionstore(r_conditions_set, *range_keys)
|
55
|
+
when :sorted_set
|
56
|
+
Zermelo.redis.zunionstore(r_conditions_set, range_keys)
|
57
|
+
end
|
58
|
+
elsif idx_class.nil?
|
59
|
+
# query against the :id field
|
41
60
|
cond_objects, cond_ids = value.partition do |v|
|
42
61
|
[Zermelo::Filter, Zermelo::Associations::Multiple].any? {|c| v.is_a?(c)}
|
43
62
|
end
|
@@ -53,15 +72,33 @@ module Zermelo
|
|
53
72
|
backend.key_to_redis_key(k)
|
54
73
|
end
|
55
74
|
|
56
|
-
|
75
|
+
case source.type
|
76
|
+
when :set
|
77
|
+
Zermelo.redis.sunionstore(r_conditions_set, *cond_keys)
|
78
|
+
when :sorted_set
|
79
|
+
Zermelo.redis.zunionstore(r_conditions_set, cond_keys)
|
80
|
+
end
|
57
81
|
end
|
58
82
|
unless cond_ids.empty?
|
59
|
-
|
60
|
-
|
83
|
+
case source.type
|
84
|
+
when :set
|
85
|
+
s_ids = cond_ids.map {|ci| ci.is_a?(Zermelo::Associations::Singular) ? ci.id : ci }
|
86
|
+
Zermelo.redis.sadd(r_conditions_set, s_ids)
|
87
|
+
when :sorted_set
|
88
|
+
z_ids = cond_ids.map do |ci|
|
89
|
+
# is 1 a valid sort value? what's happening with it?
|
90
|
+
if ci.is_a?(Zermelo::Associations::Singular)
|
91
|
+
[1, ci.id]
|
92
|
+
else
|
93
|
+
[1, ci]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
Zermelo.redis.zadd(r_conditions_set, z_ids)
|
97
|
+
end
|
61
98
|
end
|
62
99
|
else
|
63
|
-
index_keys = value.
|
64
|
-
il = backend.index_lookup(att, associated_class,
|
100
|
+
index_keys = value.collect {|v|
|
101
|
+
il = backend.index_lookup(att, associated_class, source.type,
|
65
102
|
idx_class, v, attr_types[att], conditions_temp_keys)
|
66
103
|
backend.key_to_redis_key(il)
|
67
104
|
}
|
@@ -75,6 +112,9 @@ module Zermelo
|
|
75
112
|
end
|
76
113
|
end
|
77
114
|
memo << conditions_set
|
115
|
+
elsif use_sort_attr
|
116
|
+
memo << backend.range_lookup(associated_class.ids_key, value,
|
117
|
+
source.type, attr_types[att], associated_class, temp_keys)
|
78
118
|
elsif idx_class.nil?
|
79
119
|
case value
|
80
120
|
when Zermelo::Filter
|
@@ -84,15 +124,22 @@ module Zermelo
|
|
84
124
|
when Zermelo::Associations::Multiple
|
85
125
|
memo << value.instance_variable_get('@record_ids_key')
|
86
126
|
else
|
87
|
-
ts = associated_class.send(:temp_key,
|
127
|
+
ts = associated_class.send(:temp_key, source.type)
|
88
128
|
temp_keys << ts
|
89
129
|
r_ts = backend.key_to_redis_key(ts)
|
90
|
-
|
91
|
-
|
130
|
+
case source.type
|
131
|
+
when :set
|
132
|
+
s_id = value.is_a?(Zermelo::Associations::Singular) ? value.id : value
|
133
|
+
Zermelo.redis.sadd(r_ts, s_id)
|
134
|
+
when :sorted_set
|
135
|
+
# is 1 a valid sort value? what's happening with it?
|
136
|
+
z_id = [1, (value.is_a?(Zermelo::Associations::Singular) ? value.id : value)]
|
137
|
+
Zermelo.redis.zadd(r_ts, z_id)
|
138
|
+
end
|
92
139
|
memo << ts
|
93
140
|
end
|
94
141
|
else
|
95
|
-
memo << backend.index_lookup(att, associated_class,
|
142
|
+
memo << backend.index_lookup(att, associated_class, source.type,
|
96
143
|
idx_class, value, attr_types[att], temp_keys)
|
97
144
|
end
|
98
145
|
end
|
data/lib/zermelo/record.rb
CHANGED
@@ -12,9 +12,7 @@ require 'zermelo/records/class_methods'
|
|
12
12
|
require 'zermelo/records/type_validator'
|
13
13
|
|
14
14
|
module Zermelo
|
15
|
-
|
16
15
|
module Record
|
17
|
-
|
18
16
|
extend ActiveSupport::Concern
|
19
17
|
|
20
18
|
include Zermelo::Records::InstMethods
|
@@ -31,11 +29,11 @@ module Zermelo
|
|
31
29
|
|
32
30
|
# include ActiveModel::MassAssignmentSecurity
|
33
31
|
|
34
|
-
@lock = Monitor.new
|
35
|
-
|
36
32
|
extend Zermelo::Records::ClassMethods
|
37
33
|
extend Zermelo::Associations::ClassMethods
|
38
34
|
|
35
|
+
@lock = Monitor.new
|
36
|
+
|
39
37
|
attr_accessor :attributes
|
40
38
|
|
41
39
|
define_model_callbacks :create, :update, :destroy
|
@@ -47,7 +45,5 @@ module Zermelo
|
|
47
45
|
|
48
46
|
define_attributes :id => :string
|
49
47
|
end
|
50
|
-
|
51
48
|
end
|
52
|
-
|
53
49
|
end
|
@@ -11,11 +11,8 @@ require 'zermelo/records/attributes'
|
|
11
11
|
require 'zermelo/records/key'
|
12
12
|
|
13
13
|
module Zermelo
|
14
|
-
|
15
14
|
module Records
|
16
|
-
|
17
15
|
module ClassMethods
|
18
|
-
|
19
16
|
include Zermelo::Records::Attributes
|
20
17
|
|
21
18
|
extend Forwardable
|
@@ -37,14 +34,6 @@ module Zermelo
|
|
37
34
|
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
38
35
|
end
|
39
36
|
|
40
|
-
def add_id(id)
|
41
|
-
backend.add(ids_key, id.to_s)
|
42
|
-
end
|
43
|
-
|
44
|
-
def delete_id(id)
|
45
|
-
backend.delete(ids_key, id.to_s)
|
46
|
-
end
|
47
|
-
|
48
37
|
def lock(*klasses, &block)
|
49
38
|
klasses += [self] unless klasses.include?(self)
|
50
39
|
backend.lock(*klasses, &block)
|
@@ -57,7 +46,7 @@ module Zermelo
|
|
57
46
|
|
58
47
|
begin
|
59
48
|
yield
|
60
|
-
rescue Exception => e
|
49
|
+
rescue Exception # => e
|
61
50
|
backend.abort_transaction
|
62
51
|
# p e.message
|
63
52
|
# puts e.backtrace.join("\n")
|
@@ -94,14 +83,6 @@ module Zermelo
|
|
94
83
|
self.name.demodulize.underscore
|
95
84
|
end
|
96
85
|
|
97
|
-
def ids_key
|
98
|
-
@ids_key ||= Zermelo::Records::Key.new(
|
99
|
-
:klass => self, :name => 'ids',
|
100
|
-
:type => :set,
|
101
|
-
:object => :attribute
|
102
|
-
)
|
103
|
-
end
|
104
|
-
|
105
86
|
def temp_key(type)
|
106
87
|
Zermelo::Records::Key.new(
|
107
88
|
:klass => self,
|
@@ -119,9 +100,60 @@ module Zermelo
|
|
119
100
|
def filter
|
120
101
|
backend.filter(ids_key, self)
|
121
102
|
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module Unordered
|
106
|
+
extend ActiveSupport::Concern
|
107
|
+
|
108
|
+
module ClassMethods
|
109
|
+
def ids_key
|
110
|
+
@ids_key ||= Zermelo::Records::Key.new(
|
111
|
+
:klass => self, :name => 'ids',
|
112
|
+
:type => :set,
|
113
|
+
:object => :attribute
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_id(id)
|
118
|
+
backend.add(ids_key, id)
|
119
|
+
end
|
122
120
|
|
121
|
+
def delete_id(id)
|
122
|
+
backend.delete(ids_key, id)
|
123
|
+
end
|
124
|
+
end
|
123
125
|
end
|
124
126
|
|
125
|
-
|
127
|
+
module Ordered
|
128
|
+
extend ActiveSupport::Concern
|
129
|
+
|
130
|
+
module ClassMethods
|
131
|
+
extend Forwardable
|
126
132
|
|
133
|
+
def_delegators :filter,
|
134
|
+
:first, :last
|
135
|
+
|
136
|
+
def ids_key
|
137
|
+
@ids_key ||= Zermelo::Records::Key.new(
|
138
|
+
:klass => self, :name => 'ids',
|
139
|
+
:type => :sorted_set,
|
140
|
+
:object => :attribute
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def define_sort_attribute(k)
|
145
|
+
@sort_attribute = k
|
146
|
+
@sort_attribute_type = attribute_types[k.to_sym]
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_id(id, val)
|
150
|
+
backend.add(ids_key, [backend.safe_value(@sort_attribute_type, val), id])
|
151
|
+
end
|
152
|
+
|
153
|
+
def delete_id(id)
|
154
|
+
backend.delete(ids_key, id)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
127
159
|
end
|
@@ -83,6 +83,15 @@ module Zermelo
|
|
83
83
|
creating = !self.persisted?
|
84
84
|
saved = false
|
85
85
|
|
86
|
+
sort_val = nil
|
87
|
+
case self
|
88
|
+
when Zermelo::Records::Ordered
|
89
|
+
sort_attr = self.class.instance_variable_get('@sort_attribute')
|
90
|
+
raise 'Ordered record types must define_sort_attribute' if sort_attr.nil?
|
91
|
+
sort_val = @attributes[sort_attr.to_s]
|
92
|
+
raise "Value required for sort_attribute #{sort_attr}" if sort_val.nil?
|
93
|
+
end
|
94
|
+
|
86
95
|
run_callbacks( (creating ? :create : :update) ) do
|
87
96
|
|
88
97
|
idx_attrs = self.class.send(:with_index_data) do |d|
|
@@ -119,9 +128,16 @@ module Zermelo
|
|
119
128
|
end
|
120
129
|
end
|
121
130
|
|
122
|
-
# ids is a set, so update won't create duplicates
|
131
|
+
# ids is a set/sorted set, so update won't create duplicates
|
123
132
|
# NB influxdb backend doesn't need this
|
124
|
-
|
133
|
+
|
134
|
+
# FIXME distinguish between this in the class methods?
|
135
|
+
case self
|
136
|
+
when Zermelo::Records::Ordered
|
137
|
+
self.class.add_id(@attributes['id'], sort_val)
|
138
|
+
when Zermelo::Records::Unordered
|
139
|
+
self.class.add_id(@attributes['id'])
|
140
|
+
end
|
125
141
|
end
|
126
142
|
|
127
143
|
@is_new = false
|
@@ -4,10 +4,22 @@ require 'zermelo/record'
|
|
4
4
|
|
5
5
|
module Zermelo
|
6
6
|
module Records
|
7
|
-
module
|
7
|
+
module RedisSet
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
include Zermelo::Record
|
11
|
+
include Zermelo::Records::Unordered
|
12
|
+
|
13
|
+
included do
|
14
|
+
set_backend :redis
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module RedisSortedSet
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
include Zermelo::Record
|
22
|
+
include Zermelo::Records::Ordered
|
11
23
|
|
12
24
|
included do
|
13
25
|
set_backend :redis
|
@@ -8,9 +8,9 @@ module Zermelo
|
|
8
8
|
value = record.send(name)
|
9
9
|
next if value.nil?
|
10
10
|
valid_type = Zermelo::ALL_TYPES[type]
|
11
|
-
unless valid_type.any? {|
|
11
|
+
unless valid_type.any? {|t| value.is_a?(t) }
|
12
12
|
count = (valid_type.size > 1) ? 'one of ' : ''
|
13
|
-
type_str = valid_type.collect {|
|
13
|
+
type_str = valid_type.collect {|t| t.name }.join(", ")
|
14
14
|
record.errors.add(name, "should be #{count}#{type_str} but is #{value.class.name}")
|
15
15
|
end
|
16
16
|
end
|
data/lib/zermelo/version.rb
CHANGED
@@ -12,7 +12,7 @@ describe Zermelo::Associations::Index do
|
|
12
12
|
|
13
13
|
module ZermeloExamples
|
14
14
|
class RedisIndex
|
15
|
-
include Zermelo::Records::
|
15
|
+
include Zermelo::Records::RedisSet
|
16
16
|
define_attributes :emotion => :string
|
17
17
|
validates :emotion, :presence => true, :inclusion => {:in => %w(happy sad indifferent)}
|
18
18
|
index_by :emotion
|
@@ -212,13 +212,13 @@ describe Zermelo::Associations::Multiple do
|
|
212
212
|
|
213
213
|
module ZermeloExamples
|
214
214
|
class AssociationsHasManyParentRedis
|
215
|
-
include Zermelo::Records::
|
215
|
+
include Zermelo::Records::RedisSet
|
216
216
|
has_many :children, :class_name => 'ZermeloExamples::AssociationsHasManyChildRedis',
|
217
217
|
:inverse_of => :parent
|
218
218
|
end
|
219
219
|
|
220
220
|
class AssociationsHasManyChildRedis
|
221
|
-
include Zermelo::Records::
|
221
|
+
include Zermelo::Records::RedisSet
|
222
222
|
define_attributes :important => :boolean
|
223
223
|
index_by :important
|
224
224
|
belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasManyParentRedis',
|
@@ -671,7 +671,7 @@ describe Zermelo::Associations::Multiple do
|
|
671
671
|
|
672
672
|
module ZermeloExamples
|
673
673
|
class AssociationsHasAndBelongsToManyPrimaryRedis
|
674
|
-
include Zermelo::Records::
|
674
|
+
include Zermelo::Records::RedisSet
|
675
675
|
define_attributes :active => :boolean
|
676
676
|
index_by :active
|
677
677
|
has_and_belongs_to_many :secondaries,
|
@@ -680,7 +680,7 @@ describe Zermelo::Associations::Multiple do
|
|
680
680
|
end
|
681
681
|
|
682
682
|
class AssociationsHasAndBelongsToManySecondaryRedis
|
683
|
-
include Zermelo::Records::
|
683
|
+
include Zermelo::Records::RedisSet
|
684
684
|
# define_attributes :important => :boolean
|
685
685
|
# index_by :important
|
686
686
|
has_and_belongs_to_many :primaries,
|
@@ -773,7 +773,7 @@ describe Zermelo::Associations::Multiple do
|
|
773
773
|
|
774
774
|
module ZermeloExamples
|
775
775
|
class AssociationsHasSortedSetParentRedis
|
776
|
-
include Zermelo::Records::
|
776
|
+
include Zermelo::Records::RedisSet
|
777
777
|
has_sorted_set :children, :class_name => 'ZermeloExamples::AssociationsHasSortedSetChildRedis',
|
778
778
|
:inverse_of => :parent, :key => :timestamp
|
779
779
|
has_sorted_set :reversed_children, :class_name => 'ZermeloExamples::AssociationsHasSortedSetChildRedis',
|
@@ -781,11 +781,11 @@ describe Zermelo::Associations::Multiple do
|
|
781
781
|
end
|
782
782
|
|
783
783
|
class AssociationsHasSortedSetChildRedis
|
784
|
-
include Zermelo::Records::
|
784
|
+
include Zermelo::Records::RedisSortedSet
|
785
785
|
define_attributes :emotion => :string,
|
786
786
|
:timestamp => :timestamp
|
787
|
+
define_sort_attribute :timestamp
|
787
788
|
index_by :emotion
|
788
|
-
range_index_by :timestamp
|
789
789
|
belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasSortedSetParentRedis',
|
790
790
|
:inverse_of => :children
|
791
791
|
belongs_to :reversed_parent, :class_name => 'ZermeloExamples::AssociationsHasSortedSetParentRedis',
|
@@ -811,10 +811,9 @@ describe Zermelo::Associations::Multiple do
|
|
811
811
|
'timestamp' => attrs[:timestamp].to_f}.to_a.flatten)
|
812
812
|
|
813
813
|
redis.sadd("#{ck}::indices:by_emotion:string:#{attrs[:emotion]}", attrs[:id])
|
814
|
-
redis.zadd("#{ck}::indices:by_timestamp", attrs[:timestamp].to_f, attrs[:id])
|
815
814
|
redis.hmset("#{ck}:#{attrs[:id]}:assocs:belongs_to",
|
816
815
|
{'parent_id' => parent.id}.to_a.flatten) unless parent.nil?
|
817
|
-
redis.
|
816
|
+
redis.zadd("#{ck}::attrs:ids", attrs[:timestamp].to_f, attrs[:id])
|
818
817
|
end
|
819
818
|
|
820
819
|
def create_reversed_child(parent, attrs = {})
|
@@ -824,10 +823,9 @@ describe Zermelo::Associations::Multiple do
|
|
824
823
|
'timestamp' => attrs[:timestamp].to_f}.to_a.flatten)
|
825
824
|
|
826
825
|
redis.sadd("#{ck}::indices:by_emotion:string:#{attrs[:emotion]}", attrs[:id])
|
827
|
-
redis.zadd("#{ck}::indices:by_timestamp", attrs[:timestamp].to_f, attrs[:id])
|
828
826
|
redis.hmset("#{ck}:#{attrs[:id]}:assocs:belongs_to",
|
829
827
|
{'reversed_parent_id' => parent.id}.to_a.flatten) unless parent.nil?
|
830
|
-
redis.
|
828
|
+
redis.zadd("#{ck}::attrs:ids", attrs[:timestamp].to_f, attrs[:id])
|
831
829
|
end
|
832
830
|
|
833
831
|
let(:parent) {
|
@@ -846,11 +844,10 @@ describe Zermelo::Associations::Multiple do
|
|
846
844
|
"#{pk}:8:assocs:children_ids",
|
847
845
|
"#{ck}::attrs:ids",
|
848
846
|
"#{ck}::indices:by_emotion:string:indifferent",
|
849
|
-
"#{ck}::indices:by_timestamp",
|
850
847
|
"#{ck}:4:attrs",
|
851
848
|
"#{ck}:4:assocs:belongs_to"])
|
852
849
|
|
853
|
-
expect(redis.
|
850
|
+
expect(redis.zrange("#{ck}::attrs:ids", 0, -1)).to eq(['4'])
|
854
851
|
expect(redis.hgetall("#{ck}:4:attrs")).to eq(
|
855
852
|
{'emotion' => 'indifferent', 'timestamp' => time.to_f.to_s}
|
856
853
|
)
|
@@ -882,12 +879,12 @@ describe Zermelo::Associations::Multiple do
|
|
882
879
|
create_child(parent, :id => '4', :emotion => 'indifferent', :timestamp => time)
|
883
880
|
child = child_class.find_by_id('4')
|
884
881
|
|
885
|
-
expect(redis.
|
882
|
+
expect(redis.zrange("#{ck}::attrs:ids", 0, -1)).to eq(['4'])
|
886
883
|
expect(redis.zrange("#{pk}:8:assocs:children_ids", 0, -1)).to eq(['4'])
|
887
884
|
|
888
885
|
parent.children.remove(child)
|
889
886
|
|
890
|
-
expect(redis.
|
887
|
+
expect(redis.zrange("#{ck}::attrs:ids", 0, -1)).to eq(['4']) # child not deleted
|
891
888
|
expect(redis.zrange("#{pk}:8:assocs:children_ids", 0, -1)).to eq([]) # but association is
|
892
889
|
end
|
893
890
|
|
@@ -898,7 +895,6 @@ describe Zermelo::Associations::Multiple do
|
|
898
895
|
expect(redis.keys).to match_array(["#{pk}::attrs:ids",
|
899
896
|
"#{pk}:8:assocs:children_ids",
|
900
897
|
"#{ck}::attrs:ids",
|
901
|
-
"#{ck}::indices:by_timestamp",
|
902
898
|
"#{ck}::indices:by_emotion:string:upset",
|
903
899
|
"#{ck}:6:attrs",
|
904
900
|
"#{ck}:6:assocs:belongs_to"])
|
@@ -906,7 +902,6 @@ describe Zermelo::Associations::Multiple do
|
|
906
902
|
parent.destroy
|
907
903
|
|
908
904
|
expect(redis.keys).to match_array(["#{ck}::attrs:ids",
|
909
|
-
"#{ck}::indices:by_timestamp",
|
910
905
|
"#{ck}::indices:by_emotion:string:upset",
|
911
906
|
"#{ck}:6:attrs"])
|
912
907
|
end
|
@@ -12,10 +12,10 @@ describe Zermelo::Associations::RangeIndex do
|
|
12
12
|
|
13
13
|
module ZermeloExamples
|
14
14
|
class RedisRangeIndex
|
15
|
-
include Zermelo::Records::
|
15
|
+
include Zermelo::Records::RedisSet
|
16
16
|
define_attributes :created_at => :timestamp
|
17
|
-
validates :created_at, :presence => true
|
18
17
|
range_index_by :created_at
|
18
|
+
validates :created_at, :presence => true
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -42,13 +42,13 @@ describe Zermelo::Associations::Singular do
|
|
42
42
|
|
43
43
|
module ZermeloExamples
|
44
44
|
class AssociationsHasOneParentRedis
|
45
|
-
include Zermelo::Records::
|
45
|
+
include Zermelo::Records::RedisSet
|
46
46
|
has_one :child, :class_name => 'ZermeloExamples::AssociationsHasOneChildRedis',
|
47
47
|
:inverse_of => :parent
|
48
48
|
end
|
49
49
|
|
50
50
|
class AssociationsHasOneChildRedis
|
51
|
-
include Zermelo::Records::
|
51
|
+
include Zermelo::Records::RedisSet
|
52
52
|
belongs_to :parent, :class_name => 'ZermeloExamples::AssociationsHasOneParentRedis',
|
53
53
|
:inverse_of => :child
|
54
54
|
end
|
@@ -10,7 +10,7 @@ describe Zermelo::Associations::UniqueIndex do
|
|
10
10
|
|
11
11
|
module ZermeloExamples
|
12
12
|
class RedisUniqueIndex
|
13
|
-
include Zermelo::Records::
|
13
|
+
include Zermelo::Records::RedisSet
|
14
14
|
define_attributes :name => :string
|
15
15
|
validates :name, :presence => true
|
16
16
|
unique_index_by :name
|
@@ -8,12 +8,12 @@ describe Zermelo::Locks::RedisLock, :redis => true do
|
|
8
8
|
|
9
9
|
module Zermelo
|
10
10
|
class RedisLockExample
|
11
|
-
include Zermelo::Records::
|
11
|
+
include Zermelo::Records::RedisSet
|
12
12
|
define_attributes :name => :string
|
13
13
|
end
|
14
14
|
|
15
15
|
class AnotherExample
|
16
|
-
include Zermelo::Records::
|
16
|
+
include Zermelo::Records::RedisSet
|
17
17
|
define_attributes :age => :integer
|
18
18
|
end
|
19
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zermelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ali Graham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -137,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
137
|
requirements:
|
138
138
|
- Redis and/or InfluxDB, and the related gems
|
139
139
|
rubyforge_project:
|
140
|
-
rubygems_version: 2.4.
|
140
|
+
rubygems_version: 2.4.5.1
|
141
141
|
signing_key:
|
142
142
|
specification_version: 4
|
143
143
|
summary: ActiveModel-based set-theoretic ORM for Redis/InfluxDB
|