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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -0,0 +1,247 @@
1
+ require 'forwardable'
2
+
3
+ module Zermelo
4
+ module Associations
5
+ class Multiple
6
+
7
+ extend Forwardable
8
+
9
+ def_delegators :filter, :intersect, :union, :diff, :sort,
10
+ :find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
11
+ :page, :all, :each, :collect, :map,
12
+ :select, :find_all, :reject, :destroy_all,
13
+ :ids, :count, :empty?, :exists?,
14
+ :associated_ids_for
15
+
16
+ def initialize(type, parent_klass, parent_id, name)
17
+ @type = type
18
+ @parent_klass = parent_klass
19
+ @parent_id = parent_id
20
+ @name = name
21
+
22
+ @backend = parent_klass.send(:backend)
23
+
24
+ @key_type = case @type
25
+ when :has_many, :has_and_belongs_to_many
26
+ :set
27
+ when :has_sorted_set
28
+ :sorted_set
29
+ end
30
+
31
+ @record_ids_key = Zermelo::Records::Key.new(
32
+ :klass => parent_klass,
33
+ :id => parent_id,
34
+ :name => "#{name}_ids",
35
+ :type => @key_type,
36
+ :object => :association
37
+ )
38
+
39
+ parent_klass.send(:with_association_data, name.to_sym) do |data|
40
+ @associated_class = data.data_klass
41
+ @lock_klasses = [data.data_klass] + data.related_klasses
42
+ @inverse = data.inverse
43
+ @sort_key = data.sort_key
44
+ @sort_order = data.sort_order
45
+ @callbacks = data.callbacks
46
+ end
47
+
48
+ raise ':inverse_of must be set' if @inverse.nil?
49
+ end
50
+
51
+ def first
52
+ # FIXME raise error unless :has_sorted_set.eql?(@type)
53
+ filter.first
54
+ end
55
+
56
+ def last
57
+ # FIXME raise error unless :has_sorted_set.eql?(@type)
58
+ filter.last
59
+ end
60
+
61
+ def <<(record)
62
+ add(record)
63
+ self # for << 'a' << 'b'
64
+ end
65
+
66
+ def add(*records)
67
+ raise 'No records to add' if records.empty?
68
+ raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
69
+ raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
70
+ @parent_klass.lock(*@lock_klasses) do
71
+ record_ids = case @type
72
+ when :has_many, :has_and_belongs_to_many
73
+ records.is_a?(Zermelo::Filter) ? records.ids : records.map(&:id)
74
+ when :has_sorted_set
75
+ records.map {|r| [r.send(@sort_key.to_sym).to_f, r.id]}
76
+ end
77
+ _add_ids({:callbacks => true}, *record_ids)
78
+ end
79
+ end
80
+
81
+ def add_ids(*record_ids)
82
+ raise 'No record ids to add' if record_ids.empty?
83
+ @parent_klass.lock(*@lock_klasses) do
84
+ records = @associated_class.find_by_ids!(*record_ids)
85
+ _add_ids({:callbacks => true}, *record_ids)
86
+ end
87
+ end
88
+
89
+ # TODO support dependent delete, for now just removes the association
90
+ def remove(*records)
91
+ raise 'No records to remove' if records.empty?
92
+ raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
93
+ raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
94
+ @parent_klass.lock(*@lock_klasses) do
95
+ _remove_ids({:callbacks => true},
96
+ *(records.is_a?(Zermelo::Filter) ? records.ids : records.map(&:id)))
97
+ end
98
+ end
99
+
100
+ def remove_ids(*record_ids)
101
+ raise 'No record ids to remove' if record_ids.empty?
102
+ @parent_klass.lock(*@lock_klasses) do
103
+ _remove_ids({:callbacks => true}, *record_ids)
104
+ end
105
+ end
106
+
107
+ def clear
108
+ @parent_klass.lock(*@lock_klasses) do
109
+ _remove_ids({:callbacks => true}, *filter.ids) unless filter.empty?
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def _inverse
116
+ return @inverse_obj unless @inverse_obj.nil?
117
+ @associated_class.send(:with_association_data, @inverse.to_sym) do |data|
118
+ @inverse_obj = case @type
119
+ when :has_many, :has_sorted_set
120
+ # inverse is belongs_to
121
+ # FIXME neater to do multiple hash keys at once, if backends support it
122
+ Zermelo::Records::Key.new(
123
+ :klass => @associated_class,
124
+ :name => 'belongs_to',
125
+ :type => :hash,
126
+ :object => :association
127
+ )
128
+ when :has_and_belongs_to_many
129
+ Zermelo::Records::Key.new(
130
+ :klass => @associated_class,
131
+ :name => "#{@inverse}_ids",
132
+ :type => :set,
133
+ :object => :association
134
+ )
135
+ end
136
+ end
137
+ @inverse_obj
138
+ end
139
+
140
+ # associated will be a belongs_to; on remove already runs inside a lock and transaction
141
+ def on_remove
142
+ _remove_ids({:callbacks => false}, *filter.ids) unless filter.empty?
143
+ end
144
+
145
+ def _add_ids(opts = {}, *record_ids)
146
+ ba = @callbacks[:before_add]
147
+ if ba.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(ba) ||
148
+ !@parent_klass.send(ba, @parent_id, *record_ids).is_a?(FalseClass)
149
+
150
+ new_txn = @backend.begin_transaction
151
+
152
+ # FIXME neater to do multiple hash keys at once for inverse, if backends support it
153
+ case @type
154
+ when :has_many
155
+ # inverse is belongs_to
156
+ record_ids.each do |record_id|
157
+ _inverse.id = record_id
158
+ @backend.add(_inverse, "#{@inverse}_id" => @parent_id)
159
+ end
160
+ when :has_sorted_set
161
+ # inverse is belongs_to
162
+ record_ids.each do |(score, record_id)|
163
+ _inverse.id = record_id
164
+ @backend.add(_inverse, "#{@inverse}_id" => @parent_id)
165
+ end
166
+ when :has_and_belongs_to_many
167
+ # inverse is has_and_belongs_to_many
168
+ record_ids.each do |record_id|
169
+ _inverse.id = record_id
170
+ @backend.add(_inverse, @parent_id)
171
+ end
172
+ end
173
+
174
+ @backend.add(@record_ids_key, record_ids)
175
+
176
+ @backend.commit_transaction if new_txn
177
+ aa = @callbacks[:after_add]
178
+ if !aa.nil? && opts[:callbacks] && @parent_klass.respond_to?(aa)
179
+ @parent_klass.send(aa, @parent_id, *record_ids)
180
+ end
181
+ end
182
+ end
183
+
184
+ def _remove_ids(opts = {}, *record_ids)
185
+ br = @callbacks[:before_remove]
186
+ if br.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(br) ||
187
+ !@parent_klass.send(br, @parent_id, *record_ids).is_a?(FalseClass)
188
+
189
+ new_txn = @backend.begin_transaction
190
+
191
+ # FIXME neater to do multiple hash keys at once for inverse, if backends support it
192
+ case @type
193
+ when :has_many, :has_sorted_set
194
+ # inverse is belongs_to
195
+ record_ids.each do |record_id|
196
+ _inverse.id = record_id
197
+ @backend.delete(_inverse, "#{@inverse}_id")
198
+ end
199
+ when :has_and_belongs_to_many
200
+ # inverse is has_and_belongs_to_many
201
+ record_ids.each do |record_id|
202
+ _inverse.id = record_id
203
+ @backend.delete(_inverse, @parent_id)
204
+ end
205
+ end
206
+
207
+ @backend.delete(@record_ids_key, record_ids)
208
+
209
+ @backend.commit_transaction if new_txn
210
+
211
+ ar = @callbacks[:after_remove]
212
+ if !ar.nil? && opts[:callbacks] && @parent_klass.respond_to?(ar)
213
+ @parent_klass.send(ar, @parent_id, *record_ids)
214
+ end
215
+ end
216
+ end
217
+
218
+ # creates a new filter class each time it's called, to store the
219
+ # state for this particular filter chain
220
+ def filter
221
+ @backend.filter(@record_ids_key, @associated_class, @parent_klass,
222
+ @parent_id, @callbacks, @sort_order)
223
+ end
224
+
225
+ def self.associated_ids_for(backend, type, klass, name, *these_ids)
226
+ key_type = case type
227
+ when :has_many, :has_and_belongs_to_many
228
+ :set
229
+ when :has_sorted_set
230
+ :sorted_set
231
+ end
232
+
233
+ these_ids.each_with_object({}) do |this_id, memo|
234
+ key = Zermelo::Records::Key.new(
235
+ :klass => klass,
236
+ :id => this_id,
237
+ :name => "#{name}_ids",
238
+ :type => key_type,
239
+ :object => :association
240
+ )
241
+ memo[this_id] = backend.get(key)
242
+ end
243
+ end
244
+
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,44 @@
1
+ # NB index instances are all internal to zermelo, not user-accessible
2
+
3
+ require 'zermelo/records/key'
4
+
5
+ module Zermelo
6
+ module Associations
7
+ class RangeIndex
8
+
9
+ def initialize(parent_klass, name)
10
+ @parent_klass = parent_klass
11
+ @attribute_name = name
12
+
13
+ @backend = parent_klass.send(:backend)
14
+
15
+ parent_klass.send(:with_index_data, name.to_sym) do |data|
16
+ @attribute_type = data.type
17
+ end
18
+ end
19
+
20
+ def delete_id(id, value)
21
+ @backend.delete(key, id)
22
+ end
23
+
24
+ def add_id(id, value)
25
+ @backend.add(key, [@backend.safe_value(@attribute_type, value), id])
26
+ end
27
+
28
+ def move_id(id, value_from, indexer_to, value_to)
29
+ @backend.move(key, [@backend.safe_value(@attribute_type, value_from), id],
30
+ indexer_to.key, [@backend.safe_value(@attribute_type, value_to), id])
31
+ end
32
+
33
+ def key
34
+ @indexer ||= Zermelo::Records::Key.new(
35
+ :klass => @parent_klass,
36
+ :name => "by_#{@attribute_name}",
37
+ :type => :sorted_set,
38
+ :object => :index
39
+ )
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,193 @@
1
+ module Zermelo
2
+ module Associations
3
+ class Singular
4
+
5
+ def initialize(type, parent_klass, parent_id, name)
6
+ @type = type
7
+ @parent_klass = parent_klass
8
+ @parent_id = parent_id
9
+ @name = name
10
+
11
+ @backend = parent_klass.send(:backend)
12
+
13
+ @record_id_key = Zermelo::Records::Key.new(
14
+ :klass => parent_klass,
15
+ :id => parent_id,
16
+ :name => type.to_s,
17
+ :type => :hash,
18
+ :object => :association
19
+ )
20
+
21
+ parent_klass.send(:with_association_data, name.to_sym) do |data|
22
+ @associated_class = data.data_klass
23
+ @lock_klasses = [data.data_klass] + data.related_klasses
24
+ @inverse = data.inverse
25
+ @callbacks = data.callbacks
26
+ end
27
+
28
+ raise ':inverse_of must be set' if @inverse.nil?
29
+ @inverse_key = "#{@inverse}_id"
30
+ end
31
+
32
+ def value=(record)
33
+ if record.nil?
34
+ @parent_klass.lock(*@lock_klasses) do
35
+ _clear(:callbacks => true)
36
+ end
37
+ else
38
+ raise 'Invalid record class' unless record.is_a?(@associated_class)
39
+ raise 'Record must have been saved' unless record.persisted?
40
+ @parent_klass.lock(*@lock_klasses) do
41
+ opts = {:callbacks => true}
42
+ if :sorted_set.eql?(_inverse.type)
43
+ opts[:score] = @parent_klass.find_by_id!(@parent_id).send(@inverse_sort_key.to_sym).to_f
44
+ end
45
+ _set(opts, record.id)
46
+ end
47
+ end
48
+ end
49
+
50
+ def value
51
+ v = nil
52
+ @parent_klass.lock(*@lock_klasses) do
53
+ br = @callbacks[:before_read]
54
+ @parent_klass.send(br, @parent_id) if !br.nil? && @parent_klass.respond_to?(br)
55
+ id = @backend.get(@record_id_key)["#{@name}_id"]
56
+ # # TODO maybe: uses hgetall, need separate getter for hash/list/set
57
+ # @backend.get_hash_value(@record_id_key, "#{@name}_id")
58
+ v = @associated_class.send(:load, id) unless id.nil?
59
+ ar = @callbacks[:after_read]
60
+ @parent_klass.send(ar, @parent_id, v) if !ar.nil? && @parent_klass.respond_to?(ar)
61
+ end
62
+ v
63
+ end
64
+
65
+ private
66
+
67
+ # on_remove already runs inside a lock & transaction
68
+ def on_remove
69
+ _clear(:callbacks => false)
70
+ end
71
+
72
+ def _inverse
73
+ return @inverse_obj unless @inverse_obj.nil?
74
+ @associated_class.send(:with_association_data, @inverse.to_sym) do |data|
75
+ @inverse_obj = case @type
76
+ when :belongs_to
77
+ key_name, key_type = case data.data_type
78
+ when :has_many
79
+ ["#{@inverse}_ids", :set]
80
+ when :has_sorted_set
81
+ ["#{@inverse}_ids", :sorted_set]
82
+ when :has_one
83
+ ["has_one", :hash]
84
+ end
85
+
86
+ @inverse_sort_key = data.sort_key
87
+
88
+ Zermelo::Records::Key.new(
89
+ :klass => @associated_class,
90
+ :name => key_name,
91
+ :type => key_type,
92
+ :object => :association
93
+ )
94
+ when :has_one
95
+ # inverse is belongs_to
96
+ Zermelo::Records::Key.new(
97
+ :klass => @associated_class,
98
+ :name => 'belongs_to',
99
+ :type => :hash,
100
+ :object => :association
101
+ )
102
+ end
103
+ end
104
+ @inverse_obj
105
+ end
106
+
107
+ def _clear(opts = {})
108
+ bc = @callbacks[:before_clear]
109
+ if bc.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(bc) ||
110
+ !@parent_klass.send(bc, @parent_id).is_a?(FalseClass)
111
+
112
+ record_id = @backend.get(@record_id_key)["#{@name}_id"]
113
+ _inverse.id = record_id
114
+
115
+ new_txn = @backend.begin_transaction
116
+
117
+ case @type
118
+ when :belongs_to, :has_one
119
+ # FIXME can we access the assoc type instead?
120
+ case _inverse.type
121
+ when :set, :sorted_set
122
+ @backend.delete(_inverse, @parent_id)
123
+ when :hash
124
+ @backend.delete(_inverse, "#{@inverse}_id")
125
+ end
126
+
127
+ @backend.delete(@record_id_key, "#{@name}_id")
128
+ end
129
+
130
+ @backend.commit_transaction if new_txn
131
+
132
+ ac = @callbacks[:after_clear]
133
+ if !ac.nil? && opts[:callbacks] && @parent_klass.respond_to?(ac)
134
+ @parent_klass.send(ac, @parent_id, record_id)
135
+ end
136
+ end
137
+ end
138
+
139
+ def _set(opts = {}, record_id)
140
+ bs = @callbacks[:before_set]
141
+ if bs.nil? || !opts[:callbacks] || !@parent_klass.respond_to?(bs) ||
142
+ !@parent_klass.send(bs, @parent_id, record_id).is_a?(FalseClass)
143
+
144
+ _inverse.id = record_id
145
+
146
+ new_txn = @backend.begin_transaction
147
+
148
+ # FIXME can we access the assoc type instead?
149
+ case _inverse.type
150
+ when :set
151
+ @backend.add(_inverse, @parent_id)
152
+ when :sorted_set
153
+ @backend.add(_inverse, [opts[:score], @parent_id])
154
+ when :hash
155
+ @backend.add(_inverse, @inverse_key => @parent_id)
156
+ end
157
+
158
+ @backend.add(@record_id_key, "#{@name}_id" => record_id)
159
+
160
+ @backend.commit_transaction if new_txn
161
+
162
+ as = @callbacks[:after_set]
163
+ if !as.nil? && opts[:callbacks] && @parent_klass.respond_to?(as)
164
+ @parent_klass.send(as, @parent_id, record_id)
165
+ end
166
+ end
167
+ end
168
+
169
+ def self.associated_ids_for(backend, type, klass, name, inversed, *these_ids)
170
+ these_ids.each_with_object({}) do |this_id, memo|
171
+ key = Zermelo::Records::Key.new(
172
+ :klass => klass,
173
+ :id => this_id,
174
+ :name => type.to_s,
175
+ :type => :hash,
176
+ :object => :association
177
+ )
178
+
179
+ assoc_id = backend.get(key)["#{name}_id"]
180
+ # assoc_id = backend.get_hash_value(key, "#{name}_id")
181
+
182
+ if inversed
183
+ memo[assoc_id] ||= []
184
+ memo[assoc_id] << this_id
185
+ else
186
+ memo[this_id] = assoc_id
187
+ end
188
+ end
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -1,5 +1,7 @@
1
1
  # NB index instances are all internal to zermelo, not user-accessible
2
2
 
3
+ require 'zermelo/records/key'
4
+
3
5
  module Zermelo
4
6
  module Associations
5
7
  class UniqueIndex
@@ -10,8 +12,6 @@ module Zermelo
10
12
 
11
13
  @backend = parent_klass.send(:backend)
12
14
 
13
- @indexers = {}
14
-
15
15
  parent_klass.send(:with_index_data, name.to_sym) do |data|
16
16
  @attribute_type = data.type
17
17
  end
@@ -26,7 +26,8 @@ module Zermelo
26
26
  end
27
27
 
28
28
  def move_id(id, value_from, indexer_to, value_to)
29
- @backend.move(key, {@backend.index_keys(@attribute_type, value_to).join(':') => id}, indexer_to.key)
29
+ @backend.move(key, {@backend.index_keys(@attribute_type, value_from).join(':') => id},
30
+ indexer_to.key, {@backend.index_keys(@attribute_type, value_to).join(':') => id})
30
31
  end
31
32
 
32
33
  def key
@@ -0,0 +1,120 @@
1
+ require 'active_support/concern'
2
+
3
+ require 'zermelo/locks/no_lock'
4
+
5
+ module Zermelo
6
+
7
+ module Backend
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ def escape_key_name(name)
12
+ name.gsub(/%/, '%%').gsub(/ /, '%20').gsub(/:/, '%3A')
13
+ end
14
+
15
+ def unescape_key_name(name)
16
+ name.gsub(/%3A/, ':').gsub(/%20/, ' ').gsub(/%%/, '%')
17
+ end
18
+
19
+ def safe_value(type, value)
20
+ case type
21
+ when :string, :integer
22
+ value.to_s
23
+ when :float, :timestamp
24
+ value.to_f
25
+ when :boolean
26
+ (!!value).to_s
27
+ end
28
+ end
29
+
30
+ def index_keys(type, value)
31
+ return ["null", "null"] if value.nil?
32
+
33
+ case type
34
+ when :string
35
+ ["string", escape_key_name(value)]
36
+ when :integer
37
+ ["integer", escape_key_name(value.to_s)]
38
+ when :float
39
+ ["float", escape_key_name(value.to_s)]
40
+ when :timestamp
41
+ case value
42
+ when Integer
43
+ ["timestamp", escape_key_name(value.to_s)]
44
+ when Time, DateTime
45
+ ["timestamp", escape_key_name(value.to_f.to_s)]
46
+ end
47
+ when :boolean
48
+ case value
49
+ when TrueClass
50
+ ["boolean", "true"]
51
+ when FalseClass
52
+ ["boolean", "false"]
53
+ end
54
+ end
55
+ end
56
+
57
+ # for hashes, lists, sets
58
+ def add(key, value)
59
+ change(:add, key, value)
60
+ end
61
+
62
+ def delete(key, value)
63
+ change(:delete, key, value)
64
+ end
65
+
66
+ def move(key_from, value_from, key_to, value_to)
67
+ change(:move, key_from, value_from, key_to, value_to)
68
+ end
69
+
70
+ def clear(key)
71
+ change(:clear, key)
72
+ end
73
+
74
+ # works for both simple and complex types (i.e. strings, numbers, booleans,
75
+ # hashes, lists, sets)
76
+ def set(key, value)
77
+ change(:set, key, value)
78
+ end
79
+
80
+ def purge(key)
81
+ change(:purge, key)
82
+ end
83
+
84
+ def get(attr_key)
85
+ get_multiple(attr_key)[attr_key.klass.send(:class_key)][attr_key.id][attr_key.name.to_s]
86
+ end
87
+
88
+ def lock(*klasses, &block)
89
+ ret = nil
90
+ # doesn't handle re-entrant case for influxdb, which has no locking yet
91
+ locking = Thread.current[:zermelo_locking]
92
+ if locking.nil?
93
+ lock_proc = proc do
94
+ begin
95
+ Thread.current[:zermelo_locking] = klasses
96
+ ret = block.call
97
+ ensure
98
+ Thread.current[:zermelo_locking] = nil
99
+ end
100
+ end
101
+
102
+ lock_klass = case self
103
+ when Zermelo::Backends::Redis
104
+ Zermelo::Locks::RedisLock
105
+ else
106
+ Zermelo::Locks::NoLock
107
+ end
108
+
109
+ lock_klass.new.lock(*klasses, &lock_proc)
110
+ else
111
+ # accepts any subset of 'locking'
112
+ unless (klasses - locking).empty?
113
+ raise "Currently locking #{locking.map(&:name)}, cannot lock different set #{klasses.map(&:name)}"
114
+ end
115
+ ret = block.call
116
+ end
117
+ ret
118
+ end
119
+ end
120
+ end