zermelo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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