frivol 0.3.1 → 0.4.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.
@@ -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