zermelo 1.1.0 → 1.2.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 +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
@@ -1,111 +0,0 @@
|
|
1
|
-
require 'zermelo/filters/base'
|
2
|
-
|
3
|
-
# TODO check escaping of ids and index_keys -- shouldn't allow bare :
|
4
|
-
|
5
|
-
module Zermelo
|
6
|
-
|
7
|
-
module Filters
|
8
|
-
|
9
|
-
class RedisFilter
|
10
|
-
|
11
|
-
include Zermelo::Filters::Base
|
12
|
-
|
13
|
-
# TODO polite error when first/last applied to set
|
14
|
-
|
15
|
-
# more step users
|
16
|
-
def first
|
17
|
-
lock {
|
18
|
-
first_id = resolve_steps(:first)
|
19
|
-
first_id.nil? ? nil : _load(first_id)
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
def last
|
24
|
-
lock {
|
25
|
-
last_id = resolve_steps(:last)
|
26
|
-
last_id.nil? ? nil : _load(last_id)
|
27
|
-
}
|
28
|
-
end
|
29
|
-
# end step users
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def _count
|
34
|
-
resolve_steps(:count)
|
35
|
-
end
|
36
|
-
|
37
|
-
def _ids
|
38
|
-
resolve_steps(:ids)
|
39
|
-
end
|
40
|
-
|
41
|
-
def _exists?(id)
|
42
|
-
return if id.nil?
|
43
|
-
resolve_steps(:exists?, id)
|
44
|
-
end
|
45
|
-
|
46
|
-
# If called with a block -- takes a block and passes the name of a set to
|
47
|
-
# it; deletes all temporary sets once done
|
48
|
-
|
49
|
-
# If called with any arguments -- treats them as a hash of shortcuts
|
50
|
-
|
51
|
-
# If not called with any arguments -- returns two values, the first is
|
52
|
-
# the name of a set containing the filtered ids, the second is a boolean
|
53
|
-
# for whether or not to clear up that set once it's been used
|
54
|
-
|
55
|
-
def resolve_steps(shortcut, *args)
|
56
|
-
if @steps.empty?
|
57
|
-
sc_klass = {
|
58
|
-
:list => Zermelo::Filters::Steps::ListStep,
|
59
|
-
:set => Zermelo::Filters::Steps::SetStep,
|
60
|
-
:sorted_set => Zermelo::Filters::Steps::SortedSetStep
|
61
|
-
}[@initial_key.type]
|
62
|
-
sc = sc_klass.const_get(:REDIS_SHORTCUTS)[shortcut]
|
63
|
-
ret = if sc.nil?
|
64
|
-
yield(@initial_key)
|
65
|
-
else
|
66
|
-
sc.call(*([backend.key_to_redis_key(@initial_key)] + args))
|
67
|
-
end
|
68
|
-
return(ret)
|
69
|
-
end
|
70
|
-
|
71
|
-
idx_attrs = @associated_class.send(:with_index_data) do |d|
|
72
|
-
d.each_with_object({}) do |(name, data), memo|
|
73
|
-
memo[name.to_s] = data.index_klass
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
attr_types = @associated_class.send(:attribute_types)
|
78
|
-
|
79
|
-
backend.temp_key_wrap do |temp_keys|
|
80
|
-
result = nil
|
81
|
-
last_step = @steps.last
|
82
|
-
|
83
|
-
step_opts = {
|
84
|
-
:index_attrs => idx_attrs,
|
85
|
-
:attr_types => attr_types,
|
86
|
-
:temp_keys => temp_keys,
|
87
|
-
:source => @initial_key
|
88
|
-
}
|
89
|
-
|
90
|
-
@steps.each do |step|
|
91
|
-
unless step.class.accepted_types.include?(step_opts[:source].type)
|
92
|
-
raise "'#{step.class.name}' does not accept input type #{step_opts[:source].type}"
|
93
|
-
end
|
94
|
-
|
95
|
-
if step == last_step
|
96
|
-
step_opts.update(:shortcut => shortcut, :shortcut_args => args)
|
97
|
-
end
|
98
|
-
|
99
|
-
result = step.resolve(backend, @associated_class, step_opts)
|
100
|
-
|
101
|
-
step_opts[:source] = result unless step == last_step
|
102
|
-
end
|
103
|
-
|
104
|
-
result
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
end
|
@@ -1,156 +0,0 @@
|
|
1
|
-
require 'zermelo/filters/steps/base_step'
|
2
|
-
|
3
|
-
module Zermelo
|
4
|
-
module Filters
|
5
|
-
class Steps
|
6
|
-
class SortedSetStep < Zermelo::Filters::Steps::BaseStep
|
7
|
-
def self.accepted_types
|
8
|
-
[:sorted_set]
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.returns_type
|
12
|
-
:sorted_set
|
13
|
-
end
|
14
|
-
|
15
|
-
REDIS_SHORTCUTS = {
|
16
|
-
:ids => proc {|key| Zermelo.redis.zrange(key, 0, -1) },
|
17
|
-
:count => proc {|key| Zermelo.redis.zcard(key) },
|
18
|
-
:exists? => proc {|key, id| !Zermelo.redis.zscore(key, id).nil? },
|
19
|
-
:first => proc {|key| Zermelo.redis.zrange(key, 0, 0).first },
|
20
|
-
:last => proc {|key| Zermelo.redis.zrevrange(key, 0, 0).first }
|
21
|
-
}
|
22
|
-
|
23
|
-
def resolve(backend, associated_class, opts = {})
|
24
|
-
op = @options[:op]
|
25
|
-
start = @options[:start]
|
26
|
-
finish = @options[:finish]
|
27
|
-
|
28
|
-
case backend
|
29
|
-
when Zermelo::Backends::RedisBackend
|
30
|
-
source = opts[:source]
|
31
|
-
idx_attrs = opts[:index_attrs]
|
32
|
-
attr_types = opts[:attr_types]
|
33
|
-
temp_keys = opts[:temp_keys]
|
34
|
-
|
35
|
-
range_temp_key = associated_class.send(:temp_key, :sorted_set)
|
36
|
-
temp_keys << range_temp_key
|
37
|
-
range_ids_set = backend.key_to_redis_key(range_temp_key)
|
38
|
-
|
39
|
-
if @options[:by_score]
|
40
|
-
start = '-inf' if start.nil? || (start <= 0)
|
41
|
-
finish = '+inf' if finish.nil? || (finish <= 0)
|
42
|
-
else
|
43
|
-
start = 0 if start.nil?
|
44
|
-
finish = -1 if finish.nil?
|
45
|
-
end
|
46
|
-
|
47
|
-
args = [start, finish]
|
48
|
-
|
49
|
-
if @options[:by_score]
|
50
|
-
query = :zrangebyscore
|
51
|
-
args = args.map(&:to_s)
|
52
|
-
else
|
53
|
-
query = :zrange
|
54
|
-
end
|
55
|
-
|
56
|
-
args << {:with_scores => :true}
|
57
|
-
|
58
|
-
if @options[:limit]
|
59
|
-
args.last.update(:limit => [0, @options[:limit].to_i])
|
60
|
-
end
|
61
|
-
|
62
|
-
r_source = backend.key_to_redis_key(source)
|
63
|
-
args.unshift(r_source)
|
64
|
-
|
65
|
-
range_ids_scores = Zermelo.redis.send(query, *args)
|
66
|
-
|
67
|
-
unless range_ids_scores.empty?
|
68
|
-
Zermelo.redis.zadd(range_ids_set, range_ids_scores.map(&:reverse))
|
69
|
-
end
|
70
|
-
|
71
|
-
self.class.evaluate(backend, @options[:op], associated_class,
|
72
|
-
source, [range_temp_key], temp_keys, opts)
|
73
|
-
|
74
|
-
when Zermelo::Backends::InfluxDBBackend
|
75
|
-
|
76
|
-
query = ''
|
77
|
-
|
78
|
-
unless opts[:first].is_a?(TrueClass)
|
79
|
-
case @options[:op]
|
80
|
-
when :intersect_range, :diff_range
|
81
|
-
query += ' AND '
|
82
|
-
when :union_range
|
83
|
-
query += ' OR '
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
start = nil if !start.nil? && (start <= 0)
|
88
|
-
finish = nil if !finish.nil? && (finish <= 0)
|
89
|
-
|
90
|
-
unless start.nil? && finish.nil?
|
91
|
-
time_q = []
|
92
|
-
|
93
|
-
case @options[:op]
|
94
|
-
when :intersect_range, :union_range
|
95
|
-
unless start.nil?
|
96
|
-
time_q << "(time > #{start - 1}s)"
|
97
|
-
end
|
98
|
-
unless finish.nil?
|
99
|
-
time_q << "(time < #{finish}s)"
|
100
|
-
end
|
101
|
-
when :diff_range
|
102
|
-
unless start.nil?
|
103
|
-
time_q << "(time < #{start}s)"
|
104
|
-
end
|
105
|
-
unless finish.nil?
|
106
|
-
time_q << "(time > #{finish - 1}s)"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
query += time_q.join(' AND ')
|
111
|
-
end
|
112
|
-
|
113
|
-
query += ")"
|
114
|
-
query
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.evaluate(backend, op, associated_class, source, source_keys, temp_keys, opts = {})
|
119
|
-
shortcut = opts[:shortcut]
|
120
|
-
|
121
|
-
weights = case op
|
122
|
-
when :union, :union_range
|
123
|
-
[0.0] * source_keys.length
|
124
|
-
when :diff, :diff_range
|
125
|
-
[-1.0] * source_keys.length
|
126
|
-
end
|
127
|
-
|
128
|
-
r_source = backend.key_to_redis_key(source)
|
129
|
-
r_source_keys = source_keys.collect {|sk| backend.key_to_redis_key(sk) }
|
130
|
-
|
131
|
-
dest_sorted_set = associated_class.send(:temp_key, :sorted_set)
|
132
|
-
temp_keys << dest_sorted_set
|
133
|
-
r_dest_sorted_set = backend.key_to_redis_key(dest_sorted_set)
|
134
|
-
|
135
|
-
case op
|
136
|
-
when :union, :union_range
|
137
|
-
Zermelo.redis.zinterstore(r_dest_sorted_set, r_source_keys, :weights => weights, :aggregate => 'max')
|
138
|
-
Zermelo.redis.zunionstore(r_dest_sorted_set, [r_source, r_dest_sorted_set])
|
139
|
-
when :intersect, :intersect_range
|
140
|
-
Zermelo.redis.zinterstore(r_dest_sorted_set, [r_source] + r_source_keys, :weights => weights, :aggregate => 'max')
|
141
|
-
when :diff, :diff_range
|
142
|
-
# 'zdiffstore' via weights, relies on non-zero scores being used
|
143
|
-
# see https://code.google.com/p/redis/issues/detail?id=579
|
144
|
-
Zermelo.redis.zinterstore(r_dest_sorted_set, r_source_keys, :weights => weights, :aggregate => 'max')
|
145
|
-
Zermelo.redis.zunionstore(r_dest_sorted_set, [r_source, r_dest_sorted_set])
|
146
|
-
Zermelo.redis.zremrangebyscore(r_dest_sorted_set, "0", "0")
|
147
|
-
end
|
148
|
-
|
149
|
-
return dest_sorted_set if shortcut.nil?
|
150
|
-
REDIS_SHORTCUTS[shortcut].call(*([r_dest_sorted_set] + opts[:shortcut_args]))
|
151
|
-
end
|
152
|
-
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
data/lib/zermelo/records/base.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'monitor'
|
2
|
-
|
3
|
-
require 'active_support/concern'
|
4
|
-
require 'active_support/core_ext/object/blank'
|
5
|
-
require 'active_support/inflector'
|
6
|
-
require 'active_model'
|
7
|
-
|
8
|
-
require 'zermelo/associations/class_methods'
|
9
|
-
|
10
|
-
require 'zermelo/records/instance_methods'
|
11
|
-
require 'zermelo/records/class_methods'
|
12
|
-
require 'zermelo/records/type_validator'
|
13
|
-
|
14
|
-
# TODO escape ids and index_keys -- shouldn't allow bare :
|
15
|
-
|
16
|
-
# TODO callbacks on before/after add/delete on association?
|
17
|
-
|
18
|
-
# TODO optional sort via Redis SORT, first/last for has_many via those
|
19
|
-
|
20
|
-
# TODO get DIFF working for exclusion case against ZSETs
|
21
|
-
|
22
|
-
module Zermelo
|
23
|
-
|
24
|
-
module Records
|
25
|
-
|
26
|
-
module Base
|
27
|
-
|
28
|
-
extend ActiveSupport::Concern
|
29
|
-
|
30
|
-
include Zermelo::Records::InstMethods
|
31
|
-
|
32
|
-
included do
|
33
|
-
include ActiveModel::AttributeMethods
|
34
|
-
extend ActiveModel::Callbacks
|
35
|
-
include ActiveModel::Dirty
|
36
|
-
include ActiveModel::Validations
|
37
|
-
include ActiveModel::Validations::Callbacks
|
38
|
-
|
39
|
-
# include ActiveModel::MassAssignmentSecurity
|
40
|
-
|
41
|
-
@lock = Monitor.new
|
42
|
-
|
43
|
-
extend Zermelo::Records::ClassMethods
|
44
|
-
extend Zermelo::Associations::ClassMethods
|
45
|
-
|
46
|
-
attr_accessor :attributes
|
47
|
-
|
48
|
-
define_model_callbacks :create, :update, :destroy
|
49
|
-
|
50
|
-
attribute_method_suffix "=" # attr_writers
|
51
|
-
# attribute_method_suffix "" # attr_readers # DEPRECATED
|
52
|
-
|
53
|
-
validates_with Zermelo::Records::TypeValidator
|
54
|
-
|
55
|
-
define_attributes :id => :string
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
|
-
require 'zermelo/records/base'
|
4
|
-
|
5
|
-
# TODO check escaping of ids and index_keys -- shouldn't allow bare :, ' '
|
6
|
-
|
7
|
-
# TODO callbacks on before/after add/delete on association?
|
8
|
-
|
9
|
-
module Zermelo
|
10
|
-
|
11
|
-
module Records
|
12
|
-
|
13
|
-
module RedisRecord
|
14
|
-
|
15
|
-
extend ActiveSupport::Concern
|
16
|
-
|
17
|
-
include Zermelo::Records::Base
|
18
|
-
|
19
|
-
included do
|
20
|
-
set_backend :redis
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|