frivol 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb5b81ba6e03198c9bd7e33e3ce843dd3a0d48df
4
- data.tar.gz: 1e2456b6e27f97eec9309fc7e638c498bc166f5a
3
+ metadata.gz: 49b60bcea96f41ca6dfd54dc9b3b4c63db50c144
4
+ data.tar.gz: e47b8d3203ca595433192dbe55673fff5d2ad13e
5
5
  SHA512:
6
- metadata.gz: 741f725ccee1f4fd43e5edde0d2de85500511c84c547dbc1af85baa2a8eff8cbc9498809d38a7c60e28d6d22eb86a43bfe42e0db74bd8e6087c1cfa8f7f4e3da
7
- data.tar.gz: 0d1cc7ddeaffc023ccb1449a28e1ce9b9f536667d2fea6c3f7f2e5ed96663162905dc009114adef5dcb7ea38cb2cc8a2b8594f1e95ba37261efc7e0383397a73
6
+ metadata.gz: b4ebac79ca4b09b7685580b630ad763c75766b9daaf70b624c85d856bbcb44282ace07cc6ec2f890fb3c6519bac2ab4116b36580d910e9c2d128365ff9313801
7
+ data.tar.gz: e523e418a679e1b81e0645314e482e592f01f23fa89db470ec52ef382f394043ea8afe355195771bdf657b8aead9d2b10381296c00674915c4294d2e40f07c41
@@ -2,14 +2,15 @@ language: ruby
2
2
  bundler_args: --without development
3
3
  before_install:
4
4
  - 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
5
+ services:
6
+ - redis-server
7
+ - riak
5
8
  rvm:
6
- - 1.8.7
7
- - 1.9.2
8
9
  - 1.9.3
9
10
  - 2.0.0
10
- # - 2.1.0
11
- - jruby-18mode
12
- - jruby-19mode
11
+ - 2.1.0
12
+ - 2.2.0
13
13
  - ruby-head
14
+ - jruby-19mode
14
15
  - jruby-head
15
- - ree
16
+ script: bundle exec rake test:all
@@ -0,0 +1,15 @@
1
+ <!--- TODO: Date and version -->
2
+ # March 2014 (v)
3
+ <!--- TODO: Check on this version number -->
4
+ - Drop support for Redis < 2.2.6
5
+ - Add backends for Redis, Redis::Distributed and Riak
6
+ - FIXED: counters never expire if created with increment or decrement methods
7
+
8
+ # Previous
9
+ <!--- TODO: Fill in -->
10
+
11
+
12
+ # TODO/BUGS(?):
13
+ - Hook AR reload method to clear_storage?
14
+ - Add a way (maybe using MR) to expire keys in Riak
15
+ - BUG: Now, counters always have their expiry reset
data/Gemfile CHANGED
@@ -1,13 +1,16 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'multi_json'
4
- gem 'redis'
5
4
  gem 'rake'
6
5
 
7
6
  group :development do
8
7
  gem 'jeweler'
8
+ gem 'pry'
9
+ gem 'pry-debugger'
9
10
  end
10
11
 
11
12
  group :test do
12
13
  gem 'test-unit'
14
+ gem 'redis'
15
+ gem 'riak-client'
13
16
  end
@@ -10,14 +10,27 @@ when a hot system is restarted. Frivol's design is such that it solves our probl
10
10
  believe it is generic enough to be used in many Rails web projects and even in other types of
11
11
  projects altogether.
12
12
 
13
+ As of version 0.4.0, Frivol supports various backends, including Redis::Destributed and Riak.
14
+ There's a Multi backend which will migrate keys from an old backend to a new one, like from
15
+ Redis to Redis::Destributed.
16
+
13
17
  == Usage
18
+
19
+ === Configuration
14
20
  Configure Frivol in your configuration, for example in an initializer or in environment.rb
15
21
  REDIS_CONFIG = {
16
22
  :host => "localhost",
17
23
  :port => 6379
18
24
  }
19
- Frivol::Config.redis_config = REDIS_CONFIG
25
+ Frivol::Config.backend = Frivol::Backend::Redis.new(REDIS_CONFIG)
26
+
27
+ **Note:** This configuration has changed in version 0.4.0 with the introduction of <tt>Backend</tt>s
28
+
29
+ You can also use RedisDestributed or Riak backends. These are configured similarly. Finally there is a
30
+ Multi which works to migrate data from an old backend to a new one. For example, we used a Multi backend
31
+ to migrate from a single Redis instance to a RedisDistributed pair.
20
32
 
33
+ === Standard Usage
21
34
  Now include Frivol in whichever classes you'd like to make use of temporary storage. You can optionally
22
35
  call the <tt>storage_expires_in(time)</tt> class method to set a default expiry. In your methods you can
23
36
  now call the <tt>store(keys_and_values)</tt> and <tt>retrieve(keys_and_defaults)</tt> methods.
@@ -29,6 +42,33 @@ The default is not to expire the storage, in which case it will live for as long
29
42
  <tt>delete_storage</tt>, as the name suggests will immediately delete the storage, while <tt>clear_storage</tt>
30
43
  will clear the cache that Frivol keeps and force the next <tt>retrieve</tt> to return to Redis for the data.
31
44
 
45
+ ==== Example
46
+ class BigComplexCalcer
47
+ include Frivol
48
+ storage_expires_in 600 # temporary storage expires in 10 minutes.
49
+ def initialize(key)
50
+ @key = key
51
+ end
52
+ def storage_key(bucket = nil)
53
+ "frivol-test-#{key}" # override the storage key because we don't respond_to? :id, and don't care about buckets
54
+ end
55
+ def big_complex_calc
56
+ retrieve :complex => :do_big_complex_calc # do_big_complex_calc is the method to get the default from
57
+ end
58
+ def last_calc_done
59
+ last = retrieve(:last => nil) # default is nil
60
+ return "never" if last.nil?
61
+ return "#{Time.now - Time.at(last)} seconds ago"
62
+ end
63
+ def do_big_complex_calc
64
+ # Wee! Do some really hard work here...
65
+ # ...still working...
66
+ store :complex => result, :last => Time.now.to_i # ...and let's keep the result for at least 10 minutes, as well as the last timme we did it
67
+ end
68
+ end
69
+
70
+
71
+ === Buckets
32
72
  Since version 0.1.5 Frivol can create different storage buckets. Note that this introduces a breaking change
33
73
  to the <tt>storage_key</tt> method if you have overriden it. It now takes a +bucket+ parameter.
34
74
 
@@ -42,6 +82,7 @@ exactly like the standard +store+ and +retrieve+ methods. There will also be <tt
42
82
  take a integer (value and default, respectively) and the increment does not take a parameter. Since version 0.2.1
43
83
  there is also <tt>increment_my_counter_by</tt>, <tt>decrement_my_counter</tt> and <tt>decrement_my_counter_by<tt>.
44
84
 
85
+ === Conditional retrieval
45
86
  Fine grained control of storing and retrieving values from buckets can be controlled using the :condition and
46
87
  :else options.
47
88
  storage_bucket :my_bucket,
@@ -61,30 +102,21 @@ Frivol uses the +storage_key+ method to create a base key for storage in Redis.
61
102
  <tt>"#{self.class.name}-#{id}"</tt> so you'll want to override that method if you have classes that don't
62
103
  respond to id.
63
104
 
64
- == Example
65
- class BigComplexCalcer
66
- include Frivol
67
- storage_expires_in 600 # temporary storage expires in 10 minutes.
68
- def initialize(key)
69
- @key = key
70
- end
71
- def storage_key(bucket = nil)
72
- "frivol-test-#{key}" # override the storage key because we don't respond_to? :id, and don't care about buckets
73
- end
74
- def big_complex_calc
75
- retrieve :complex => :do_big_complex_calc # do_big_complex_calc is the method to get the default from
76
- end
77
- def last_calc_done
78
- last = retrieve(:last => nil) # default is nil
79
- return "never" if last.nil?
80
- return "#{Time.now - Time.at(last)} seconds ago"
81
- end
82
- def do_big_complex_calc
83
- # Wee! Do some really hard work here...
84
- # ...still working...
85
- store :complex => result, :last => Time.now.to_i # ...and let's keep the result for at least 10 minutes, as well as the last me we did it
86
- end
105
+ === Frivolize
106
+ The +frivolize+ method is similar to +memoize+ except that it uses the backend to store the data which can thus be
107
+ shared amongst multiple processes.
108
+
109
+ def long_running_result
110
+ # do lots of hard work
111
+ end
112
+ frivolize :long_running_result
113
+
114
+ The frivolize also is also able to take options to make counters and expire.
115
+
116
+ def long_running_count
117
+ # count something that takes a long time
87
118
  end
119
+ frivolize :long_running_count, counter: true, expires_in: 10.minutes
88
120
 
89
121
  == Time Extensions
90
122
  These extensions allow the storing and retrieving of <tt>Time</tt> and
data/Rakefile CHANGED
@@ -24,6 +24,23 @@ Rake::TestTask.new(:test) do |test|
24
24
  test.verbose = true
25
25
  end
26
26
 
27
+ namespace :test do
28
+ desc 'Test all backends and the Multi backend in all combinations'
29
+ task :all do
30
+ %w[
31
+ fake_redis redis redis_distributed riak multi
32
+ multi_redis_redis multi_redis_redis_distributed
33
+ multi_redis_riak multi_redis_distributed_riak
34
+ multi_riak_redis multi_riak_redis_distributed
35
+ ].each do |backend|
36
+ puts "Testing #{backend}"
37
+ ENV['backend'] = backend
38
+ Rake::Task["test"].reenable
39
+ Rake::Task["test"].invoke
40
+ end
41
+ end
42
+ end
43
+
27
44
  task :default => :test
28
45
 
29
46
  begin
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: frivol 0.3.1 ruby lib
5
+ # stub: frivol 0.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "frivol"
9
- s.version = "0.3.1"
9
+ s.version = "0.4.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Marc Heiligers"]
14
- s.date = "2014-03-06"
14
+ s.date = "2015-06-28"
15
15
  s.description = "Simple Redis backed temporary storage intended primarily for use with ActiveRecord models to provide caching"
16
16
  s.email = "marc@eternal.co.za"
17
17
  s.extra_rdoc_files = [
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.files = [
22
22
  ".document",
23
23
  ".travis.yml",
24
+ "CHANGES.md",
24
25
  "Gemfile",
25
26
  "LICENSE",
26
27
  "README.rdoc",
@@ -53,6 +54,10 @@ Gem::Specification.new do |s|
53
54
  "doc/rdoc-style.css",
54
55
  "frivol.gemspec",
55
56
  "lib/frivol.rb",
57
+ "lib/frivol/backend/multi.rb",
58
+ "lib/frivol/backend/redis.rb",
59
+ "lib/frivol/backend/redis_distributed.rb",
60
+ "lib/frivol/backend/riak.rb",
56
61
  "lib/frivol/class_methods.rb",
57
62
  "lib/frivol/config.rb",
58
63
  "lib/frivol/functor.rb",
@@ -60,6 +65,7 @@ Gem::Specification.new do |s|
60
65
  "lib/frivol/time_extensions.rb",
61
66
  "test/fake_redis.rb",
62
67
  "test/helper.rb",
68
+ "test/test_backend.rb",
63
69
  "test/test_buckets.rb",
64
70
  "test/test_condition.rb",
65
71
  "test/test_condition_with_counters.rb",
@@ -68,6 +74,10 @@ Gem::Specification.new do |s|
68
74
  "test/test_extensions.rb",
69
75
  "test/test_frivol.rb",
70
76
  "test/test_frivolize.rb",
77
+ "test/test_multi_backend.rb",
78
+ "test/test_multi_backend_counters.rb",
79
+ "test/test_multi_backend_expiry.rb",
80
+ "test/test_riak.rb",
71
81
  "test/test_seeds.rb",
72
82
  "test/test_threads.rb"
73
83
  ]
@@ -80,20 +90,23 @@ Gem::Specification.new do |s|
80
90
 
81
91
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
82
92
  s.add_runtime_dependency(%q<multi_json>, [">= 0"])
83
- s.add_runtime_dependency(%q<redis>, [">= 0"])
84
93
  s.add_runtime_dependency(%q<rake>, [">= 0"])
85
94
  s.add_development_dependency(%q<jeweler>, [">= 0"])
95
+ s.add_development_dependency(%q<pry>, [">= 0"])
96
+ s.add_development_dependency(%q<pry-debugger>, [">= 0"])
86
97
  else
87
98
  s.add_dependency(%q<multi_json>, [">= 0"])
88
- s.add_dependency(%q<redis>, [">= 0"])
89
99
  s.add_dependency(%q<rake>, [">= 0"])
90
100
  s.add_dependency(%q<jeweler>, [">= 0"])
101
+ s.add_dependency(%q<pry>, [">= 0"])
102
+ s.add_dependency(%q<pry-debugger>, [">= 0"])
91
103
  end
92
104
  else
93
105
  s.add_dependency(%q<multi_json>, [">= 0"])
94
- s.add_dependency(%q<redis>, [">= 0"])
95
106
  s.add_dependency(%q<rake>, [">= 0"])
96
107
  s.add_dependency(%q<jeweler>, [">= 0"])
108
+ s.add_dependency(%q<pry>, [">= 0"])
109
+ s.add_dependency(%q<pry-debugger>, [">= 0"])
97
110
  end
98
111
  end
99
112
 
@@ -1,5 +1,3 @@
1
- require "redis"
2
-
3
1
  # == Frivol
4
2
  module Frivol
5
3
  require "frivol/config"
@@ -56,7 +54,7 @@ module Frivol
56
54
  # Expire the stored data in +time+ seconds.
57
55
  def expire_storage(time, bucket = nil)
58
56
  return if time.nil?
59
- Frivol::Config.redis.expire storage_key(bucket), time
57
+ Frivol::Config.backend.expire storage_key(bucket), time
60
58
  end
61
59
 
62
60
  # The base key used for storage in Redis.
@@ -0,0 +1,159 @@
1
+ module Frivol
2
+ module Backend
3
+ # == Configuration
4
+ # This backend is used to migrate from one backend to another (and another) (and another).
5
+ # Essentially, this backend will check for the existence of the key in the newest backend,
6
+ # and if not found check in the older backends in order. If it's found in an older backend
7
+ # it will move the value to the new backend.
8
+ #
9
+ # I have used this in production to move from Redis to RedisDistributed.
10
+ # old_backend = Frivol::Backend::Redis.new(:db => 10)
11
+ # new_backend = Frivol::Backend::RedisDistributed.new(["redis://127.0.0.1:6379/11", "redis://127.0.0.1:6379/12"])
12
+ # Frivol::Config.backend = Frivol::Backend::Multi.new([ new_backend, old_backend ])
13
+ class Multi
14
+ # :nodoc:
15
+ BackendsNotUniqueError = Class.new(StandardError)
16
+
17
+ def initialize(backends)
18
+ unless backends.map(&:inspect).uniq.size == backends.size
19
+ raise BackendsNotUniqueError, "Backends are not unique: #{backends.map(&:inspect).join(', ')}"
20
+ end
21
+ @primary_backend = backends.shift
22
+ @other_backends = backends
23
+ end
24
+
25
+ # Hashes
26
+ def get(key, expiry = Frivol::NEVER_EXPIRE)
27
+ val = @primary_backend.get(key)
28
+ val = migrate(key, expiry) if val.nil?
29
+ val
30
+ end
31
+
32
+ def set(key, val, expiry = Frivol::NEVER_EXPIRE)
33
+ @other_backends.each { |be| be.del(key) }
34
+ @primary_backend.set(key, val, expiry)
35
+ end
36
+
37
+ def del(key)
38
+ @primary_backend.del(key)
39
+ @other_backends.each { |be| be.del(key) }
40
+ end
41
+
42
+ def exists(key)
43
+ @primary_backend.exists(key) ||
44
+ @other_backends.detect { |be| be.exists(key) }
45
+ end
46
+
47
+ # Counters
48
+ def getc(key, expiry = Frivol::NEVER_EXPIRE)
49
+ val = @primary_backend.getc(key)
50
+ val = migratec(key, :getc, 0, expiry) if val.nil?
51
+ val
52
+ end
53
+
54
+ def setc(key, val, expiry = Frivol::NEVER_EXPIRE)
55
+ @other_backends.each { |be| be.delc(key) }
56
+ @primary_backend.setc(key, val, expiry)
57
+ end
58
+
59
+ def delc(key)
60
+ @primary_backend.delc(key)
61
+ @other_backends.each { |be| be.delc(key) }
62
+ end
63
+
64
+ def existsc(key)
65
+ @primary_backend.existsc(key) ||
66
+ @other_backends.detect { |be| be.existsc(key) }
67
+ end
68
+
69
+ def incr(key, expiry = Frivol::NEVER_EXPIRE)
70
+ if @primary_backend.existsc(key)
71
+ @primary_backend.incr(key)
72
+ else
73
+ migratec(key, :incrby, 1, expiry)
74
+ end
75
+ end
76
+
77
+ def decr(key, expiry = Frivol::NEVER_EXPIRE)
78
+ if @primary_backend.existsc(key)
79
+ @primary_backend.decr(key)
80
+ else
81
+ migratec(key, :decrby, 1, expiry)
82
+ end
83
+ end
84
+
85
+ def incrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
86
+ if @primary_backend.existsc(key)
87
+ @primary_backend.incrby(key, amt)
88
+ else
89
+ migratec(key, :incrby, amt, expiry)
90
+ end
91
+ end
92
+
93
+ def decrby(key, amt, expiry = Frivol::NEVER_EXPIRE)
94
+ if @primary_backend.existsc(key)
95
+ @primary_backend.decrby(key, amt)
96
+ else
97
+ migratec(key, :decrby, amt, expiry)
98
+ end
99
+ end
100
+
101
+ # Expiry/TTL
102
+ def expire(key, ttl)
103
+ @primary_backend.expire(key, ttl)
104
+ end
105
+
106
+ def ttl(key)
107
+ expiry = @primary_backend.ttl(key)
108
+ if expiry.nil?
109
+ @other_backends.each do |be|
110
+ expiry = be.ttl(key)
111
+ return expiry unless expiry.nil?
112
+ end
113
+ nil
114
+ else
115
+ expiry
116
+ end
117
+ end
118
+
119
+ # Connection
120
+ def flushdb
121
+ @primary_backend.flushdb
122
+ @other_backends.each { |be| be.flushdb }
123
+ end
124
+
125
+ def connection
126
+ @primary_backend.connection
127
+ end
128
+
129
+ # Migration
130
+ def migrate(key, expiry = Frivol::NEVER_EXPIRE)
131
+ backend = @other_backends.detect { |be| be.exists(key) }
132
+ if backend
133
+ val = backend.get(key)
134
+ ttl = backend.ttl(key)
135
+ @primary_backend.set(key, val, ttl)
136
+ @other_backends.each { |be| be.del key }
137
+ val
138
+ end
139
+ end
140
+
141
+ def migratec(key, method = :incrby, amt = 0, expiry = Frivol::NEVER_EXPIRE)
142
+ backend = @other_backends.detect { |be| be.existsc(key) }
143
+ if backend
144
+ val = backend.getc(key).to_i
145
+ ttl = backend.ttl(key)
146
+ @primary_backend.incrby(key, val) unless val.zero?
147
+ val = @primary_backend.send(method, key, amt) unless amt.zero? || method == :getc
148
+ @primary_backend.expire(key, ttl) if ttl
149
+ @other_backends.each { |be| be.delc key }
150
+ val
151
+ elsif method != :getc
152
+ @primary_backend.send(method, key, amt, expiry)
153
+ else
154
+ nil
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end