zermelo 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +10 -0
- data/.travis.yml +27 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +512 -0
- data/Rakefile +1 -0
- data/lib/zermelo/associations/association_data.rb +24 -0
- data/lib/zermelo/associations/belongs_to.rb +115 -0
- data/lib/zermelo/associations/class_methods.rb +244 -0
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +128 -0
- data/lib/zermelo/associations/has_many.rb +120 -0
- data/lib/zermelo/associations/has_one.rb +109 -0
- data/lib/zermelo/associations/has_sorted_set.rb +124 -0
- data/lib/zermelo/associations/index.rb +50 -0
- data/lib/zermelo/associations/index_data.rb +18 -0
- data/lib/zermelo/associations/unique_index.rb +44 -0
- data/lib/zermelo/backends/base.rb +115 -0
- data/lib/zermelo/backends/influxdb_backend.rb +178 -0
- data/lib/zermelo/backends/redis_backend.rb +281 -0
- data/lib/zermelo/filters/base.rb +235 -0
- data/lib/zermelo/filters/influxdb_filter.rb +162 -0
- data/lib/zermelo/filters/redis_filter.rb +558 -0
- data/lib/zermelo/filters/steps/base_step.rb +22 -0
- data/lib/zermelo/filters/steps/diff_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/diff_step.rb +17 -0
- data/lib/zermelo/filters/steps/intersect_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/intersect_step.rb +17 -0
- data/lib/zermelo/filters/steps/limit_step.rb +17 -0
- data/lib/zermelo/filters/steps/offset_step.rb +17 -0
- data/lib/zermelo/filters/steps/sort_step.rb +17 -0
- data/lib/zermelo/filters/steps/union_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/union_step.rb +17 -0
- data/lib/zermelo/locks/no_lock.rb +16 -0
- data/lib/zermelo/locks/redis_lock.rb +221 -0
- data/lib/zermelo/records/base.rb +62 -0
- data/lib/zermelo/records/class_methods.rb +127 -0
- data/lib/zermelo/records/collection.rb +14 -0
- data/lib/zermelo/records/errors.rb +24 -0
- data/lib/zermelo/records/influxdb_record.rb +35 -0
- data/lib/zermelo/records/instance_methods.rb +224 -0
- data/lib/zermelo/records/key.rb +19 -0
- data/lib/zermelo/records/redis_record.rb +27 -0
- data/lib/zermelo/records/type_validator.rb +20 -0
- data/lib/zermelo/version.rb +3 -0
- data/lib/zermelo.rb +102 -0
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_many_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_one_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +6 -0
- data/spec/lib/zermelo/associations/index_spec.rb +6 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +6 -0
- 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/locks/redis_lock_spec.rb +170 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +258 -0
- data/spec/lib/zermelo/records/key_spec.rb +6 -0
- data/spec/lib/zermelo/records/redis_record_spec.rb +1426 -0
- data/spec/lib/zermelo/records/type_validator_spec.rb +6 -0
- data/spec/lib/zermelo/version_spec.rb +6 -0
- data/spec/lib/zermelo_spec.rb +6 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/support/profile_all_formatter.rb +44 -0
- data/spec/support/uncolored_doc_formatter.rb +74 -0
- data/zermelo.gemspec +30 -0
- metadata +174 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'zermelo/filters/steps/base_step'
|
2
|
+
|
3
|
+
module Zermelo
|
4
|
+
module Filters
|
5
|
+
class Steps
|
6
|
+
class SortStep < Zermelo::Filters::Steps::BaseStep
|
7
|
+
def self.accepted_types
|
8
|
+
[:set, :sorted_set]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.returns_type
|
12
|
+
:list
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'zermelo/filters/steps/base_step'
|
2
|
+
|
3
|
+
module Zermelo
|
4
|
+
module Filters
|
5
|
+
class Steps
|
6
|
+
class UnionRangeStep < 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
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'zermelo/filters/steps/base_step'
|
2
|
+
|
3
|
+
module Zermelo
|
4
|
+
module Filters
|
5
|
+
class Steps
|
6
|
+
class UnionStep < Zermelo::Filters::Steps::BaseStep
|
7
|
+
def self.accepted_types
|
8
|
+
[:set, :sorted_set]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.returns_type
|
12
|
+
:set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
|
2
|
+
module Zermelo
|
3
|
+
|
4
|
+
class LockNotAcquired < StandardError; end
|
5
|
+
|
6
|
+
module Locks
|
7
|
+
|
8
|
+
class RedisLock
|
9
|
+
|
10
|
+
# Adapted from https://github.com/mlanett/redis-lock,
|
11
|
+
# now covers locking multiple keys at once
|
12
|
+
|
13
|
+
attr_accessor :expires_at, :life, :sleep_in_ms
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@owner_value = Thread.current.object_id
|
17
|
+
@life = 60
|
18
|
+
@timeout = 10
|
19
|
+
@sleep_in_ms = 125
|
20
|
+
end
|
21
|
+
|
22
|
+
def lock(*record_klasses, &block)
|
23
|
+
@keys = record_klasses.map{|k| k.send(:class_key) }.sort.map{|k| "#{k}::lock" }
|
24
|
+
do_lock_with_timeout(@timeout) or raise Zermelo::LockNotAcquired.new(@keys.join(", "))
|
25
|
+
result = true
|
26
|
+
if block
|
27
|
+
begin
|
28
|
+
result = (block.arity == 1) ? block.call(self) : block.call
|
29
|
+
# rescue Exception => e
|
30
|
+
# puts e.message
|
31
|
+
# puts e.backtrace.join("\n")
|
32
|
+
# raise e
|
33
|
+
ensure
|
34
|
+
release_lock
|
35
|
+
end
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def extend_life( new_life )
|
41
|
+
do_extend( new_life ) or raise Zermelo::LockNotAcquired.new(@keys.join(", "))
|
42
|
+
end
|
43
|
+
|
44
|
+
def unlock
|
45
|
+
release_lock
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def full_keys
|
51
|
+
@full_keys ||= @keys.map {|k| ["#{k}:owner", "#{k}:expiry"] }.flatten
|
52
|
+
end
|
53
|
+
|
54
|
+
def owner_keys
|
55
|
+
@owner_keys ||= @keys.map {|k| "#{k}:owner" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def expiry_keys
|
59
|
+
@expiry_keys ||= @keys.map {|k| "#{k}:expiry" }
|
60
|
+
end
|
61
|
+
|
62
|
+
def do_lock_with_timeout( timeout )
|
63
|
+
locked = false
|
64
|
+
with_timeout(timeout) { locked = do_lock }
|
65
|
+
locked
|
66
|
+
end
|
67
|
+
|
68
|
+
# @returns true if locked, false otherwise
|
69
|
+
def do_lock( tries = 3 )
|
70
|
+
# We need to set both owner and expire at the same time
|
71
|
+
# If the existing lock is stale, we delete it and try again once
|
72
|
+
|
73
|
+
locked = nil
|
74
|
+
|
75
|
+
loop do
|
76
|
+
new_xval = Time.now.to_i + @life
|
77
|
+
|
78
|
+
lock_keyvals = @keys.map {|k| ["#{k}:owner", @owner_value,
|
79
|
+
"#{k}:expiry", new_xval] }.flatten
|
80
|
+
|
81
|
+
result = Zermelo.redis.msetnx(*lock_keyvals)
|
82
|
+
|
83
|
+
if [1, true].include?(result)
|
84
|
+
# log :debug, "do_lock() success"
|
85
|
+
@expires_at = new_xval
|
86
|
+
locked = true
|
87
|
+
break
|
88
|
+
else
|
89
|
+
# log :debug, "do_lock() failed"
|
90
|
+
# consider the possibility that this lock is stale
|
91
|
+
tries -= 1
|
92
|
+
next if tries > 0 && stale_key?
|
93
|
+
locked = false
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
locked
|
98
|
+
end
|
99
|
+
|
100
|
+
def do_extend( new_life )
|
101
|
+
# We use watch and a transaction to ensure we only change a lock we own
|
102
|
+
# The transaction fails if the watched variable changed
|
103
|
+
# Use my_owner = oval to make testing easier.
|
104
|
+
new_xval = Time.now.to_i + new_life
|
105
|
+
extended = false
|
106
|
+
with_watch( *owner_keys ) do
|
107
|
+
owners = Zermelo.redis.mget( *owner_keys )
|
108
|
+
if owners == ([@owner_value.to_s] * owner_keys.size)
|
109
|
+
result = Zermelo.redis.multi do |multi|
|
110
|
+
multi.mset( *(expiry_keys.zip( [new_xval] * expiry_keys.size)) )
|
111
|
+
end
|
112
|
+
if result == ['OK']
|
113
|
+
# log :debug, "do_extend() success"
|
114
|
+
@expires_at = new_xval
|
115
|
+
extended = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
extended
|
120
|
+
end
|
121
|
+
|
122
|
+
# Only actually deletes it if we own it.
|
123
|
+
# There may be strange cases where we fail to delete it, in which case expiration will solve the problem.
|
124
|
+
def release_lock
|
125
|
+
released = false
|
126
|
+
with_watch( *full_keys ) do
|
127
|
+
owners = Zermelo.redis.mget( *owner_keys )
|
128
|
+
if owners == ([@owner_value.to_s] * owner_keys.size)
|
129
|
+
result = Zermelo.redis.multi do |multi|
|
130
|
+
multi.del(*full_keys)
|
131
|
+
end
|
132
|
+
if result && (result.size == 1) && (result.first == full_keys.size)
|
133
|
+
released = true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
released
|
138
|
+
end
|
139
|
+
|
140
|
+
def stale_key?
|
141
|
+
# Check if expiration exists and is it stale?
|
142
|
+
# If so, delete it.
|
143
|
+
# watch() all keys so we can detect if they change while we do this
|
144
|
+
# multi() will fail if keys have changed after watch()
|
145
|
+
# Thus, we snapshot consistency at the time of watch()
|
146
|
+
# Note: inside a watch() we get one and only one multi()
|
147
|
+
now = Time.now.to_i
|
148
|
+
stale = false
|
149
|
+
with_watch( *full_keys ) do
|
150
|
+
|
151
|
+
owners_expires = Zermelo.redis.mget(full_keys)
|
152
|
+
|
153
|
+
if owners_expires.each_slice(2).all? {|owner, expire| is_deletable?( owner, expire, now)}
|
154
|
+
result = Zermelo.redis.multi do |multi|
|
155
|
+
multi.del(*full_keys)
|
156
|
+
end
|
157
|
+
# If anything changed then multi() fails and returns nil
|
158
|
+
if result && (result.size == 1) && (result.first == owner_keys.size)
|
159
|
+
# log :info, "Deleted stale key from #{owner}"
|
160
|
+
stale = true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end # watch
|
164
|
+
stale
|
165
|
+
end
|
166
|
+
|
167
|
+
def locked?
|
168
|
+
now = Time.now.to_i
|
169
|
+
owners_expires = Zermelo.redis.mget(full_keys)
|
170
|
+
owners_expires && (owners_expires.size == (@keys.size * 2)) &&
|
171
|
+
owners_expires.each_slice(2).all? {|owner, expiration| is_locked?(owner, expiration, now)}
|
172
|
+
end
|
173
|
+
|
174
|
+
# returns true if the lock exists and is owned by the given owner
|
175
|
+
def is_locked?(owner, expiration, now)
|
176
|
+
(owner == @owner_value) && ! is_deletable?(owner, expiration, now)
|
177
|
+
end
|
178
|
+
|
179
|
+
# returns true if this is a broken or expired lock
|
180
|
+
def is_deletable?( owner, expiration, now)
|
181
|
+
expiration = expiration.to_i
|
182
|
+
(owner || (expiration > 0)) && (!owner || (expiration < now))
|
183
|
+
end
|
184
|
+
|
185
|
+
def with_watch( *args, &block )
|
186
|
+
Zermelo.redis.watch( *args )
|
187
|
+
begin
|
188
|
+
block.call
|
189
|
+
ensure
|
190
|
+
Zermelo.redis.unwatch
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Calls block until it returns true or times out.
|
195
|
+
# @param block should return true if successful, false otherwise
|
196
|
+
# @returns true if successful, false otherwise
|
197
|
+
def with_timeout( timeout, &block )
|
198
|
+
expire = Time.now + timeout.to_f
|
199
|
+
sleepy = @sleep_in_ms / 1000.to_f()
|
200
|
+
# this looks inelegant compared to while Time.now < expire, but does not oversleep
|
201
|
+
ret = nil
|
202
|
+
loop do
|
203
|
+
if block.call
|
204
|
+
ret = true
|
205
|
+
break
|
206
|
+
end
|
207
|
+
if (Time.now + sleepy) > expire
|
208
|
+
ret = false
|
209
|
+
break
|
210
|
+
end
|
211
|
+
sleep(sleepy)
|
212
|
+
# might like a different strategy, but general goal is not use 100% cpu while contending for a lock.
|
213
|
+
end
|
214
|
+
ret
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require 'zermelo'
|
5
|
+
|
6
|
+
require 'zermelo/backends/influxdb_backend'
|
7
|
+
require 'zermelo/backends/redis_backend'
|
8
|
+
|
9
|
+
require 'zermelo/records/key'
|
10
|
+
|
11
|
+
module Zermelo
|
12
|
+
|
13
|
+
module Records
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def_delegators :filter, :intersect, :union, :diff, :sort,
|
20
|
+
:find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
|
21
|
+
:page, :all, :each, :collect, :map,
|
22
|
+
:select, :find_all, :reject, :destroy_all,
|
23
|
+
:ids, :count, :empty?, :exists?,
|
24
|
+
:associated_ids_for
|
25
|
+
|
26
|
+
def generate_id
|
27
|
+
return SecureRandom.uuid if SecureRandom.respond_to?(:uuid)
|
28
|
+
# from 1.9 stdlib
|
29
|
+
ary = SecureRandom.random_bytes(16).unpack("NnnnnN")
|
30
|
+
ary[2] = (ary[2] & 0x0fff) | 0x4000
|
31
|
+
ary[3] = (ary[3] & 0x3fff) | 0x8000
|
32
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_id(id)
|
36
|
+
backend.add(ids_key, id.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_id(id)
|
40
|
+
backend.delete(ids_key, id.to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
def attribute_types
|
44
|
+
ret = nil
|
45
|
+
@lock.synchronize do
|
46
|
+
ret = (@attribute_types ||= {}).dup
|
47
|
+
end
|
48
|
+
ret
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock(*klasses, &block)
|
52
|
+
klasses += [self] unless klasses.include?(self)
|
53
|
+
backend.lock(*klasses, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def transaction(&block)
|
57
|
+
failed = false
|
58
|
+
|
59
|
+
backend.begin_transaction
|
60
|
+
|
61
|
+
begin
|
62
|
+
yield
|
63
|
+
rescue Exception => e
|
64
|
+
backend.abort_transaction
|
65
|
+
p e.message
|
66
|
+
puts e.backtrace.join("\n")
|
67
|
+
failed = true
|
68
|
+
ensure
|
69
|
+
backend.commit_transaction unless failed
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO include exception info
|
73
|
+
raise "Transaction failed" if failed
|
74
|
+
end
|
75
|
+
|
76
|
+
def backend
|
77
|
+
raise "No data storage backend set for #{self.name}" if @backend.nil?
|
78
|
+
@backend
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def define_attributes(options = {})
|
84
|
+
options.each_pair do |key, value|
|
85
|
+
raise "Unknown attribute type ':#{value}' for ':#{key}'" unless
|
86
|
+
Zermelo.valid_type?(value)
|
87
|
+
self.define_attribute_methods([key])
|
88
|
+
end
|
89
|
+
@lock.synchronize do
|
90
|
+
(@attribute_types ||= {}).update(options)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_backend(backend_type)
|
95
|
+
@backend ||= case backend_type.to_sym
|
96
|
+
when :influxdb
|
97
|
+
Zermelo::Backends::InfluxDBBackend.new
|
98
|
+
when :redis
|
99
|
+
Zermelo::Backends::RedisBackend.new
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def ids_key
|
106
|
+
@ids_key ||= Zermelo::Records::Key.new(:klass => class_key, :name => 'ids',
|
107
|
+
:type => :set, :object => :attribute)
|
108
|
+
end
|
109
|
+
|
110
|
+
def class_key
|
111
|
+
self.name.demodulize.underscore
|
112
|
+
end
|
113
|
+
|
114
|
+
def load(id)
|
115
|
+
object = self.new
|
116
|
+
object.load(id) ? object : nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def filter
|
120
|
+
backend.filter(ids_key, self)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Zermelo
|
2
|
+
module Records
|
3
|
+
# high-level abstraction for a set or list of record ids
|
4
|
+
class Collection
|
5
|
+
attr_reader :klass, :name, :type
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@klass = opts[:class]
|
9
|
+
@name = opts[:name]
|
10
|
+
@type = opts[:type]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Zermelo
|
2
|
+
module Records
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
class RecordNotFound < RuntimeError
|
6
|
+
attr_reader :klass, :id
|
7
|
+
|
8
|
+
def initialize(k, i)
|
9
|
+
@klass = k
|
10
|
+
@id = i
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RecordsNotFound < RuntimeError
|
15
|
+
attr_reader :klass, :ids
|
16
|
+
|
17
|
+
def initialize(k, i)
|
18
|
+
@klass = k
|
19
|
+
@ids = i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
require 'zermelo/records/base'
|
4
|
+
|
5
|
+
# a record is a row in a time series (named for the record class)
|
6
|
+
|
7
|
+
# all attributes are stored as fields in that row
|
8
|
+
|
9
|
+
# a save will delete (if required) and create the row
|
10
|
+
|
11
|
+
# if time field does not exist, this will be created automatically by influxdb
|
12
|
+
|
13
|
+
# indexing -- not really relevant until query building has been worked on, but
|
14
|
+
# everything in the influxdb query language should be supportable, maybe those
|
15
|
+
# just indicate what should be queryable?
|
16
|
+
|
17
|
+
# TODO: ensure time_precision is set for the incoming data
|
18
|
+
|
19
|
+
# class level values are in other time series (with similar names to the
|
20
|
+
# related redis sets)
|
21
|
+
|
22
|
+
module Zermelo
|
23
|
+
module Records
|
24
|
+
module InfluxDBRecord
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
include Zermelo::Records::Base
|
28
|
+
|
29
|
+
included do
|
30
|
+
set_backend :influxdb
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|