frivol 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +15 -0
- data/Gemfile +13 -0
- data/LICENSE +1 -1
- data/README.rdoc +58 -38
- data/Rakefile +16 -26
- data/VERSION +1 -1
- data/frivol.gemspec +74 -62
- data/lib/frivol.rb +14 -363
- data/lib/frivol/class_methods.rb +195 -0
- data/lib/frivol/config.rb +39 -0
- data/lib/frivol/functor.rb +37 -0
- data/lib/frivol/helpers.rb +133 -0
- data/lib/frivol/time_extensions.rb +45 -0
- data/test/fake_redis.rb +36 -11
- data/test/helper.rb +16 -11
- data/test/test_buckets.rb +53 -0
- data/test/test_condition.rb +43 -0
- data/test/test_condition_with_counters.rb +90 -0
- data/test/test_counters.rb +72 -0
- data/test/test_else_with_counters.rb +39 -0
- data/test/test_extensions.rb +15 -0
- data/test/test_frivol.rb +96 -388
- data/test/test_frivolize.rb +81 -0
- data/test/test_seeds.rb +53 -0
- data/test/test_threads.rb +15 -0
- metadata +88 -88
- data/.gitignore +0 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a24e083670901a1e0114ec42a4da90f666ef23f
|
4
|
+
data.tar.gz: ce84cd346fde8137c233c18feb0e13858c1c5f41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5bfd4d1406e56a839615cca1577f31fc900d4a8f23e2ca788bbcf0d2b5ea6507fc63f635a8db9de734a8a81083973ec0d8c1670338e0b66fd6c62805765b27e
|
7
|
+
data.tar.gz: f641d253e43ae8fe48ddb2bde13940289423bf877302c61d72b6d8b490feedbb1d84b2e3dc794dd03bcc0f85046979c3ee54e362cbb92390cb6c457bad5244fd
|
data/.travis.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
language: ruby
|
2
|
+
bundler_args: --without development
|
3
|
+
before_install:
|
4
|
+
- 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
|
5
|
+
rvm:
|
6
|
+
- 1.8.7
|
7
|
+
- 1.9.2
|
8
|
+
- 1.9.3
|
9
|
+
- 2.0.0
|
10
|
+
# - 2.1.0
|
11
|
+
- jruby-18mode
|
12
|
+
- jruby-19mode
|
13
|
+
- ruby-head
|
14
|
+
- jruby-head
|
15
|
+
- ree
|
data/Gemfile
ADDED
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,80 +1,100 @@
|
|
1
1
|
= Frivol - Frivolously simple temporary storage backed by Redis
|
2
2
|
A really simple Redis-backed temporary storage mechanism intended to be used with ActiveRecord,
|
3
|
-
but will work with other ORM's or any classes really.
|
4
|
-
|
5
|
-
|
6
|
-
of
|
7
|
-
with communication of status from background tasks running in Resque on workers to the front
|
3
|
+
but will work with other ORM's or any classes really.
|
4
|
+
I developed Frivol secifically for use in Mad Mimi (http://madmimi.com) to help with caching
|
5
|
+
of data which requires fairly long running (multi-second) database queries, and also to aid
|
6
|
+
with communication of status from background Resque jobs running on the workers to the front
|
8
7
|
end web servers. Redis was chosen because we already had Resque, which is Redis-backed. Also,
|
9
8
|
unlike memcached, Redis persists it's data to disk, meaning there is far less warmup required
|
10
|
-
when a hot system is restarted. Frivol's design is such that it solves our problem, but I
|
11
|
-
believe it is generic enough to be used in many Rails web projects and even in other types of
|
12
|
-
projects altogether.
|
9
|
+
when a hot system is restarted. Frivol's design is such that it solves our problem, but I
|
10
|
+
believe it is generic enough to be used in many Rails web projects and even in other types of
|
11
|
+
projects altogether.
|
13
12
|
|
14
13
|
== Usage
|
15
14
|
Configure Frivol in your configuration, for example in an initializer or in environment.rb
|
16
15
|
REDIS_CONFIG = {
|
17
|
-
:host => "localhost",
|
16
|
+
:host => "localhost",
|
18
17
|
:port => 6379
|
19
18
|
}
|
20
19
|
Frivol::Config.redis_config = REDIS_CONFIG
|
20
|
+
|
21
21
|
Now include Frivol in whichever classes you'd like to make use of temporary storage. You can optionally
|
22
|
-
call the <tt>storage_expires_in(time)</tt> class method to set a default expiry. In your methods you can
|
22
|
+
call the <tt>storage_expires_in(time)</tt> class method to set a default expiry. In your methods you can
|
23
23
|
now call the <tt>store(keys_and_values)</tt> and <tt>retrieve(keys_and_defaults)</tt> methods.
|
24
|
-
|
25
24
|
Defaults in the +retrieve+ method can be symbols, in which case Frivol will check if the class <tt>respond_to?</tt>
|
26
25
|
a method by that name to get the default.
|
27
26
|
|
28
|
-
The <tt>expire_storage(time)</tt> method can be used to set the expiry time in seconds of the temporary storage.
|
27
|
+
The <tt>expire_storage(time)</tt> method can be used to set the expiry time in seconds of the temporary storage.
|
29
28
|
The default is not to expire the storage, in which case it will live for as long as Redis keeps it.
|
29
|
+
<tt>delete_storage</tt>, as the name suggests will immediately delete the storage, while <tt>clear_storage</tt>
|
30
|
+
will clear the cache that Frivol keeps and force the next <tt>retrieve</tt> to return to Redis for the data.
|
31
|
+
|
32
|
+
Since version 0.1.5 Frivol can create different storage buckets. Note that this introduces a breaking change
|
33
|
+
to the <tt>storage_key</tt> method if you have overriden it. It now takes a +bucket+ parameter.
|
30
34
|
|
35
|
+
Buckets can have their own expiry time and there are special counter buckets which simply keep an integer count.
|
36
|
+
storage_bucket :my_bucket, :expires_in => 5.minutes
|
37
|
+
storage_bucket :my_counter, :counter => true
|
38
|
+
|
39
|
+
Given the above, Frivol will create <tt>store_my_bucket</tt> and <tt>retrieve_my_bucket</tt> methods which work
|
40
|
+
exactly like the standard +store+ and +retrieve+ methods. There will also be <tt>store_my_counter</tt>,
|
41
|
+
<tt>retrieve_my_counter</tt> and <tt>increment_my_counter</tt> methods. The counter store and retrieve only
|
42
|
+
take a integer (value and default, respectively) and the increment does not take a parameter. Since version 0.2.1
|
43
|
+
there is also <tt>increment_my_counter_by</tt>, <tt>decrement_my_counter</tt> and <tt>decrement_my_counter_by<tt>.
|
44
|
+
|
45
|
+
Fine grained control of storing and retrieving values from buckets can be controlled using the :condition and
|
46
|
+
:else options.
|
47
|
+
storage_bucket :my_bucket,
|
48
|
+
:condition => Proc.new{ |object, frivol_method, *args| ... },
|
49
|
+
:else => :your_method
|
50
|
+
For the above example, frivol execute the :condition proc and passes the instance of the current class, which
|
51
|
+
method is being attempted (increment, increment_by, store, retrieve, etc.) and any arguments that may have been
|
52
|
+
passed on to frivol.
|
53
|
+
If the condition returns a truthy result, the frivol method is executed unimpeded, otherwise frivol moves on to
|
54
|
+
:else. :else for the above example is a method on the instance, and that method must be able to receive the frivol
|
55
|
+
method used (as a string) and any arguments passed to that method:
|
56
|
+
def your_method(frivol_method, *args)
|
57
|
+
...
|
58
|
+
end
|
59
|
+
The :condition and :else options can be specified as a proc, symbol, true or false.
|
31
60
|
Frivol uses the +storage_key+ method to create a base key for storage in Redis. The current implementation uses
|
32
61
|
<tt>"#{self.class.name}-#{id}"</tt> so you'll want to override that method if you have classes that don't
|
33
|
-
respond to id.
|
62
|
+
respond to id.
|
34
63
|
|
35
|
-
Frivol also extends Time to allow it to be (de)serialized to JSON, which currently used to store
|
36
|
-
data in Redis.
|
37
64
|
== Example
|
38
65
|
class BigComplexCalcer
|
39
66
|
include Frivol
|
40
67
|
storage_expires_in 600 # temporary storage expires in 10 minutes.
|
41
|
-
|
42
68
|
def initialize(key)
|
43
69
|
@key = key
|
44
70
|
end
|
45
|
-
|
46
|
-
|
47
|
-
"frivol-test-#{key}" # override the storage key because we don't respond_to? id
|
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
|
48
73
|
end
|
49
|
-
|
50
74
|
def big_complex_calc
|
51
75
|
retrieve :complex => :do_big_complex_calc # do_big_complex_calc is the method to get the default from
|
52
76
|
end
|
53
|
-
|
54
77
|
def last_calc_done
|
55
|
-
last = retrieve
|
78
|
+
last = retrieve(:last => nil) # default is nil
|
56
79
|
return "never" if last.nil?
|
57
|
-
return "#{Time.now - last} seconds ago"
|
80
|
+
return "#{Time.now - Time.at(last)} seconds ago"
|
58
81
|
end
|
59
|
-
|
60
82
|
def do_big_complex_calc
|
61
83
|
# Wee! Do some really hard work here...
|
62
84
|
# ...still working...
|
63
|
-
store :complex => result, :last => Time.now # ...and let's keep the result for at least 10 minutes, as well as the last
|
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
|
64
86
|
end
|
65
87
|
end
|
66
88
|
|
89
|
+
== Time Extensions
|
90
|
+
These extensions allow the storing and retrieving of <tt>Time</tt> and
|
91
|
+
<tt>ActiveSupport::TimeWithZone</tt> objects in Frivol. We now recommend that
|
92
|
+
times are stored using <tt>#to_i</tt>, but the extensions are provided for
|
93
|
+
legacy Frivol users. In order to use them you will need to have:
|
94
|
+
require 'frivol/extensions'
|
95
|
+
You can also create your own extensions to save other complex objects in
|
96
|
+
Frivol. Your classes will need a <tt>#to_json(*a)</tt> to dump the object
|
97
|
+
and a <tt>.json_create(o)</tt> class method to load the object. You will need to
|
98
|
+
tell <tt>Frivol</tt> to allow the use of create extensions for your class with:
|
99
|
+
Frivol::Config.allow_json_create << Klass
|
67
100
|
|
68
|
-
== Note on Patches/Pull Requests
|
69
|
-
|
70
|
-
* Fork the project.
|
71
|
-
* Make your feature addition or bug fix.
|
72
|
-
* Add tests for it. This is important so I don't break it in a
|
73
|
-
future version unintentionally.
|
74
|
-
* Commit, do not mess with rakefile, version, or history.
|
75
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
76
|
-
* Send me a pull request. Bonus points for topic branches.
|
77
|
-
|
78
|
-
== Copyright
|
79
|
-
|
80
|
-
Copyright (c) 2010 Marc Heiligers. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -10,9 +10,6 @@ begin
|
|
10
10
|
gem.email = "marc@eternal.co.za"
|
11
11
|
gem.homepage = "http://github.com/marcheiligers/frivol"
|
12
12
|
gem.authors = ["Marc Heiligers"]
|
13
|
-
gem.add_dependency "json", ">= 1.2.0"
|
14
|
-
gem.add_dependency "redis", ">= 2.0.10"
|
15
|
-
gem.add_development_dependency "shoulda", ">= 2.11.1"
|
16
13
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
14
|
end
|
18
15
|
Jeweler::GemcutterTasks.new
|
@@ -22,34 +19,27 @@ end
|
|
22
19
|
|
23
20
|
require 'rake/testtask'
|
24
21
|
Rake::TestTask.new(:test) do |test|
|
25
|
-
test.libs << '
|
22
|
+
test.libs << 'test'
|
26
23
|
test.pattern = 'test/**/test_*.rb'
|
27
24
|
test.verbose = true
|
28
25
|
end
|
29
26
|
|
30
|
-
begin
|
31
|
-
require 'rcov/rcovtask'
|
32
|
-
Rcov::RcovTask.new do |test|
|
33
|
-
test.libs << 'test'
|
34
|
-
test.pattern = 'test/**/test_*.rb'
|
35
|
-
test.verbose = true
|
36
|
-
end
|
37
|
-
rescue LoadError
|
38
|
-
task :rcov do
|
39
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
task :test => :check_dependencies
|
44
|
-
|
45
27
|
task :default => :test
|
46
28
|
|
47
|
-
|
48
|
-
|
49
|
-
|
29
|
+
begin
|
30
|
+
require 'rdoc/task'
|
31
|
+
Rake::RDocTask.new do |rdoc|
|
32
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
33
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
34
|
+
rdoc.rdoc_dir = 'rdoc'
|
35
|
+
rdoc.title = "frivol #{version}"
|
36
|
+
rdoc.rdoc_files.include('README*')
|
37
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
38
|
+
end
|
39
|
+
rescue LoadError, RuntimeError => e
|
40
|
+
if e.is_a?(LoadError) || (e.is_a?(RuntimeError) && e.message.start_with?("ERROR: 'rake/rdoctask'"))
|
41
|
+
puts "RDocTask (or a dependency) not available. Maybe older Ruby?"
|
42
|
+
else
|
43
|
+
raise e
|
44
|
+
end
|
55
45
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/frivol.gemspec
CHANGED
@@ -1,87 +1,99 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: frivol 0.3.0 ruby lib
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
8
|
+
s.name = "frivol"
|
9
|
+
s.version = "0.3.0"
|
9
10
|
|
10
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
11
13
|
s.authors = ["Marc Heiligers"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
14
|
+
s.date = "2014-03-06"
|
15
|
+
s.description = "Simple Redis backed temporary storage intended primarily for use with ActiveRecord models to provide caching"
|
16
|
+
s.email = "marc@eternal.co.za"
|
15
17
|
s.extra_rdoc_files = [
|
16
18
|
"LICENSE",
|
17
|
-
|
19
|
+
"README.rdoc"
|
18
20
|
]
|
19
21
|
s.files = [
|
20
22
|
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
s.require_paths = ["lib"]
|
60
|
-
s.rubygems_version = %q{1.3.7}
|
61
|
-
s.summary = %q{Simple Redis backed temporary storage}
|
62
|
-
s.test_files = [
|
23
|
+
".travis.yml",
|
24
|
+
"Gemfile",
|
25
|
+
"LICENSE",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"doc/classes/Frivol.html",
|
30
|
+
"doc/classes/Frivol.src/M000003.html",
|
31
|
+
"doc/classes/Frivol.src/M000004.html",
|
32
|
+
"doc/classes/Frivol.src/M000005.html",
|
33
|
+
"doc/classes/Frivol.src/M000006.html",
|
34
|
+
"doc/classes/Frivol.src/M000007.html",
|
35
|
+
"doc/classes/Frivol.src/M000008.html",
|
36
|
+
"doc/classes/Frivol/ClassMethods.html",
|
37
|
+
"doc/classes/Frivol/ClassMethods.src/M000009.html",
|
38
|
+
"doc/classes/Frivol/ClassMethods.src/M000010.html",
|
39
|
+
"doc/classes/Frivol/ClassMethods.src/M000011.html",
|
40
|
+
"doc/classes/Frivol/Config.html",
|
41
|
+
"doc/classes/Frivol/Config.src/M000012.html",
|
42
|
+
"doc/classes/Frivol/Config.src/M000013.html",
|
43
|
+
"doc/classes/Frivol/Config.src/M000014.html",
|
44
|
+
"doc/classes/Time.html",
|
45
|
+
"doc/classes/Time.src/M000001.html",
|
46
|
+
"doc/classes/Time.src/M000002.html",
|
47
|
+
"doc/created.rid",
|
48
|
+
"doc/files/lib/frivol_rb.html",
|
49
|
+
"doc/fr_class_index.html",
|
50
|
+
"doc/fr_file_index.html",
|
51
|
+
"doc/fr_method_index.html",
|
52
|
+
"doc/index.html",
|
53
|
+
"doc/rdoc-style.css",
|
54
|
+
"frivol.gemspec",
|
55
|
+
"lib/frivol.rb",
|
56
|
+
"lib/frivol/class_methods.rb",
|
57
|
+
"lib/frivol/config.rb",
|
58
|
+
"lib/frivol/functor.rb",
|
59
|
+
"lib/frivol/helpers.rb",
|
60
|
+
"lib/frivol/time_extensions.rb",
|
63
61
|
"test/fake_redis.rb",
|
64
|
-
|
65
|
-
|
62
|
+
"test/helper.rb",
|
63
|
+
"test/test_buckets.rb",
|
64
|
+
"test/test_condition.rb",
|
65
|
+
"test/test_condition_with_counters.rb",
|
66
|
+
"test/test_counters.rb",
|
67
|
+
"test/test_else_with_counters.rb",
|
68
|
+
"test/test_extensions.rb",
|
69
|
+
"test/test_frivol.rb",
|
70
|
+
"test/test_frivolize.rb",
|
71
|
+
"test/test_seeds.rb",
|
72
|
+
"test/test_threads.rb"
|
66
73
|
]
|
74
|
+
s.homepage = "http://github.com/marcheiligers/frivol"
|
75
|
+
s.rubygems_version = "2.2.1"
|
76
|
+
s.summary = "Simple Redis backed temporary storage"
|
67
77
|
|
68
78
|
if s.respond_to? :specification_version then
|
69
|
-
|
70
|
-
s.specification_version = 3
|
79
|
+
s.specification_version = 4
|
71
80
|
|
72
81
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
73
|
-
s.add_runtime_dependency(%q<
|
74
|
-
s.add_runtime_dependency(%q<redis>, [">=
|
75
|
-
s.
|
82
|
+
s.add_runtime_dependency(%q<multi_json>, [">= 0"])
|
83
|
+
s.add_runtime_dependency(%q<redis>, [">= 0"])
|
84
|
+
s.add_runtime_dependency(%q<rake>, [">= 0"])
|
85
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
76
86
|
else
|
77
|
-
s.add_dependency(%q<
|
78
|
-
s.add_dependency(%q<redis>, [">=
|
79
|
-
s.add_dependency(%q<
|
87
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
88
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
89
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
90
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
80
91
|
end
|
81
92
|
else
|
82
|
-
s.add_dependency(%q<
|
83
|
-
s.add_dependency(%q<redis>, [">=
|
84
|
-
s.add_dependency(%q<
|
93
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
94
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
95
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
96
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
85
97
|
end
|
86
98
|
end
|
87
99
|
|
data/lib/frivol.rb
CHANGED
@@ -1,93 +1,15 @@
|
|
1
|
-
# = Frivol - Frivolously simple temporary storage backed by Redis
|
2
|
-
# A really simple Redis-backed temporary storage mechanism intended to be used with ActiveRecord,
|
3
|
-
# but will work with other ORM's or any classes really.
|
4
|
-
#
|
5
|
-
# I developed Frivol secifically for use in Mad Mimi (http://madmimi.com) to help with caching
|
6
|
-
# of data which requires fairly long running (multi-second) database queries, and also to aid
|
7
|
-
# with communication of status from background Resque jobs running on the workers to the front
|
8
|
-
# end web servers. Redis was chosen because we already had Resque, which is Redis-backed. Also,
|
9
|
-
# unlike memcached, Redis persists it's data to disk, meaning there is far less warmup required
|
10
|
-
# when a hot system is restarted. Frivol's design is such that it solves our problem, but I
|
11
|
-
# believe it is generic enough to be used in many Rails web projects and even in other types of
|
12
|
-
# projects altogether.
|
13
|
-
#
|
14
|
-
# == Usage
|
15
|
-
# Configure Frivol in your configuration, for example in an initializer or in environment.rb
|
16
|
-
# REDIS_CONFIG = {
|
17
|
-
# :host => "localhost",
|
18
|
-
# :port => 6379
|
19
|
-
# }
|
20
|
-
# Frivol::Config.redis_config = REDIS_CONFIG
|
21
|
-
# Now include Frivol in whichever classes you'd like to make use of temporary storage. You can optionally
|
22
|
-
# call the <tt>storage_expires_in(time)</tt> class method to set a default expiry. In your methods you can
|
23
|
-
# now call the <tt>store(keys_and_values)</tt> and <tt>retrieve(keys_and_defaults)</tt> methods.
|
24
|
-
#
|
25
|
-
# Defaults in the +retrieve+ method can be symbols, in which case Frivol will check if the class <tt>respond_to?</tt>
|
26
|
-
# a method by that name to get the default.
|
27
|
-
#
|
28
|
-
# The <tt>expire_storage(time)</tt> method can be used to set the expiry time in seconds of the temporary storage.
|
29
|
-
# The default is not to expire the storage, in which case it will live for as long as Redis keeps it.
|
30
|
-
# <tt>delete_storage</tt>, as the name suggests will immediately delete the storage, while <tt>clear_storage</tt>
|
31
|
-
# will clear the cache that Frivol keeps and force the next <tt>retrieve</tt> to return to Redis for the data.
|
32
|
-
#
|
33
|
-
# Since version 0.1.5 Frivol can create different storage buckets. Note that this introduces a breaking change
|
34
|
-
# to the <tt>storage_key</tt> method if you have overriden it. It now takes a +bucket+ parameter.
|
35
|
-
#
|
36
|
-
# Buckets can have their own expiry time and there are special counter buckets which simply keep an integer count.
|
37
|
-
#
|
38
|
-
# storage_bucket :my_bucket, :expires_in => 5.minutes
|
39
|
-
# storage_bucket :my_counter, :counter => true
|
40
|
-
#
|
41
|
-
# Given the above, Frivol will create <tt>store_my_bucket</tt> and <tt>retrieve_my_bucket</tt> methods which work
|
42
|
-
# exactly like the standard +store+ and +retrieve+ methods. There will also be <tt>store_my_counter</tt>,
|
43
|
-
# <tt>retrieve_my_counter</tt> and <tt>increment_my_counter</tt> methods. The counter store and retrieve only
|
44
|
-
# take a integer (value and default, respectively) and the increment does not take a parameter.
|
45
|
-
#
|
46
|
-
# These methods are thread safe if you pass <tt>:thread_safe => true</tt> to the Redis configuration.
|
47
|
-
#
|
48
|
-
# Frivol uses the +storage_key+ method to create a base key for storage in Redis. The current implementation uses
|
49
|
-
# <tt>"#{self.class.name}-#{id}"</tt> so you'll want to override that method if you have classes that don't
|
50
|
-
# respond to id.
|
51
|
-
#
|
52
|
-
# Frivol also extends Time to allow it to be (de)serialized to JSON, which currently used to store
|
53
|
-
# data in Redis.
|
54
|
-
# == Example
|
55
|
-
# class BigComplexCalcer
|
56
|
-
# include Frivol
|
57
|
-
# storage_expires_in 600 # temporary storage expires in 10 minutes.
|
58
|
-
#
|
59
|
-
# def initialize(key)
|
60
|
-
# @key = key
|
61
|
-
# end
|
62
|
-
#
|
63
|
-
# def storage_key(bucket = nil)
|
64
|
-
# "frivol-test-#{key}" # override the storage key because we don't respond_to? :id, and don't care about buckets
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# def big_complex_calc
|
68
|
-
# retrieve :complex => :do_big_complex_calc # do_big_complex_calc is the method to get the default from
|
69
|
-
# end
|
70
|
-
#
|
71
|
-
# def last_calc_done
|
72
|
-
# last = retrieve :last => nil # default is nil
|
73
|
-
# return "never" if last.nil?
|
74
|
-
# return "#{Time.now - last} seconds ago"
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# def do_big_complex_calc
|
78
|
-
# # Wee! Do some really hard work here...
|
79
|
-
# # ...still working...
|
80
|
-
# store :complex => result, :last => Time.now # ...and let's keep the result for at least 10 minutes, as well as the last time we did it
|
81
|
-
# end
|
82
|
-
# end
|
83
|
-
require "json"
|
84
1
|
require "redis"
|
85
2
|
|
86
3
|
# == Frivol
|
87
4
|
module Frivol
|
5
|
+
require "frivol/config"
|
6
|
+
require "frivol/functor"
|
7
|
+
require "frivol/helpers"
|
8
|
+
require "frivol/class_methods"
|
9
|
+
|
88
10
|
# Defines a constant to indicate that storage should never expire
|
89
11
|
NEVER_EXPIRE = nil
|
90
|
-
|
12
|
+
|
91
13
|
# Store a hash of keys and values.
|
92
14
|
#
|
93
15
|
# The hash need not be the complete hash of all things stored, just those you want to change.
|
@@ -102,10 +24,10 @@ module Frivol
|
|
102
24
|
end
|
103
25
|
Frivol::Helpers.store_hash(self, hash)
|
104
26
|
end
|
105
|
-
|
27
|
+
|
106
28
|
# Retrieve stored values, or defaults.
|
107
29
|
#
|
108
|
-
# If you retrieve a single key just that value is returned. If you retrieve multiple keys an array of
|
30
|
+
# If you retrieve a single key just that value is returned. If you retrieve multiple keys an array of
|
109
31
|
# values is returned. You might do:
|
110
32
|
# name = retrieve :name => "Marc Heiligers"
|
111
33
|
# first_name, last_name = retrieve :first_name => "Marc", :last_name => "Heiligers"
|
@@ -120,26 +42,26 @@ module Frivol
|
|
120
42
|
return result.first if result.size == 1
|
121
43
|
result
|
122
44
|
end
|
123
|
-
|
45
|
+
|
124
46
|
# Deletes the stored values (and clears the cache).
|
125
47
|
def delete_storage
|
126
48
|
Frivol::Helpers.delete_hash self
|
127
49
|
end
|
128
|
-
|
50
|
+
|
129
51
|
# Clears the cached values and forces the next retrieve to fetch from Redis.
|
130
52
|
def clear_storage
|
131
53
|
Frivol::Helpers.clear_hash self
|
132
54
|
end
|
133
|
-
|
55
|
+
|
134
56
|
# Expire the stored data in +time+ seconds.
|
135
57
|
def expire_storage(time, bucket = nil)
|
136
58
|
return if time.nil?
|
137
59
|
Frivol::Config.redis.expire storage_key(bucket), time
|
138
60
|
end
|
139
|
-
|
140
|
-
# The base key used for storage in Redis.
|
61
|
+
|
62
|
+
# The base key used for storage in Redis.
|
141
63
|
#
|
142
|
-
# This method has been implemented for use with ActiveRecord and uses <tt>"#{self.class.name}-#{id}"</tt>
|
64
|
+
# This method has been implemented for use with ActiveRecord and uses <tt>"#{self.class.name}-#{id}"</tt>
|
143
65
|
# for the default bucket and <tt>"#{self.class.name}-#{id}-#{bucket}"</tt> for a named bucket.
|
144
66
|
# If you are not using ActiveRecord, or using classes that don't respond to id, you should override
|
145
67
|
# this method in your class.
|
@@ -149,279 +71,8 @@ module Frivol
|
|
149
71
|
@frivol_key ||= "#{self.class.name}-#{id}"
|
150
72
|
bucket.nil? ? @frivol_key : "#{@frivol_key}-#{bucket}"
|
151
73
|
end
|
152
|
-
|
153
|
-
# == Frivol::Config
|
154
|
-
# Sets the Frivol configuration (currently only the Redis config), allows access to the configured Redis instance,
|
155
|
-
# and has a helper method to include Frivol in a class with an optional storage expiry parameter
|
156
|
-
module Config
|
157
|
-
# Set the Redis configuration.
|
158
|
-
#
|
159
|
-
# Expects a hash such as
|
160
|
-
# REDIS_CONFIG = {
|
161
|
-
# :host => "localhost",
|
162
|
-
# :port => 6379
|
163
|
-
# }
|
164
|
-
# Frivol::Config.redis_config = REDIS_CONFIG
|
165
|
-
def self.redis_config=(config)
|
166
|
-
@@redis = Redis.new(config)
|
167
|
-
end
|
168
|
-
|
169
|
-
# Returns the configured Redis instance
|
170
|
-
def self.redis
|
171
|
-
@@redis
|
172
|
-
end
|
173
|
-
|
174
|
-
# A convenience method to include Frivol in a class, with an optional storage expiry parameter.
|
175
|
-
#
|
176
|
-
# For example, you might have the following in environment.rb:
|
177
|
-
# Frivol::Config.redis_config = REDIS_CONFIG
|
178
|
-
# Frivol::Config.include_in ActiveRecord::Base, 600
|
179
|
-
# Which would include Frivol in ActiveRecord::Base and set the default storage expiry to 10 minutes
|
180
|
-
def self.include_in(host_class, storage_expires_in = nil)
|
181
|
-
host_class.send(:include, Frivol)
|
182
|
-
host_class.storage_expires_in storage_expires_in if storage_expires_in
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
module Helpers #:nodoc:
|
187
|
-
def self.store_hash(instance, hash, bucket = nil)
|
188
|
-
data, is_new = get_data_and_is_new instance
|
189
|
-
data[bucket.to_s] = hash
|
190
|
-
|
191
|
-
store_value instance, is_new[bucket.to_s], hash.to_json, bucket
|
192
|
-
|
193
|
-
self.set_data_and_is_new instance, data, is_new
|
194
|
-
end
|
195
|
-
|
196
|
-
def self.store_value(instance, is_new, value, bucket = nil)
|
197
|
-
key = instance.send(:storage_key, bucket)
|
198
|
-
time = instance.class.storage_expiry(bucket)
|
199
|
-
if time == Frivol::NEVER_EXPIRE
|
200
|
-
Frivol::Config.redis[key] = value
|
201
|
-
else
|
202
|
-
Frivol::Config.redis.multi do |redis|
|
203
|
-
time = redis.ttl(key) unless is_new
|
204
|
-
redis[key] = value
|
205
|
-
redis.expire(key, time)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def self.retrieve_hash(instance, bucket = nil)
|
211
|
-
data, is_new = get_data_and_is_new instance
|
212
|
-
return data[bucket.to_s] if data.key?(bucket.to_s)
|
213
|
-
key = instance.send(:storage_key, bucket)
|
214
|
-
json = Frivol::Config.redis[key]
|
215
|
-
|
216
|
-
is_new[bucket.to_s] = json.nil?
|
217
|
-
|
218
|
-
hash = json.nil? ? {} : JSON.parse(json)
|
219
|
-
data[bucket.to_s] = hash
|
220
|
-
|
221
|
-
self.set_data_and_is_new instance, data, is_new
|
222
|
-
hash
|
223
|
-
end
|
224
|
-
|
225
|
-
def self.delete_hash(instance, bucket = nil)
|
226
|
-
key = instance.send(:storage_key, bucket)
|
227
|
-
Frivol::Config.redis.del key
|
228
|
-
clear_hash(instance, bucket)
|
229
|
-
end
|
230
|
-
|
231
|
-
def self.clear_hash(instance, bucket = nil)
|
232
|
-
key = instance.send(:storage_key, bucket)
|
233
|
-
data = instance.instance_variable_defined?(:@frivol_data) ? instance.instance_variable_get(:@frivol_data) : {}
|
234
|
-
data.delete(bucket.to_s)
|
235
|
-
instance.instance_variable_set :@frivol_data, data
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.get_data_and_is_new(instance)
|
239
|
-
data = instance.instance_variable_defined?(:@frivol_data) ? instance.instance_variable_get(:@frivol_data) : {}
|
240
|
-
is_new = instance.instance_variable_defined?(:@frivol_is_new) ? instance.instance_variable_get(:@frivol_is_new) : {}
|
241
|
-
[data, is_new]
|
242
|
-
end
|
243
|
-
|
244
|
-
def self.set_data_and_is_new(instance, data, is_new)
|
245
|
-
instance.instance_variable_set :@frivol_data, data
|
246
|
-
instance.instance_variable_set :@frivol_is_new, is_new
|
247
|
-
end
|
248
|
-
|
249
|
-
def self.store_counter(instance, counter, value)
|
250
|
-
key = instance.send(:storage_key, counter)
|
251
|
-
is_new = !Frivol::Config.redis.exists(key)
|
252
|
-
store_value instance, is_new, value, counter
|
253
|
-
end
|
254
|
-
|
255
|
-
def self.retrieve_counter(instance, counter, default)
|
256
|
-
key = instance.send(:storage_key, counter)
|
257
|
-
(Frivol::Config.redis[key] || default).to_i
|
258
|
-
end
|
259
|
-
|
260
|
-
def self.increment_counter(instance, counter)
|
261
|
-
key = instance.send(:storage_key, counter)
|
262
|
-
Frivol::Config.redis.incr(key)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# == Frivol::ClassMethods
|
267
|
-
# These methods are available on the class level when Frivol is included in the class.
|
268
|
-
module ClassMethods
|
269
|
-
# Set the storage expiry time in seconds for the default bucket or the bucket passed.
|
270
|
-
def storage_expires_in(time, bucket = nil)
|
271
|
-
@frivol_storage_expiry ||= {}
|
272
|
-
@frivol_storage_expiry[bucket.to_s] = time
|
273
|
-
end
|
274
|
-
|
275
|
-
# Get the storage expiry time in seconds for the default bucket or the bucket passed.
|
276
|
-
def storage_expiry(bucket = nil)
|
277
|
-
@frivol_storage_expiry ||= {}
|
278
|
-
@frivol_storage_expiry.key?(bucket.to_s) ? @frivol_storage_expiry[bucket.to_s] : NEVER_EXPIRE
|
279
|
-
end
|
280
|
-
|
281
|
-
# Create a storage bucket.
|
282
|
-
# Frivol creates store_#{bucket} and retrieve_#{bucket} methods automatically.
|
283
|
-
# These methods work exactly like the default store and retrieve methods except that the bucket is
|
284
|
-
# stored in it's own key in Redis and can have it's own expiry time.
|
285
|
-
#
|
286
|
-
# Counters are special in that they do not store a hash but only a single integer value and also
|
287
|
-
# that the data in a counter is not cached for the lifespan of the object, but rather each call
|
288
|
-
# hits Redis. This is intended to make counters thread safe (for example you may have multiple
|
289
|
-
# workers working on a job and they can each increment a progress counter which would not work
|
290
|
-
# with the default retrieve/store method that normal buckets use). For this to actually be thread safe
|
291
|
-
# you need to pass the thread safe option to the config when you make the connection.
|
292
|
-
#
|
293
|
-
# In the case of a counter, the methods work slightly differently:
|
294
|
-
# - store_#{bucket} only takes an integer value to store (no key)
|
295
|
-
# - retrieve_#{bucket} only takes an integer default, and returns only the integer value
|
296
|
-
# - there is an added increment_#{bucket} method which increments the counter by 1
|
297
|
-
#
|
298
|
-
# Options are :expires_in which sets the expiry time for a bucket,
|
299
|
-
# and :counter to create a special counter storage bucket.
|
300
|
-
def storage_bucket(bucket, options = {})
|
301
|
-
time = options[:expires_in]
|
302
|
-
storage_expires_in(time, bucket) if !time.nil?
|
303
|
-
is_counter = options[:counter]
|
304
|
-
|
305
|
-
self.class_eval do
|
306
|
-
if is_counter
|
307
|
-
define_method "store_#{bucket}" do |value|
|
308
|
-
Frivol::Helpers.store_counter(self, bucket, value)
|
309
|
-
end
|
310
|
-
|
311
|
-
define_method "retrieve_#{bucket}" do |default|
|
312
|
-
Frivol::Helpers.retrieve_counter(self, bucket, default)
|
313
|
-
end
|
314
|
-
|
315
|
-
define_method "increment_#{bucket}" do
|
316
|
-
Frivol::Helpers.increment_counter(self, bucket)
|
317
|
-
end
|
318
|
-
else
|
319
|
-
define_method "store_#{bucket}" do |keys_and_values|
|
320
|
-
hash = Frivol::Helpers.retrieve_hash(self, bucket)
|
321
|
-
keys_and_values.each do |key, value|
|
322
|
-
hash[key.to_s] = value
|
323
|
-
end
|
324
|
-
Frivol::Helpers.store_hash(self, hash, bucket)
|
325
|
-
end
|
326
|
-
|
327
|
-
define_method "retrieve_#{bucket}" do |keys_and_defaults|
|
328
|
-
hash = Frivol::Helpers.retrieve_hash(self, bucket)
|
329
|
-
result = keys_and_defaults.map do |key, default|
|
330
|
-
hash[key.to_s] || (default.is_a?(Symbol) && respond_to?(default) && send(default)) || default
|
331
|
-
end
|
332
|
-
return result.first if result.size == 1
|
333
|
-
result
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
define_method "delete_#{bucket}" do
|
338
|
-
Frivol::Helpers.delete_hash(self, bucket)
|
339
|
-
end
|
340
|
-
|
341
|
-
define_method "clear_#{bucket}" do
|
342
|
-
Frivol::Helpers.clear_hash(self, bucket)
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# Use Frivol to cache results for a method (similar to memoize).
|
347
|
-
# Options are :bucket which sets the bucket name for the storage,
|
348
|
-
# :expires_in which sets the expiry time for a bucket,
|
349
|
-
# and :counter to create a special counter storage bucket.
|
350
|
-
#
|
351
|
-
# If not :counter the key is the method_name.
|
352
|
-
#
|
353
|
-
# If you supply :expires_in you must also supply a :bucket otherwise
|
354
|
-
# it is ignored (and the default class expires_in is used if supplied).
|
355
|
-
#
|
356
|
-
# If :counter and no :bucket is provided the :bucket is set to the
|
357
|
-
# :bucket is set to the method_name (and so the :expires_in will be used).
|
358
|
-
def frivolize(method_name, options = {})
|
359
|
-
bucket = options[:bucket]
|
360
|
-
time = options[:expires_in]
|
361
|
-
is_counter = options[:counter]
|
362
|
-
bucket = method_name if bucket.nil? && is_counter
|
363
|
-
frivolized_method_name = "frivolized_#{method_name}"
|
364
|
-
|
365
|
-
self.class_eval do
|
366
|
-
alias_method frivolized_method_name, method_name
|
367
|
-
storage_bucket(bucket, { :expires_in => time, :counter => is_counter }) unless bucket.nil?
|
368
74
|
|
369
|
-
if is_counter
|
370
|
-
define_method method_name do
|
371
|
-
value = send "retrieve_#{bucket}", -2147483647 # A rediculously small number that is unlikely to be used: -2**31 + 1
|
372
|
-
if value == -2147483647
|
373
|
-
value = send frivolized_method_name
|
374
|
-
send "store_#{bucket}", value
|
375
|
-
end
|
376
|
-
value
|
377
|
-
end
|
378
|
-
elsif !bucket.nil?
|
379
|
-
define_method method_name do
|
380
|
-
value = send "retrieve_#{bucket}", { method_name => false }
|
381
|
-
if !value
|
382
|
-
value = send frivolized_method_name
|
383
|
-
send "store_#{bucket}", { method_name => value }
|
384
|
-
end
|
385
|
-
value
|
386
|
-
end
|
387
|
-
else
|
388
|
-
define_method method_name do
|
389
|
-
value = retrieve method_name => false
|
390
|
-
if !value
|
391
|
-
value = send frivolized_method_name
|
392
|
-
store method_name.to_sym => value
|
393
|
-
end
|
394
|
-
value
|
395
|
-
end
|
396
|
-
end
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
# def storage_default(keys_and_defaults)
|
402
|
-
# @frivol_defaults ||= {}
|
403
|
-
# @frivol_defaults.merge keys_and_defaults
|
404
|
-
# end
|
405
|
-
end
|
406
|
-
|
407
75
|
def self.included(host) #:nodoc:
|
408
76
|
host.extend(ClassMethods)
|
409
77
|
end
|
410
78
|
end
|
411
|
-
|
412
|
-
# == Time
|
413
|
-
# An extension to the Time class which allows Time instances to be serialized by <tt>#to_json</tt> and deserialized by <tt>JSON#parse</tt>.
|
414
|
-
class Time
|
415
|
-
# Serialize to JSON
|
416
|
-
def to_json(*a)
|
417
|
-
{
|
418
|
-
'json_class' => self.class.name,
|
419
|
-
'data' => self.to_s
|
420
|
-
}.to_json(*a)
|
421
|
-
end
|
422
|
-
|
423
|
-
# Deserialize from JSON
|
424
|
-
def self.json_create(o)
|
425
|
-
Time.parse(*o['data'])
|
426
|
-
end
|
427
|
-
end
|