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 ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm gemset use cacheable
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cacheable.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
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
@@ -0,0 +1,3 @@
1
+ module Cacheable
2
+ VERSION = "1.0.0"
3
+ 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
@@ -0,0 +1,9 @@
1
+ class Comment < ActiveRecord::Base
2
+ include Cacheable
3
+
4
+ belongs_to :commentable, :polymorphic => true
5
+
6
+ model_cache do
7
+ with_association :commentable
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class Post < ActiveRecord::Base
2
+ include Cacheable
3
+
4
+ belongs_to :user
5
+ has_many :comments, :as => :commentable
6
+
7
+ model_cache do
8
+ with_key
9
+ with_association :user
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ class User < ActiveRecord::Base
2
+ include Cacheable
3
+
4
+ has_many :posts
5
+
6
+ model_cache do
7
+ with_key
8
+ with_attribute :login
9
+ with_method :last_post
10
+ end
11
+
12
+ def last_post
13
+ posts.last
14
+ end
15
+ end
@@ -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