frivol 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'multi_json'
4
+ gem 'redis'
5
+ gem 'rake'
6
+
7
+ group :development do
8
+ gem 'jeweler'
9
+ end
10
+
11
+ group :test do
12
+ gem 'test-unit'
13
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Marc Heiligers
1
+ Copyright (c) 2009-2014 Marc Heiligers
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -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
- 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 help
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
- def storage_key
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 :last => nil # default is nil
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 time we did it
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 << 'lib' << 'test'
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
- require 'rake/rdoctask'
48
- Rake::RDocTask.new do |rdoc|
49
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
29
+ begin
30
+ require 'rdoc/task'
31
+ Rake::RDocTask.new do |rdoc|
32
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
33
 
51
- rdoc.rdoc_dir = 'rdoc'
52
- rdoc.title = "frivol #{version}"
53
- rdoc.rdoc_files.include('README*')
54
- rdoc.rdoc_files.include('lib/**/*.rb')
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.2.0
1
+ 0.3.0
@@ -1,87 +1,99 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
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 = %q{frivol}
8
- s.version = "0.2.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 = %q{2011-03-17}
13
- s.description = %q{Simple Redis backed temporary storage intended primarily for use with ActiveRecord models to provide caching}
14
- s.email = %q{marc@eternal.co.za}
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
- "README.rdoc"
19
+ "README.rdoc"
18
20
  ]
19
21
  s.files = [
20
22
  ".document",
21
- ".gitignore",
22
- "LICENSE",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "doc/classes/Frivol.html",
27
- "doc/classes/Frivol.src/M000003.html",
28
- "doc/classes/Frivol.src/M000004.html",
29
- "doc/classes/Frivol.src/M000005.html",
30
- "doc/classes/Frivol.src/M000006.html",
31
- "doc/classes/Frivol.src/M000007.html",
32
- "doc/classes/Frivol.src/M000008.html",
33
- "doc/classes/Frivol/ClassMethods.html",
34
- "doc/classes/Frivol/ClassMethods.src/M000009.html",
35
- "doc/classes/Frivol/ClassMethods.src/M000010.html",
36
- "doc/classes/Frivol/ClassMethods.src/M000011.html",
37
- "doc/classes/Frivol/Config.html",
38
- "doc/classes/Frivol/Config.src/M000012.html",
39
- "doc/classes/Frivol/Config.src/M000013.html",
40
- "doc/classes/Frivol/Config.src/M000014.html",
41
- "doc/classes/Time.html",
42
- "doc/classes/Time.src/M000001.html",
43
- "doc/classes/Time.src/M000002.html",
44
- "doc/created.rid",
45
- "doc/files/lib/frivol_rb.html",
46
- "doc/fr_class_index.html",
47
- "doc/fr_file_index.html",
48
- "doc/fr_method_index.html",
49
- "doc/index.html",
50
- "doc/rdoc-style.css",
51
- "frivol.gemspec",
52
- "lib/frivol.rb",
53
- "test/fake_redis.rb",
54
- "test/helper.rb",
55
- "test/test_frivol.rb"
56
- ]
57
- s.homepage = %q{http://github.com/marcheiligers/frivol}
58
- s.rdoc_options = ["--charset=UTF-8"]
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
- "test/helper.rb",
65
- "test/test_frivol.rb"
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
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
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<json>, [">= 1.2.0"])
74
- s.add_runtime_dependency(%q<redis>, [">= 2.0.10"])
75
- s.add_development_dependency(%q<shoulda>, [">= 2.11.1"])
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<json>, [">= 1.2.0"])
78
- s.add_dependency(%q<redis>, [">= 2.0.10"])
79
- s.add_dependency(%q<shoulda>, [">= 2.11.1"])
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<json>, [">= 1.2.0"])
83
- s.add_dependency(%q<redis>, [">= 2.0.10"])
84
- s.add_dependency(%q<shoulda>, [">= 2.11.1"])
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
 
@@ -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