frivol 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ require "redis"
2
+
3
+ module Frivol
4
+ module Backend
5
+ # == Configuration
6
+ # This is a connection to a single Redis server and the simplest backend.
7
+ # REDIS_CONFIG = {
8
+ # :host => "localhost",
9
+ # :port => 6379
10
+ # }
11
+ # Frivol::Config.backend = Frivol::Backend::Redis.new(REDIS_CONFIG)
12
+ class Redis
13
+ # :nodoc:
14
+ def initialize(config)
15
+ @config = config
16
+ end
17
+
18
+ # Hashes
19
+ def get(key, expiry = Frivol::NEVER_EXPIRE)
20
+ connection.get(key)
21
+ end
22
+ alias_method :getc, :get # Counter method alias
23
+
24
+ def set(key, val, expiry = Frivol::NEVER_EXPIRE)
25
+ set_with_expiry(key, val, expiry)
26
+ end
27
+ alias_method :setc, :set # Counter method alias
28
+
29
+ def del(key)
30
+ connection.del(key)
31
+ end
32
+ alias_method :delc, :del # Counter method alias
33
+
34
+ def exists(key)
35
+ connection.exists(key)
36
+ end
37
+ alias_method :existsc, :exists # Counter method alias
38
+
39
+ # Counters
40
+ def incr(key, expiry = Frivol::NEVER_EXPIRE)
41
+ set_with_expiry(key, 1, expiry, :incrby)
42
+ end
43
+
44
+ def decr(key, expiry = Frivol::NEVER_EXPIRE)
45
+ set_with_expiry(key, 1, expiry, :decrby)
46
+ end
47
+
48
+ def incrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
49
+ set_with_expiry(key, amt, expiry, :incrby)
50
+ end
51
+
52
+ def decrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
53
+ set_with_expiry(key, amt, expiry, :decrby)
54
+ end
55
+
56
+ # Expiry/TTL
57
+ def expire(key, ttl)
58
+ connection.expire(key, ttl)
59
+ end
60
+
61
+ def ttl(key)
62
+ time = connection.ttl(key)
63
+ time < 0 ? nil : time
64
+ end
65
+
66
+ # Connection
67
+ def flushdb
68
+ connection.flushdb
69
+ end
70
+
71
+ def connection
72
+ Thread.current[thread_key] ||= ::Redis.new(@config)
73
+ end
74
+
75
+ private
76
+ def thread_key
77
+ @thread_key ||= @config.hash.to_s.to_sym
78
+ end
79
+
80
+ def set_with_expiry(key, val, expiry, method = :set)
81
+ if expiry == Frivol::NEVER_EXPIRE
82
+ connection.send(method, key, val)
83
+ else
84
+ result = connection.multi do |redis|
85
+ redis.send(method, key, val)
86
+ redis.expire(key, expiry)
87
+ end
88
+ result[0]
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,51 @@
1
+ require "redis"
2
+ require "redis/distributed"
3
+ require File.join(File.dirname(__FILE__), "redis")
4
+
5
+ module Frivol
6
+ module Backend
7
+ # == Configuration
8
+ # While it's not well known or documented, the Redis gem includes a sharding implementation in
9
+ # Redis::Distributed[http://www.rubydoc.info/gems/redis/3.2.1/Redis/Distributed]. This backend
10
+ # makes that available for Frivol.
11
+ # REDIS_CONFIG = [{
12
+ # :host => "localhost",
13
+ # :port => 6379
14
+ # }, {
15
+ # :host => "localhost",
16
+ # :port => 6380
17
+ # }]
18
+ # Frivol::Config.backend = Frivol::Backend::RedisDistributed.new(REDIS_CONFIG)
19
+ class RedisDistributed < Frivol::Backend::Redis
20
+ # :nodoc:
21
+ def set(key, val, expiry = Frivol::NEVER_EXPIRE)
22
+ if expiry == Frivol::NEVER_EXPIRE
23
+ connection.set(key, val)
24
+ else
25
+ connection.node_for(key).multi do |redis|
26
+ redis.set(key, val)
27
+ redis.expire(key, expiry)
28
+ end
29
+ end
30
+ end
31
+ alias_method :setc, :set # Counter method alias
32
+
33
+ def connection
34
+ Thread.current[thread_key] ||= ::Redis::Distributed.new(@config)
35
+ end
36
+
37
+ private
38
+ def set_with_expiry(key, val, expiry, method = :set)
39
+ if expiry == Frivol::NEVER_EXPIRE
40
+ connection.send(method, key, val)
41
+ else
42
+ results = connection.node_for(key).multi do |redis|
43
+ redis.send(method, key, val)
44
+ redis.expire(key, expiry)
45
+ end
46
+ results[0]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,186 @@
1
+ require "riak"
2
+
3
+ module Frivol #:nodoc:
4
+ module Backend
5
+ # == Configuration
6
+ # This backend is experimental. I have not used this in production. YMMV.
7
+ # RIAK_CONFIG = {
8
+ # :protocol => 'http',
9
+ # :nodes => [ { :host => '127.0.0.1' } ]
10
+ # }
11
+ # Frivol::Config.backend = Frivol::Backend::Riak.new(RIAK_CONFIG)
12
+ class Riak
13
+ # :nodoc:
14
+ def initialize(config)
15
+ @prefix = config.delete(:prefix) || ''
16
+ @config = config
17
+ end
18
+
19
+ # Hashes
20
+ def get(key, expiry = Frivol::NEVER_EXPIRE)
21
+ obj = objects_bucket.get_or_new(key)
22
+ expires_in = ttl(key)
23
+ if expires_in.nil? || expires_in > 0
24
+ obj.raw_data
25
+ else
26
+ obj.delete
27
+ nil
28
+ end
29
+ end
30
+
31
+ def set(key, val, expiry = Frivol::NEVER_EXPIRE)
32
+ obj = objects_bucket.get_or_new(key)
33
+ obj.raw_data = val
34
+ obj.content_type = 'text/plain'
35
+ obj.store
36
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
37
+ end
38
+
39
+ def del(key)
40
+ objects_bucket.delete(key)
41
+ expires_bucket.delete(key)
42
+ end
43
+
44
+ def exists(key)
45
+ exists = objects_bucket.exist?(key)
46
+ if exists
47
+ expires_in = ttl(key)
48
+ if expires_in.nil? || expires_in > 0
49
+ true
50
+ else
51
+ objects_bucket.delete(key)
52
+ false
53
+ end
54
+ end
55
+ end
56
+
57
+ # Counters
58
+ def getc(key, expiry = Frivol::NEVER_EXPIRE)
59
+ cnt = counters_bucket.counter(key) if existsc(key)
60
+ expires_in = ttl(key)
61
+ if expires_in.nil? || expires_in > 0
62
+ cnt ? cnt.value : nil
63
+ else
64
+ delc(key)
65
+ nil
66
+ end
67
+ end
68
+
69
+ def setc(key, val, expiry = Frivol::NEVER_EXPIRE)
70
+ delc(key)
71
+ cnt = counters_bucket.counter(key)
72
+ cnt.increment(val)
73
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
74
+ end
75
+
76
+ def delc(key)
77
+ counters_bucket.delete(key)
78
+ expires_bucket.delete(key)
79
+ end
80
+
81
+ def existsc(key)
82
+ exists = counters_bucket.exist?(key)
83
+ if exists
84
+ expires_in = ttl(key)
85
+ if expires_in.nil? || expires_in > 0
86
+ true
87
+ else
88
+ counters_bucket.delete(key)
89
+ false
90
+ end
91
+ end
92
+ end
93
+
94
+ def incr(key, expiry = Frivol::NEVER_EXPIRE)
95
+ cnt = counters_bucket.counter(key)
96
+ cnt.increment
97
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
98
+ cnt.value
99
+ end
100
+
101
+ def decr(key, expiry = Frivol::NEVER_EXPIRE)
102
+ cnt = counters_bucket.counter(key)
103
+ cnt.decrement
104
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
105
+ cnt.value
106
+ end
107
+
108
+ def incrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
109
+ cnt = counters_bucket.counter(key)
110
+ cnt.increment(amt)
111
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
112
+ cnt.value
113
+ end
114
+
115
+ def decrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
116
+ cnt = counters_bucket.counter(key)
117
+ cnt.decrement(amt)
118
+ expire(key, expiry) unless expiry == Frivol::NEVER_EXPIRE
119
+ cnt.value
120
+ end
121
+
122
+ # Expiry/TTL
123
+ def expire(key, ttl)
124
+ obj = expires_bucket.get_or_new(key)
125
+ obj.raw_data = (Time.now.to_i + ttl).to_s
126
+ obj.content_type = 'text/plain'
127
+ obj.store
128
+ end
129
+
130
+ def ttl(key)
131
+ obj = expires_bucket.get_or_new(key)
132
+ expiry = obj.raw_data.to_i
133
+ if expiry == 0
134
+ nil
135
+ else
136
+ expiry - Time.now.to_i
137
+ end
138
+ end
139
+
140
+ # Connection
141
+ def flushdb
142
+ objects_bucket.keys.each { |k| objects_bucket.delete(k) }
143
+ counters_bucket.keys.each { |k| counters_bucket.delete(k) }
144
+ expires_bucket.keys.each { |k| expires_bucket.delete(k) }
145
+ end
146
+
147
+ def connection
148
+ Thread.current[thread_key] ||= begin
149
+ @objects_bucket = nil
150
+ @counters_bucket = nil
151
+ @expires_bucket = nil
152
+ ::Riak::Client.new(@config)
153
+ end
154
+ end
155
+
156
+ private
157
+ def objects_bucket
158
+ @objects_bucket ||= begin
159
+ bkt = connection.bucket("#{@prefix}frivol_objects")
160
+ bkt.props = { :last_write_wins => true }
161
+ bkt
162
+ end
163
+ end
164
+
165
+ def counters_bucket
166
+ @counters_bucket ||= begin
167
+ bkt = connection.bucket("#{@prefix}frivol_counters")
168
+ bkt.allow_mult = true
169
+ bkt
170
+ end
171
+ end
172
+
173
+ def expires_bucket
174
+ @expires_bucket ||= begin
175
+ bkt = connection.bucket("#{@prefix}frivol_expires")
176
+ bkt.props = { :last_write_wins => true }
177
+ bkt
178
+ end
179
+ end
180
+
181
+ def thread_key
182
+ @thread_key ||= @config.hash.to_s.to_sym
183
+ end
184
+ end
185
+ end
186
+ end
@@ -92,6 +92,12 @@ module Frivol
92
92
  define_method "decrement_#{bucket}_by" do |amount|
93
93
  Frivol::Helpers.decrement_counter_by(self, bucket, amount, seed_callback)
94
94
  end
95
+
96
+ define_method "delete_#{bucket}" do
97
+ condition_evaluation("delete_#{bucket}") do
98
+ Frivol::Helpers.delete_counter(self, bucket)
99
+ end
100
+ end
95
101
  else
96
102
  define_method "store_#{bucket}" do |keys_and_values|
97
103
  condition_evaluation("store_#{bucket}", keys_and_values) do
@@ -115,11 +121,11 @@ module Frivol
115
121
  return result.first if result.size == 1
116
122
  result
117
123
  end
118
- end
119
124
 
120
- define_method "delete_#{bucket}" do
121
- condition_evaluation("delete_#{bucket}") do
122
- Frivol::Helpers.delete_hash(self, bucket)
125
+ define_method "delete_#{bucket}" do
126
+ condition_evaluation("delete_#{bucket}") do
127
+ Frivol::Helpers.delete_hash(self, bucket)
128
+ end
123
129
  end
124
130
  end
125
131
 
@@ -3,31 +3,17 @@ module Frivol
3
3
  # Sets the Frivol configuration (currently only the Redis config), allows access to the configured Redis instance,
4
4
  # and has a helper method to include Frivol in a class with an optional storage expiry parameter
5
5
  module Config
6
- # Set the Redis configuration.
6
+ # Set the Backend.
7
7
  #
8
- # Expects a hash such as
9
- # REDIS_CONFIG = {
10
- # :host => "localhost",
11
- # :port => 6379
12
- # }
13
- # Frivol::Config.redis_config = REDIS_CONFIG
14
- def self.redis_config=(config)
15
- @@redis_config = config
16
- @@redis_version = nil
17
- Thread.current[:frivol_redis] = nil
18
- end
19
-
20
- # Returns the configured Redis instance
21
- def self.redis
22
- Thread.current[:frivol_redis] ||= Redis.new(@@redis_config)
23
- end
24
-
25
- def self.redis_version
26
- redis.info('server')['redis_version'].to_f
8
+ # Expects one of Frivol::Backend::Redis, Frivol::Backend::RedisDistributed,
9
+ # Frivol::Backend::Riak or Frivol::Backend::Multi
10
+ def self.backend=(new_backend)
11
+ @@backend = new_backend
27
12
  end
28
13
 
29
- def self.requires_expiry_reset?
30
- redis_version < 2.2
14
+ # Get the Backend.
15
+ def self.backend
16
+ @@backend
31
17
  end
32
18
 
33
19
  def self.allow_json_create
@@ -29,41 +29,23 @@ module Frivol
29
29
  data, is_new = get_data_and_is_new instance
30
30
  data[bucket.to_s] = hash
31
31
 
32
- store_value instance, is_new[bucket.to_s], dump_json(hash), bucket
33
-
34
- self.set_data_and_is_new instance, data, is_new
35
- end
36
-
37
- def self.store_value(instance, is_new, value, bucket = nil)
38
32
  key = instance.send(:storage_key, bucket)
39
33
  time = instance.class.storage_expiry(bucket)
40
- if time == Frivol::NEVER_EXPIRE
41
- Frivol::Config.redis[key] = value
42
- elsif Frivol::Config.requires_expiry_reset?
43
- time = redis.ttl(key).to_i unless is_new
44
- Frivol::Config.redis.multi do |redis|
45
- redis[key] = value
46
- redis.expire(key, time)
47
- end
48
- elsif is_new
49
- Frivol::Config.redis.multi do |redis|
50
- redis[key] = value
51
- redis.expire(key, time)
52
- end
53
- else
54
- Frivol::Config.redis[key] = value
55
- end
34
+ Frivol::Config.backend.set(key, dump_json(hash), is_new ? time : nil)
35
+
36
+ self.set_data_and_is_new instance, data, is_new
56
37
  end
57
38
 
58
39
  def self.retrieve_hash(instance, bucket = nil)
59
40
  data, is_new = get_data_and_is_new instance
60
41
  return data[bucket.to_s] if data.key?(bucket.to_s)
61
42
  key = instance.send(:storage_key, bucket)
62
- json = Frivol::Config.redis[key]
43
+ time = instance.class.storage_expiry(bucket)
44
+ json = Frivol::Config.backend.get(key, time).to_s
63
45
 
64
- is_new[bucket.to_s] = json.nil?
46
+ is_new[bucket.to_s] = json.empty?
65
47
 
66
- hash = json.nil? ? {} : load_json(json)
48
+ hash = json.empty? ? {} : load_json(json)
67
49
  data[bucket.to_s] = hash
68
50
 
69
51
  self.set_data_and_is_new instance, data, is_new
@@ -72,7 +54,7 @@ module Frivol
72
54
 
73
55
  def self.delete_hash(instance, bucket = nil)
74
56
  key = instance.send(:storage_key, bucket)
75
- Frivol::Config.redis.del key
57
+ Frivol::Config.backend.del key
76
58
  clear_hash(instance, bucket)
77
59
  end
78
60
 
@@ -96,41 +78,57 @@ module Frivol
96
78
 
97
79
  def self.store_counter(instance, counter, value)
98
80
  key = instance.send(:storage_key, counter)
99
- is_new = !Frivol::Config.redis.exists(key)
100
- store_value instance, is_new, value, counter
81
+ exists = Frivol::Config.backend.existsc(key)
82
+ time = instance.class.storage_expiry(counter)
83
+ Frivol::Config.backend.setc(key, value, exists ? nil : time)
101
84
  end
102
85
 
103
86
  def self.retrieve_counter(instance, counter, default)
104
87
  key = instance.send(:storage_key, counter)
105
- (Frivol::Config.redis[key] || default).to_i
88
+ time = instance.class.storage_expiry(counter)
89
+ (Frivol::Config.backend.getc(key, time) || default).to_i
106
90
  end
107
91
 
108
92
  def self.increment_counter(instance, counter, seed_callback=nil)
109
93
  key = instance.send(:storage_key, counter)
110
94
  store_counter_seed_value(key, instance, counter, seed_callback)
111
- Frivol::Config.redis.incr(key)
95
+ exists = Frivol::Config.backend.existsc(key)
96
+ time = instance.class.storage_expiry(counter)
97
+ Frivol::Config.backend.incr(key, exists ? nil : time)
112
98
  end
113
99
 
114
100
  def self.increment_counter_by(instance, counter, amount, seed_callback=nil)
115
101
  key = instance.send(:storage_key, counter)
116
102
  store_counter_seed_value(key, instance, counter, seed_callback)
117
- Frivol::Config.redis.incrby(key, amount)
103
+ exists = Frivol::Config.backend.existsc(key)
104
+ time = instance.class.storage_expiry(counter)
105
+ Frivol::Config.backend.incrby(key, amount, exists ? nil : time)
118
106
  end
119
107
 
120
108
  def self.decrement_counter(instance, counter, seed_callback=nil)
121
109
  key = instance.send(:storage_key, counter)
122
110
  store_counter_seed_value(key, instance, counter, seed_callback)
123
- Frivol::Config.redis.decr(key)
111
+ exists = Frivol::Config.backend.existsc(key)
112
+ time = instance.class.storage_expiry(counter)
113
+ Frivol::Config.backend.decr(key, exists ? nil : time)
124
114
  end
125
115
 
126
116
  def self.decrement_counter_by(instance, counter, amount, seed_callback=nil)
127
117
  key = instance.send(:storage_key, counter)
128
118
  store_counter_seed_value(key, instance, counter, seed_callback)
129
- Frivol::Config.redis.decrby(key, amount)
119
+ exists = Frivol::Config.backend.existsc(key)
120
+ time = instance.class.storage_expiry(counter)
121
+ Frivol::Config.backend.decrby(key, amount, exists ? nil : time)
122
+ end
123
+
124
+ def self.delete_counter(instance, counter = nil)
125
+ key = instance.send(:storage_key, counter)
126
+ Frivol::Config.backend.delc key
127
+ clear_hash(instance, counter)
130
128
  end
131
129
 
132
130
  def self.store_counter_seed_value(key, instance, counter, seed_callback)
133
- unless Frivol::Config.redis.exists(key) || seed_callback.nil?
131
+ unless Frivol::Config.backend.existsc(key) || seed_callback.nil?
134
132
  store_counter( instance, counter, seed_callback.call(instance))
135
133
  end
136
134
  end