tiny-cache 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +32 -0
  4. data/Rakefile +25 -0
  5. data/lib/tasks/tiny_cache_tasks.rake +4 -0
  6. data/lib/tiny-cache.rb +1 -0
  7. data/lib/tiny_cache.rb +17 -0
  8. data/lib/tiny_cache/active_record.rb +16 -0
  9. data/lib/tiny_cache/active_record/base.rb +147 -0
  10. data/lib/tiny_cache/active_record/belongs_to_association.rb +37 -0
  11. data/lib/tiny_cache/active_record/fetch_by_uniq_key.rb +54 -0
  12. data/lib/tiny_cache/active_record/finder_methods.rb +132 -0
  13. data/lib/tiny_cache/active_record/has_one_association.rb +36 -0
  14. data/lib/tiny_cache/active_record/persistence.rb +28 -0
  15. data/lib/tiny_cache/arel/wheres.rb +36 -0
  16. data/lib/tiny_cache/config.rb +23 -0
  17. data/lib/tiny_cache/record_marshal.rb +27 -0
  18. data/lib/tiny_cache/version.rb +4 -0
  19. data/spec/dummy/README.rdoc +28 -0
  20. data/spec/dummy/Rakefile +6 -0
  21. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  22. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  25. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/spec/dummy/bin/bundle +3 -0
  27. data/spec/dummy/bin/rails +4 -0
  28. data/spec/dummy/bin/rake +4 -0
  29. data/spec/dummy/config.ru +4 -0
  30. data/spec/dummy/config/application.rb +24 -0
  31. data/spec/dummy/config/boot.rb +6 -0
  32. data/spec/dummy/config/database.yml +25 -0
  33. data/spec/dummy/config/environment.rb +6 -0
  34. data/spec/dummy/config/environments/development.rb +30 -0
  35. data/spec/dummy/config/environments/production.rb +81 -0
  36. data/spec/dummy/config/environments/test.rb +37 -0
  37. data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
  38. data/spec/dummy/config/initializers/filter_parameter_logging.rb +5 -0
  39. data/spec/dummy/config/initializers/inflections.rb +17 -0
  40. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  41. data/spec/dummy/config/initializers/secret_token.rb +13 -0
  42. data/spec/dummy/config/initializers/session_store.rb +4 -0
  43. data/spec/dummy/config/initializers/wrap_parameters.rb +15 -0
  44. data/spec/dummy/config/locales/en.yml +23 -0
  45. data/spec/dummy/config/routes.rb +57 -0
  46. data/spec/dummy/public/404.html +58 -0
  47. data/spec/dummy/public/422.html +58 -0
  48. data/spec/dummy/public/500.html +57 -0
  49. data/spec/dummy/public/favicon.ico +0 -0
  50. data/spec/spec_helper.rb +41 -0
  51. metadata +152 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 320a7a13dd6a13cf07103041f4ab468605f7cdd3
4
+ data.tar.gz: 151040bfb1a27e8954f36d58edf902bc85a62476
5
+ SHA512:
6
+ metadata.gz: 86800300e15d55916da1a4bdc52ad6de322c0f3030c2a27f00291d8320ceee3f73eb7ef4d011147cb46f5d2f183f2f091c223d083d2ccbb79b58ec29d6531e8f
7
+ data.tar.gz: 28b0f0586dde4caef37f59c44900ac7d1bb1c692c99d806bfc1a63a50d179c37fb4d09523dae348d9fc42c9484cb34c8c0416e8b1426ae18a36561fec73904b1
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Tiny Cache
2
+
3
+ ## for Rails3+ model cache
4
+
5
+ ## 使用方法 usage:
6
+
7
+ * config
8
+ config/initializers 下新建 tiny_cache.rb
9
+
10
+ ```
11
+ # -*- encoding : utf-8 -*-
12
+ TinyCache.configure do |config|
13
+ config.cache_store = ::Rails.cache
14
+ end
15
+ ```
16
+
17
+ * 在model 中调用
18
+ ```
19
+ module Ruby800
20
+ class Board < ActiveRecord::Base
21
+
22
+ self.acts_as_tiny_cached
23
+
24
+ # self.acts_as_tiny_cached :version => 2, :expires_in => 2.weeks
25
+
26
+ end
27
+ end
28
+ ```
29
+
30
+ * 生成的实例方法
31
+ ...
32
+
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # begin
9
+ # require 'rdoc/task'
10
+ # rescue LoadError
11
+ # require 'rdoc/rdoc'
12
+ # require 'rake/rdoctask'
13
+ # RDoc::Task = Rake::RDocTask
14
+ # end
15
+ #
16
+ # RDoc::Task.new(:rdoc) do |rdoc|
17
+ # rdoc.rdoc_dir = 'rdoc'
18
+ # rdoc.title = 'TinyCache'
19
+ # rdoc.options << '--line-numbers'
20
+ # rdoc.rdoc_files.include('README.rdoc')
21
+ # rdoc.rdoc_files.include('lib/**/*.rb')
22
+ # end
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :tiny_cache do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1 @@
1
+ require 'tiny_cache'
@@ -0,0 +1,17 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'active_support/all'
3
+ require 'tiny_cache/config'
4
+ require 'tiny_cache/record_marshal'
5
+
6
+ module TinyCache
7
+ def self.configure
8
+ block_given? ? yield(Config) : Config
9
+ end
10
+
11
+ class << self
12
+ delegate :logger, :cache_store, :cache_key_prefix, :to => Config
13
+ end
14
+
15
+ end
16
+
17
+ require 'tiny_cache/active_record' if defined?(::ActiveRecord)
@@ -0,0 +1,16 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "#{File.dirname(__FILE__)}/active_record/base"
3
+ require "#{File.dirname(__FILE__)}/active_record/fetch_by_uniq_key"
4
+ require "#{File.dirname(__FILE__)}/active_record/finder_methods"
5
+ require "#{File.dirname(__FILE__)}/active_record/persistence"
6
+ require "#{File.dirname(__FILE__)}/active_record/belongs_to_association"
7
+ require "#{File.dirname(__FILE__)}/active_record/has_one_association"
8
+
9
+ if defined?(ActiveRecord::Base)
10
+ ::ActiveRecord::Base.send(:include, ::TinyCache::ActiveRecord::Base)
11
+ ::ActiveRecord::Base.send(:extend, ::TinyCache::ActiveRecord::FetchByUniqKey)
12
+ ::ActiveRecord::Relation.send(:include, ::TinyCache::ActiveRecord::FinderMethods)
13
+ ::ActiveRecord::Base.send(:include, ::TinyCache::ActiveRecord::Persistence)
14
+ ::ActiveRecord::Associations::BelongsToAssociation.send(:include, ::TinyCache::ActiveRecord::Associations::BelongsToAssociation)
15
+ ::ActiveRecord::Associations::HasOneAssociation.send(:include, ::TinyCache::ActiveRecord::Associations::HasOneAssociation)
16
+ end
@@ -0,0 +1,147 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module TinyCache
3
+ module ActiveRecord
4
+ module Base
5
+
6
+ def self.included base
7
+ base.class_eval do
8
+ include ::ActiveSupport::Concern
9
+
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+
13
+ class << self
14
+ alias_method_chain :update_counters, :tiny_cache
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def tiny_cache_options
21
+ @tiny_cache_options ||= {
22
+ :expires_in => 1.week,
23
+ :version => 0
24
+ }
25
+ end
26
+
27
+ def acts_as_tiny_cached(*args)
28
+ options = args.extract_options!
29
+
30
+ @tiny_cache_enabled = true
31
+
32
+ if options.key?(:expires_in)
33
+ self.tiny_cache_options[:expires_in] = options[:expires_in]
34
+ end
35
+
36
+ if options.key?(:version)
37
+ self.tiny_cache_options[:version] = options[:version]
38
+ end
39
+
40
+ after_commit :expire_tiny_cache, :on => :destroy
41
+ after_commit :update_tiny_cache, :on => :update
42
+ after_commit :write_tiny_cache, :on => :create
43
+ end
44
+
45
+ def update_counters_with_tiny_cache(id, counters)
46
+ update_counters_without_tiny_cache(id, counters).tap do
47
+ Array(id).each{|i| expire_tiny_cache(i)}
48
+ end
49
+ end
50
+
51
+ # 是否启用cache
52
+ def tiny_cache_enabled?
53
+ !!@tiny_cache_enabled
54
+ end
55
+
56
+ # 不启用cache
57
+ def without_tiny_cache
58
+ old, @tiny_cache_enabled = @tiny_cache_enabled, false
59
+
60
+ yield if block_given?
61
+ ensure
62
+ @tiny_cache_enabled = old
63
+ end
64
+
65
+ def cache_store
66
+ ::TinyCache::Config.cache_store
67
+ end
68
+
69
+ def logger
70
+ ::TinyCache::Config.logger
71
+ end
72
+
73
+ def tiny_cache_key_prefix
74
+ ::TinyCache::Config.cache_key_prefix
75
+ end
76
+
77
+ def tiny_cache_version
78
+ tiny_cache_options ? tiny_cache_options[:version] : ''
79
+ end
80
+
81
+ def tiny_cache_key(id)
82
+ "#{tiny_cache_key_prefix}/models/#{self.name}/#{id}/#{tiny_cache_version}"
83
+ end
84
+
85
+ def read_tiny_cache(id)
86
+ RecordMarshal.load(TinyCache.cache_store.read(tiny_cache_key(id))) if self.tiny_cache_enabled?
87
+ end
88
+
89
+ def expire_tiny_cache(id)
90
+ TinyCache.cache_store.delete(tiny_cache_key(id)) if self.tiny_cache_enabled?
91
+ end
92
+
93
+ end
94
+
95
+ module InstanceMethods
96
+ def try_load_from_tiny_cache
97
+ self.class.read_tiny_cache(self.id) || self
98
+ end
99
+
100
+ # 缓存的key
101
+ def tiny_cache_key
102
+ self.class.tiny_cache_key(self.id)
103
+ end
104
+
105
+ def expire_tiny_cache
106
+ self.class.expire_tiny_cache(self.id)
107
+ end
108
+
109
+ def tiny_cache_method_fetch *args, &block
110
+ options = args.extract_options!
111
+
112
+ self.class.cache_store.fetch self.tiny_cache_method_key(args), options do
113
+ block.call
114
+ end
115
+ end
116
+
117
+ def tiny_cache_method_expire *args
118
+ args.extract_options!
119
+
120
+ self.class.cache_store.delete self.tiny_cache_method_key(args)
121
+ end
122
+
123
+ def tiny_cache_method_key args
124
+ "#{self.tiny_cache_key}/method_fetch/#{args.join('/')}"
125
+ end
126
+
127
+ def refresh_tiny_cache_updated_at
128
+ self.updated_at = Time.now
129
+ end
130
+
131
+ def tiny_cache_expires_in
132
+ self.class.tiny_cache_options[:expires_in]
133
+ end
134
+
135
+ def write_tiny_cache
136
+ if self.class.tiny_cache_enabled?
137
+ ::TinyCache.cache_store.write(
138
+ self.tiny_cache_key, RecordMarshal.dump(self), :expires_in => tiny_cache_expires_in
139
+ )
140
+ end
141
+ end
142
+
143
+ alias update_tiny_cache write_tiny_cache
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module TinyCache
3
+ module ActiveRecord
4
+ module Associations
5
+ module BelongsToAssociation
6
+ extend ActiveSupport::Concern
7
+ included do
8
+ class_eval do
9
+ alias_method_chain :find_target, :tiny_cache
10
+ end
11
+ end
12
+
13
+ def association_class
14
+ @reflection.klass
15
+ end
16
+
17
+ def find_target_with_tiny_cache
18
+ return find_target_without_tiny_cache unless klass.tiny_cache_enabled?
19
+ cache_record = klass.read_tiny_cache(tiny_cache_key)
20
+ return cache_record.tap{|record| set_inverse_instance(record)} if cache_record
21
+ record = find_target_without_tiny_cache
22
+
23
+ record.tap do |r|
24
+ set_inverse_instance(r)
25
+ r.write_tiny_cache
26
+ end if record
27
+ end
28
+
29
+ private
30
+
31
+ def tiny_cache_key
32
+ owner[reflection.foreign_key]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module TinyCache
3
+ module ActiveRecord
4
+ module FetchByUniqKey
5
+ def tiny_cache_find_by_id id
6
+ begin
7
+ record = read_tiny_cache(id)
8
+ record = self.find_by_id(id) if record.nil?
9
+ record.write_tiny_cache if record
10
+ record
11
+ ensure ::ActiveRecord::RecordNotFound
12
+ nil
13
+ end
14
+ end
15
+
16
+ def tiny_cache_find_by_id! id
17
+ tiny_cache_find_by_id(id) || raise(::ActiveRecord::RecordNotFound)
18
+ end
19
+
20
+ #
21
+ # 用来根据属性进行查询缓存, 一般适合是唯一不变的条件
22
+ # eg:
23
+ # 1. topic.tiny_cache_find_by :uid => 1, :category_id => 2
24
+ #
25
+ def tiny_cache_find_by conditions={}
26
+ uniq_cache_key = tiny_cache_uniq_key(conditions)
27
+
28
+ # 根据cache_key获取id
29
+ if iid = TinyCache.cache_store.read(uniq_cache_key)
30
+ begin
31
+ self.find iid
32
+ ensure ::ActiveRecord::RecordNotFound
33
+ nil
34
+ end
35
+ else
36
+ record = self.where(conditions).first
37
+ record.tap do |record|
38
+ TinyCache.cache_store.write(uniq_cache_key, record.id)
39
+ end if record
40
+ end
41
+ end
42
+
43
+ def tiny_cache_find_by! conditions={}
44
+ tiny_cache_find_by(conditions) || raise(::ActiveRecord::RecordNotFound)
45
+ end
46
+
47
+ private
48
+
49
+ def tiny_cache_uniq_key conditions={}
50
+ "#{::TinyCache::Config.cache_key_prefix}/model_uniq_keys/#{self.name}/#{conditions.sort.inspect}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,132 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "#{File.dirname(__FILE__)}/../arel/wheres"
3
+
4
+ module TinyCache
5
+ module ActiveRecord
6
+ module FinderMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_eval do
11
+ alias_method_chain :find_one, :tiny_cache
12
+ alias_method_chain :find_by_attributes, :tiny_cache
13
+ end
14
+ end
15
+
16
+ # TODO:
17
+ def find_by_attributes(match, attributes, *args)
18
+ conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
19
+ result = where(conditions).send(match.finder)
20
+
21
+ if match.bang? && result.nil?
22
+ raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
23
+ else
24
+ yield(result) if block_given?
25
+ result
26
+ end
27
+ end
28
+
29
+ # TODO fetch multi ids
30
+ #
31
+ # Cacheable:
32
+ #
33
+ # current_user.articles.where(:status => 1).visiable.find(params[:id])
34
+ #
35
+ # Uncacheable:
36
+ #
37
+ # Article.where("user_id = '1'").find(params[:id])
38
+ # Article.where("user_id > 1").find(params[:id])
39
+ # Article.where("articles.user_id = 1").find(prams[:id])
40
+ # Article.where("user_id = 1 AND ...").find(params[:id])
41
+ def find_one_with_tiny_cache(id)
42
+ return find_one_without_tiny_cache(id) unless tiny_cache_enabled?
43
+ return find_one_without_tiny_cache(id) unless select_all_column?
44
+
45
+ id = id.id if ActiveRecord::Base === id
46
+
47
+ # if ::ActiveRecord::IdentityMap.enabled? && cachable? && record = from_identity_map(id)
48
+ # return record
49
+ # end
50
+
51
+ if cachable?
52
+ if record = @klass.read_tiny_cache(id)
53
+ return record
54
+ end
55
+ end
56
+
57
+ if cachable_without_conditions?
58
+ if record = @klass.read_tiny_cache(id)
59
+ return record if where_match_with_cache?(where_values, record)
60
+ end
61
+ end
62
+
63
+ record = find_one_without_tiny_cache(id)
64
+ record.write_tiny_cache
65
+ record
66
+ end
67
+
68
+ # TODO cache find_or_create_by_id
69
+ def find_by_attributes_with_tiny_cache(match, attributes, *args)
70
+ return find_by_attributes_without_tiny_cache(match, attributes, *args) unless tiny_cache_enabled?
71
+ return find_by_attributes_without_tiny_cache(match, attributes, *args) unless select_all_column?
72
+
73
+ conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
74
+
75
+ if conditions.has_key?("id")
76
+ result = wrap_bang(match.bang?) do
77
+ if conditions.size == 1
78
+ find_one_with_tiny_cache(conditions["id"])
79
+ else
80
+ where(conditions.except("id")).find_one_with_tiny_cache(conditions["id"])
81
+ end
82
+ end
83
+
84
+ yield(result) if block_given? #edge rails do this bug rails3.1.0 not
85
+
86
+ return result
87
+ end
88
+
89
+ find_by_attributes_without_tiny_cache(match, attributes, *args)
90
+ end
91
+
92
+ private
93
+
94
+ def wrap_bang(bang)
95
+ bang ? yield : (yield rescue nil)
96
+ end
97
+
98
+ def cachable?
99
+ where_values.blank? &&
100
+ limit_one? && order_values.blank? &&
101
+ includes_values.blank? && preload_values.blank? &&
102
+ readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
103
+ end
104
+
105
+ def cachable_without_conditions?
106
+ limit_one? && order_values.blank? &&
107
+ includes_values.blank? && preload_values.blank? &&
108
+ readonly_value.nil? && joins_values.blank? && !@klass.locking_enabled?
109
+ end
110
+
111
+ def where_match_with_cache?(where_values, cache_record)
112
+ condition = TinyCache::Arel::Wheres.new(where_values)
113
+ return false unless condition.all_equality?
114
+ condition.extract_pairs.all? do |pair|
115
+ cache_record.read_attribute(pair[:left]) == pair[:right]
116
+ end
117
+ end
118
+
119
+ def limit_one?
120
+ limit_value.blank? || limit_value == 1
121
+ end
122
+
123
+ def select_all_column?
124
+ select_values.blank?
125
+ end
126
+
127
+ # def from_identity_map(id)
128
+ # ::ActiveRecord::IdentityMap.get(@klass, id)
129
+ # end
130
+ end
131
+ end
132
+ end