zermelo 1.3.0 → 1.4.0
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.
- 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
|