simple_cacheable 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/README.md +51 -0
- data/Rakefile +2 -0
- data/cacheable.gemspec +24 -0
- data/lib/cacheable/version.rb +3 -0
- data/lib/cacheable.rb +125 -0
- data/spec/cacheable_spec.rb +110 -0
- data/spec/models/comment.rb +9 -0
- data/spec/models/post.rb +11 -0
- data/spec/models/user.rb +15 -0
- data/spec/spec_helper.rb +85 -0
- metadata +117 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
cacheable
|
2
|
+
=========
|
3
|
+
|
4
|
+
cacheable is a simple cache implementation based on activerecord, it is
|
5
|
+
extracted from [rails-bestpractices.com][1].
|
6
|
+
|
7
|
+
it supports activerecord >= 3.0.0
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
class User < ActiveRecord::Base
|
13
|
+
include Cacheable
|
14
|
+
|
15
|
+
has_many :posts
|
16
|
+
|
17
|
+
model_cache do
|
18
|
+
with_key # User.find_cached(1)
|
19
|
+
with_attribute :login # User.find_by_login('flyerhzm')
|
20
|
+
with_method :last_post # user.cached_last_post
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_post
|
24
|
+
posts.last
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Post < ActiveRecord::Base
|
29
|
+
include Cacheable
|
30
|
+
|
31
|
+
belongs_to :user
|
32
|
+
has_many :comments, :as => :commentable
|
33
|
+
|
34
|
+
model_cache do
|
35
|
+
with_key # post.find_cached(1)
|
36
|
+
with_association :user # post.cached_user
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Comment < ActiveRecord::Base
|
41
|
+
include Cacheable
|
42
|
+
|
43
|
+
belongs_to :commentable, :polymorphic => true
|
44
|
+
|
45
|
+
model_cache do
|
46
|
+
with_association :commentable # comment.cached_commentable
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
[1]:https://github.com/flyerhzm/rails-bestpractices.com
|
data/Rakefile
ADDED
data/cacheable.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cacheable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "simple_cacheable"
|
7
|
+
s.version = Cacheable::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Richard Huang"]
|
10
|
+
s.email = ["flyerhzm@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/flyerhzm/simple-cacheable"
|
12
|
+
s.summary = %q{a simple cache implementation based on activerecord}
|
13
|
+
s.description = %q{a simple cache implementation based on activerecord}
|
14
|
+
|
15
|
+
s.add_dependency("rails", ">= 3.0.0")
|
16
|
+
s.add_development_dependency("rspec")
|
17
|
+
s.add_development_dependency("mocha")
|
18
|
+
s.add_development_dependency("sqlite3-ruby")
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
data/lib/cacheable.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Cacheable
|
2
|
+
def self.included(base)
|
3
|
+
base.class_eval do
|
4
|
+
class <<self
|
5
|
+
def model_cache(&block)
|
6
|
+
instance_exec &block
|
7
|
+
end
|
8
|
+
|
9
|
+
def with_key
|
10
|
+
class_eval <<-EOF
|
11
|
+
after_update :expire_key_cache
|
12
|
+
|
13
|
+
def self.find_cached(id)
|
14
|
+
Rails.cache.fetch "#{name.tableize}/" + id.to_i.to_s do
|
15
|
+
self.find(id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
EOF
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_attribute(*attributes)
|
22
|
+
class_eval <<-EOF
|
23
|
+
after_update :expire_attribute_cache
|
24
|
+
EOF
|
25
|
+
|
26
|
+
write_inheritable_attribute :cached_indices, attributes.inject({}) { |indices, attribute| indices[attribute] = {} }
|
27
|
+
attributes.each do |attribute|
|
28
|
+
class_eval <<-EOF
|
29
|
+
def self.find_cached_by_#{attribute}(value)
|
30
|
+
indices = read_inheritable_attribute :cached_indices
|
31
|
+
indices["#{attribute}"] ||= []
|
32
|
+
indices["#{attribute}"] << value
|
33
|
+
write_inheritable_attribute :cached_indices, indices
|
34
|
+
Rails.cache.fetch attribute_cache_key("#{attribute}", value) do
|
35
|
+
self.find_by_#{attribute}(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
EOF
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_method(*methods)
|
43
|
+
class_eval <<-EOF
|
44
|
+
after_update :expire_method_cache
|
45
|
+
EOF
|
46
|
+
|
47
|
+
write_inheritable_attribute :cached_methods, methods
|
48
|
+
methods.each do |meth|
|
49
|
+
class_eval <<-EOF
|
50
|
+
def cached_#{meth}
|
51
|
+
Rails.cache.fetch method_cache_key("#{meth}") do
|
52
|
+
#{meth}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
EOF
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def with_association(*association_names)
|
60
|
+
association_names.each do |association_name|
|
61
|
+
association = reflect_on_association(association_name)
|
62
|
+
if :belongs_to == association.macro
|
63
|
+
polymorphic = association.options[:polymorphic]
|
64
|
+
class_eval <<-EOF
|
65
|
+
def cached_#{association_name}
|
66
|
+
Rails.cache.fetch association_cache_key("#{association_name}", #{polymorphic}) do
|
67
|
+
#{association_name}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
EOF
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def attribute_cache_key(attribute, value)
|
76
|
+
"#{name.tableize}/attribute/#{attribute}/#{value}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def expire_model_cache
|
83
|
+
expire_key_cache
|
84
|
+
expire_attribute_cache
|
85
|
+
expire_method_cache
|
86
|
+
end
|
87
|
+
|
88
|
+
def expire_key_cache
|
89
|
+
Rails.cache.delete model_cache_key
|
90
|
+
end
|
91
|
+
|
92
|
+
def expire_attribute_cache
|
93
|
+
if indices = self.class.read_inheritable_attribute(:cached_indices)
|
94
|
+
indices.each do |attribute, values|
|
95
|
+
values.each do |value|
|
96
|
+
Rails.cache.delete self.class.attribute_cache_key(attribute, value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def expire_method_cache
|
103
|
+
if meths = self.class.read_inheritable_attribute(:cached_methods)
|
104
|
+
meths.each do |meth|
|
105
|
+
Rails.cache.delete method_cache_key(meth)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def model_cache_key
|
111
|
+
"#{self.class.name.tableize}/#{self.id.to_i}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def method_cache_key(meth)
|
115
|
+
"#{model_cache_key}/method/#{meth}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def association_cache_key(name, polymorphic=nil)
|
119
|
+
if polymorphic
|
120
|
+
"#{self.send("#{name}_type").tableize}/#{self.send("#{name}_id")}"
|
121
|
+
else
|
122
|
+
"#{name.tableize}/#{self.send(name + "_id")}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cacheable do
|
4
|
+
let(:cache) { Rails.cache }
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
@user = User.create(:login => 'flyerhzm')
|
8
|
+
@post1 = @user.posts.create(:title => 'post1')
|
9
|
+
@post2 = @user.posts.create(:title => 'post2')
|
10
|
+
@comment1 = @post1.comments.create
|
11
|
+
@comment2 = @post2.comments.create
|
12
|
+
end
|
13
|
+
|
14
|
+
after :each do
|
15
|
+
cache.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with_key" do
|
19
|
+
it "should not cache key" do
|
20
|
+
cache.data["users/#{@user.id}"].should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should cache by User#id" do
|
24
|
+
User.find_cached(@user.id).should == @user
|
25
|
+
cache.data["users/#{@user.id}"].should == @user
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should get cached by User#id multiple times" do
|
29
|
+
User.find_cached(@user.id)
|
30
|
+
User.find_cached(@user.id).should == @user
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with_attribute" do
|
35
|
+
it "should not cache User.find_by_login" do
|
36
|
+
cache.data["users/attribute/login/flyerhzm"].should be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should cache by User.find_by_login" do
|
40
|
+
User.find_cached_by_login("flyerhzm").should == @user
|
41
|
+
cache.data["users/attribute/login/flyerhzm"].should == @user
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should get cached by User.find_by_login multiple times" do
|
45
|
+
User.find_cached_by_login("flyerhzm")
|
46
|
+
User.find_cached_by_login("flyerhzm").should == @user
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with_method" do
|
51
|
+
it "should not cache User.last_post" do
|
52
|
+
cache.data["users/#{@user.id}/method/last_post"].should be_nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should cache User#last_post" do
|
56
|
+
@user.cached_last_post.should == @user.last_post
|
57
|
+
cache.data["users/#{@user.id}/method/last_post"].should == @user.last_post
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should cache User#last_post multiple times" do
|
61
|
+
@user.cached_last_post
|
62
|
+
@user.cached_last_post.should == @user.last_post
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with_association" do
|
67
|
+
it "should not cache association" do
|
68
|
+
cache.data["users/#{@user.id}"].should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should cache Post#user" do
|
72
|
+
@post1.cached_user.should == @user
|
73
|
+
cache.data["users/#{@user.id}"].should == @user
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should cache Post#user multiple times" do
|
77
|
+
@post1.cached_user
|
78
|
+
@post1.cached_user.should == @user
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should cache Comment#commentable with polymorphic" do
|
82
|
+
cache.data["posts/#{@post1.id}"].should be_nil
|
83
|
+
@comment1.cached_commentable.should == @post1
|
84
|
+
cache.data["posts/#{@post1.id}"].should == @post1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "expire_model_cache" do
|
89
|
+
it "should delete with_key cache" do
|
90
|
+
user = User.find_cached(@user.id)
|
91
|
+
cache.data["users/#{user.id}"].should_not be_nil
|
92
|
+
user.expire_model_cache
|
93
|
+
cache.data["users/#{user.id}"].should be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should delete with_attribute cache" do
|
97
|
+
user = User.find_cached_by_login("flyerhzm")
|
98
|
+
cache.data["users/attribute/login/flyerhzm"].should == @user
|
99
|
+
@user.expire_model_cache
|
100
|
+
cache.data["users/attribute/login/flyerhzm"].should be_nil
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should delete with_method cache" do
|
104
|
+
@user.cached_last_post
|
105
|
+
cache.data["users/#{@user.id}/method/last_post"].should_not be_nil
|
106
|
+
@user.expire_model_cache
|
107
|
+
cache.data["users/#{@user.id}/method/last_post"].should be_nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/spec/models/post.rb
ADDED
data/spec/models/user.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
|
4
|
+
require 'rails'
|
5
|
+
require 'active_record'
|
6
|
+
require 'rspec'
|
7
|
+
require 'mocha'
|
8
|
+
|
9
|
+
require 'cacheable'
|
10
|
+
|
11
|
+
ActiveRecord::Migration.verbose = false
|
12
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
13
|
+
|
14
|
+
MODELS = File.join(File.dirname(__FILE__), "models")
|
15
|
+
$LOAD_PATH.unshift(MODELS)
|
16
|
+
|
17
|
+
Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
|
18
|
+
|
19
|
+
class TestCache
|
20
|
+
attr_reader :data
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@data = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(key, &block)
|
27
|
+
if read(key)
|
28
|
+
read(key)
|
29
|
+
else
|
30
|
+
write(key, block.call)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def read(key)
|
35
|
+
@data[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
def write(key, value)
|
39
|
+
@data[key] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(key)
|
43
|
+
@data.delete key
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear
|
47
|
+
@data.clear
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Rails
|
52
|
+
class <<self
|
53
|
+
def cache
|
54
|
+
@cache ||= TestCache.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
RSpec.configure do |config|
|
60
|
+
config.mock_with :mocha
|
61
|
+
|
62
|
+
config.before :all do
|
63
|
+
::ActiveRecord::Schema.define(:version => 1) do
|
64
|
+
create_table :users do |t|
|
65
|
+
t.string :login
|
66
|
+
end
|
67
|
+
|
68
|
+
create_table :posts do |t|
|
69
|
+
t.integer :user_id
|
70
|
+
t.string :title
|
71
|
+
end
|
72
|
+
|
73
|
+
create_table :comments do |t|
|
74
|
+
t.integer :commentable_id
|
75
|
+
t.string :commentable_type
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
config.after :all do
|
81
|
+
::ActiveRecord::Base.connection.tables.each do |table|
|
82
|
+
::ActiveRecord::Base.connection.drop_table(table)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_cacheable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Richard Huang
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-10-05 00:00:00 +08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rails
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.0.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: mocha
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: sqlite3-ruby
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
description: a simple cache implementation based on activerecord
|
61
|
+
email:
|
62
|
+
- flyerhzm@gmail.com
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- .rspec
|
72
|
+
- .rvmrc
|
73
|
+
- Gemfile
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- cacheable.gemspec
|
77
|
+
- lib/cacheable.rb
|
78
|
+
- lib/cacheable/version.rb
|
79
|
+
- spec/cacheable_spec.rb
|
80
|
+
- spec/models/comment.rb
|
81
|
+
- spec/models/post.rb
|
82
|
+
- spec/models/user.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: https://github.com/flyerhzm/simple-cacheable
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.6.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: a simple cache implementation based on activerecord
|
112
|
+
test_files:
|
113
|
+
- spec/cacheable_spec.rb
|
114
|
+
- spec/models/comment.rb
|
115
|
+
- spec/models/post.rb
|
116
|
+
- spec/models/user.rb
|
117
|
+
- spec/spec_helper.rb
|