cache_cow 0.0.1

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.
Files changed (62) hide show
  1. data/.gitignore +6 -0
  2. data/.gitignore.bak +4 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +16 -0
  6. data/Gemfile.lock +176 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +48 -0
  9. data/Rakefile +54 -0
  10. data/cache_cow.gemspec +32 -0
  11. data/lib/cache_cow.rb +8 -0
  12. data/lib/cache_cow/acts_as_cached.rb +172 -0
  13. data/lib/cache_cow/cacheable.rb +76 -0
  14. data/lib/cache_cow/cached_id_list.rb +46 -0
  15. data/lib/cache_cow/version.rb +3 -0
  16. data/lib/tasks/cache_cow_tasks.rake +4 -0
  17. data/spec/cache_cow/acts_as_cached_spec.rb +8 -0
  18. data/spec/cache_cow/cacheable_spec.rb +161 -0
  19. data/spec/cache_cow/cached_id_list_spec.rb +54 -0
  20. data/spec/cache_cow_spec.rb +8 -0
  21. data/spec/dummy/Rakefile +7 -0
  22. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  23. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  24. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/mailers/.gitkeep +0 -0
  27. data/spec/dummy/app/models/.gitkeep +0 -0
  28. data/spec/dummy/app/models/blog_comment.rb +2 -0
  29. data/spec/dummy/app/models/blog_post.rb +2 -0
  30. data/spec/dummy/app/models/comment.rb +2 -0
  31. data/spec/dummy/app/models/post.rb +6 -0
  32. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/spec/dummy/config.ru +4 -0
  34. data/spec/dummy/config/application.rb +49 -0
  35. data/spec/dummy/config/boot.rb +10 -0
  36. data/spec/dummy/config/database.yml +25 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +30 -0
  39. data/spec/dummy/config/environments/production.rb +60 -0
  40. data/spec/dummy/config/environments/test.rb +39 -0
  41. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/spec/dummy/config/initializers/inflections.rb +10 -0
  43. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  44. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  45. data/spec/dummy/config/initializers/session_store.rb +8 -0
  46. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  47. data/spec/dummy/config/locales/en.yml +5 -0
  48. data/spec/dummy/config/routes.rb +58 -0
  49. data/spec/dummy/db/migrate/20111026035600_create_posts.rb +11 -0
  50. data/spec/dummy/db/migrate/20111026035754_create_comments.rb +10 -0
  51. data/spec/dummy/db/migrate/20111026222959_add_sti_to_posts_and_comments.rb +6 -0
  52. data/spec/dummy/db/schema.rb +32 -0
  53. data/spec/dummy/lib/assets/.gitkeep +0 -0
  54. data/spec/dummy/log/.gitkeep +0 -0
  55. data/spec/dummy/public/404.html +26 -0
  56. data/spec/dummy/public/422.html +26 -0
  57. data/spec/dummy/public/500.html +26 -0
  58. data/spec/dummy/public/favicon.ico +0 -0
  59. data/spec/dummy/script/rails +6 -0
  60. data/spec/dummy/spec/factories.rb +11 -0
  61. data/spec/spec_helper.rb +26 -0
  62. metadata +310 -0
@@ -0,0 +1,76 @@
1
+ module CacheCow
2
+ module Cacheable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def fetch_cache(*args, &block)
8
+ options = args.extract_options!
9
+ keys = args.flatten
10
+
11
+ # raise "Doesn't support multiget" unless keys.size == 1
12
+ Rails.cache.fetch cache_key(keys.first), &block
13
+ end
14
+
15
+ def read_cache(cache_id = nil, options = {})
16
+ Rails.cache.read cache_key(cache_id), options
17
+ end
18
+
19
+ def write_cache(cache_id, value, options = {})
20
+ Rails.cache.write cache_key(cache_id), value, { :expires_in => 1500 }.merge(options)
21
+ end
22
+
23
+ def expire_cache(cache_id = nil, options = {})
24
+ Rails.cache.delete cache_key(cache_id), options
25
+ end
26
+
27
+ def cached?(cache_id = nil)
28
+ Rails.cache.exist?(cache_key(cache_id))
29
+ end
30
+
31
+ def cache_key(cache_id)
32
+ [cache_name, acts_as_cached_version, cache_id].compact.join(':').gsub(' ', '_')[0..(max_key_length - 1)]
33
+ end
34
+
35
+ def cache_name
36
+ @cache_name ||= respond_to?(:base_class) ? base_class.name : name
37
+ end
38
+
39
+ def max_key_length
40
+ 200
41
+ end
42
+
43
+ end
44
+
45
+ module InstanceMethods
46
+
47
+ def fetch_cache(key = nil, options = {}, &block)
48
+ self.class.fetch_cache cache_id(key), options, &block
49
+ end
50
+
51
+ def write_cache(key = nil, options = {})
52
+ self.class.write_cache cache_id(key), options
53
+ end
54
+
55
+ def read_cache(key = nil, options = {})
56
+ self.class.read_cache cache_id(key), options
57
+ end
58
+
59
+ def expire_cache(key = nil, options = {})
60
+ self.class.expire_cache cache_id(key), options
61
+ end
62
+
63
+ def cache_key(key = nil)
64
+ self.class.cache_key cache_id(key)
65
+ end
66
+
67
+ protected
68
+
69
+ def cache_id(key = nil)
70
+ key.nil? ? id.to_s : "#{id}:#{key}"
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ module CacheCow
2
+ module CachedIdList
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ # For example:
7
+ #
8
+ # cached_id_list :groups, :accessor => :member_of?
9
+ def cached_id_list(association_name, options = {})
10
+ accessor_name = options[:accessor]
11
+ cache_key = cached_id_list_cache_key(association_name)
12
+
13
+ define_method(cache_key) do
14
+ fetch_cache cache_key, :expires_in => 1.month do
15
+ send(association_name).select("id").map(&:id)
16
+ end
17
+ end
18
+
19
+ # if accessor_name
20
+ # self.class_eval <<-RUBY
21
+ # def #{accessor_name}(model_or_id)
22
+ # return false if model_or_id.nil?
23
+ # model_id = model_or_id.is_a?(Fixnum) ? model_or_id : model_or_id.id
24
+ # #{cache_key}.include?(model_id)
25
+ # end
26
+ # RUBY
27
+ # end
28
+ end
29
+
30
+ def cached_id_list_cache_key(association_name)
31
+ "cached_" + association_name.to_s.singularize + "_ids"
32
+ end
33
+
34
+ end
35
+
36
+ module InstanceMethods
37
+ def expire_cached_id_list(association_name)
38
+ cache_key = self.class.cached_id_list_cache_key(association_name)
39
+ expire_cache cache_key
40
+ # instance_variable_set("@#{cache_key}", nil)
41
+ # instance_variable_set("@cached_#{association_name}", nil)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module CacheCow
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cache_cow do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheCow::ActsAsCached do
4
+
5
+ it "should be a module" do
6
+ CacheCow::ActsAsCached.should be_kind_of(Module)
7
+ end
8
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheCow::Cacheable do
4
+
5
+ let(:cache_key) { "post:1" }
6
+
7
+ describe ".fetch_cache" do
8
+
9
+ it "returns nil if no block given and cache miss" do
10
+ Post.fetch_cache(cache_key).should be_nil
11
+ end
12
+
13
+ it "writes through value from block on read" do
14
+ Post.fetch_cache(cache_key) { "great post" }
15
+ Post.fetch_cache(cache_key).should == "great post"
16
+ end
17
+
18
+ it "should be accessible from Rails.cache via class cache key" do
19
+ Rails.cache.read(Post.cache_key(cache_key)).should be_nil
20
+ Post.fetch_cache(cache_key) { "great post" }
21
+ Rails.cache.read(Post.cache_key(cache_key)).should == "great post"
22
+ end
23
+
24
+ end
25
+
26
+ describe ".read_cache" do
27
+ it "should read value if cache hit" do
28
+ Post.fetch_cache(cache_key) { "great post" }
29
+ Post.read_cache(cache_key).should == "great post"
30
+ end
31
+
32
+ it "should return nil if cache miss" do
33
+ Post.read_cache(cache_key).should be_nil
34
+ end
35
+ end
36
+
37
+ describe ".write_cache" do
38
+ it "should write value" do
39
+ Post.write_cache(cache_key, "great post")
40
+ Post.read_cache(cache_key).should == "great post"
41
+ end
42
+ end
43
+
44
+ describe ".expire_cache" do
45
+ it "should delete cached value" do
46
+ Post.write_cache(cache_key, "great post")
47
+ Post.expire_cache(cache_key)
48
+ Post.read_cache(cache_key).should be_nil
49
+ end
50
+ end
51
+
52
+ describe ".cached?" do
53
+ it "should return true if cache hit" do
54
+ Post.write_cache(cache_key, "great post")
55
+ Post.cached?(cache_key).should be_true
56
+ end
57
+
58
+ it "should return false if cache miss" do
59
+ Post.cached?(cache_key).should be_false
60
+ end
61
+ end
62
+
63
+ describe ".cache_key" do
64
+
65
+ it "should namespace cache_key to class:version:cache_key" do
66
+ Post.cache_key(cache_key).should == "Post:1:post:1"
67
+ end
68
+
69
+ it "should namespace subclass to base class name" do
70
+ BlogPost.cache_key(cache_key).should == "Post:1:post:1"
71
+ end
72
+
73
+ end
74
+
75
+ describe "instance_methods" do
76
+ let(:post) { stub_model(Post) }
77
+
78
+ describe "#fetch_cache" do
79
+
80
+ it "should write through value on read" do
81
+ post.fetch_cache(cache_key).should be_nil
82
+ post.fetch_cache(cache_key) { "great post" }
83
+ post.fetch_cache(cache_key).should == "great post"
84
+ end
85
+
86
+ it "should use id as default cache_key if none given" do
87
+ post.fetch_cache { "another great post" }
88
+ Rails.cache.read(Post.cache_key(post.id)).should == "another great post"
89
+ end
90
+
91
+ it "should store value under key namespaced by class and id" do
92
+ post.fetch_cache(cache_key) { "a third great post" }
93
+ Rails.cache.read(post.cache_key(cache_key)).should == "a third great post"
94
+ end
95
+
96
+ describe "options" do
97
+ it ":force => true forces cache miss" do
98
+ post.fetch_cache { "another great post" }
99
+ post.fetch_cache(cache_key, :force => true).should be_nil
100
+ end
101
+ end
102
+
103
+ # spec for :compress
104
+ # spec for :expires_in
105
+ end
106
+
107
+ describe "#write_cache" do
108
+ it "should write to key for instance" do
109
+ post.write_cache("foo", "bar")
110
+ post.read_cache("foo").should == "bar"
111
+ end
112
+
113
+ it "should not write to another instance key" do
114
+ post.write_cache("foo", "bar")
115
+ stub_model(Post).read_cache("foo").should be_nil
116
+ end
117
+ end
118
+
119
+ describe "#read_cache" do
120
+ it "should read key for instance" do
121
+ post.write_cache("foo", "bar")
122
+ post.read_cache("foo").should == "bar"
123
+ end
124
+
125
+ it "should not read key for another instance" do
126
+ stub_model(Post).write_cache("foo", "bar")
127
+ post.read_cache("foo").should be_nil
128
+ end
129
+ end
130
+
131
+ describe "#expire_cache" do
132
+ it "should expire key for instance" do
133
+ post.write_cache("foo", "bar")
134
+ post.read_cache("foo").should == "bar"
135
+ post.expire_cache("foo")
136
+ post.read_cache("foo").should be_nil
137
+ end
138
+ end
139
+
140
+ describe "#cache_key" do
141
+ it "should namespace key by its id" do
142
+ post.cache_key("cache_key").should == "Post:1:#{post.id}:cache_key"
143
+ end
144
+ end
145
+ end
146
+
147
+ # describe "get_multi" do
148
+ # it "should do a rails multiget with all the keys" do
149
+ # Rails.cache.should_receive(:read_multi).with(*[User.cache_key("a"), User.cache_key("b")])
150
+ # User.get_multi(["a", "b"]) {}
151
+ # end
152
+ #
153
+ # it "should yield to a multiget object that passes along the appropriate options" do
154
+ # Rails.cache.should_receive(:read).with(User.cache_key("a"), :expires_in => '500').and_return("")
155
+ # User.get_multi(["a"]) do |multi|
156
+ # multi.fetch_cache("a", :expires_in => '500')
157
+ # end
158
+ # end
159
+ # end
160
+
161
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheCow::CachedIdList do
4
+
5
+ let(:post) { Factory(:post) }
6
+ let(:comment) { Factory(:comment) }
7
+
8
+ describe ".cached_id_list" do
9
+
10
+ describe "comments" do
11
+ before(:each) do
12
+ post.comments << comment
13
+ post.save!
14
+ end
15
+
16
+ it "should return ids by association" do
17
+ post.cached_comment_ids.should == post.comment_ids
18
+ end
19
+
20
+ it "should write data to rails cache" do
21
+ post.cached_comment_ids # set cache
22
+ Rails.cache.read(post.cache_key("cached_comment_ids")).should == post.comment_ids
23
+ end
24
+
25
+ it "should return [] if no comments" do
26
+ Factory(:post).cached_comment_ids.should == []
27
+ end
28
+ end
29
+
30
+
31
+ describe "blog_comments" do
32
+ it "should return ids by subclass association" do
33
+ comment_1 = Factory(:blog_comment)
34
+ comment_2 = Factory(:blog_comment)
35
+ post = Factory(:blog_post)
36
+ post.comments << comment_1
37
+ post.comments << comment_2
38
+ post.save!
39
+ post.cached_comment_ids.should == post.comment_ids
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#expire_cached_id_list" do
45
+ it "should expire list of cached ids from given association name" do
46
+ post.comments << comment
47
+ post.cached_comment_ids # set cache
48
+
49
+ post.expire_cached_id_list(:comments)
50
+
51
+ Rails.cache.read(post.cache_key("cached_comment_ids")).should be_nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe CacheCow do
4
+
5
+ it "should be a module" do
6
+ CacheCow.should be_kind_of(Module)
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
3
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
+
5
+ require File.expand_path('../config/application', __FILE__)
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,9 @@
1
+ // This is a manifest file that'll be compiled into including all the files listed below.
2
+ // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ // be included in the compiled file accessible from http://example.com/assets/application.js
4
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ // the compiled file.
6
+ //
7
+ //= require jquery
8
+ //= require jquery_ujs
9
+ //= require_tree .
@@ -0,0 +1,7 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_self
6
+ *= require_tree .
7
+ */
@@ -0,0 +1,3 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery
3
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ class BlogComment < Comment
2
+ end
@@ -0,0 +1,2 @@
1
+ class BlogPost < Post
2
+ end
@@ -0,0 +1,2 @@
1
+ class Comment < ActiveRecord::Base
2
+ end
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+ acts_as_cached
3
+
4
+ has_many :comments
5
+ cached_id_list :comments
6
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= stylesheet_link_tag "application" %>
6
+ <%= javascript_include_tag "application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>