arid_cache 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ module AridCache
2
+ class Store < Hash
3
+ extend ActiveSupport::Memoizable
4
+
5
+ # AridCache::Store::Blueprint
6
+ #
7
+ # Stores options and blocks that are used to generate results for finds
8
+ # and counts.
9
+ Blueprint = Struct.new(:key, :klass, :proc, :opts) do
10
+
11
+ def initialize(key, klass, proc=nil, opts={})
12
+ self.key = key
13
+ self.klass = klass
14
+ self.proc = proc
15
+ self.opts = opts
16
+ end
17
+
18
+ def klass=(value) # store the base class of *value*
19
+ self['klass'] = value.is_a?(Class) ? value.name : value.class.name
20
+ end
21
+
22
+ def klass
23
+ self['klass'].constantize unless self['klass'].nil?
24
+ end
25
+
26
+ def opts=(value)
27
+ self['opts'] = value.symbolize_keys! unless !value.respond_to?(:symbolize_keys)
28
+ end
29
+
30
+ def opts
31
+ self['opts'] || {}
32
+ end
33
+
34
+ def proc(object=nil)
35
+ if self['proc'].nil? && !object.nil?
36
+ self['proc'] = key
37
+ else
38
+ self['proc']
39
+ end
40
+ end
41
+ end
42
+
43
+ def has?(object, key)
44
+ self.include?(object_store_key(object, key))
45
+ end
46
+
47
+ # Empty the proc store
48
+ def delete!
49
+ delete_if { true }
50
+ end
51
+
52
+ def self.instance
53
+ @@singleton_instance ||= self.new
54
+ end
55
+
56
+ def find(object, key)
57
+ self[object_store_key(object, key)]
58
+ end
59
+
60
+ def add(object, key, proc, opts)
61
+ store_key = object_store_key(object, key)
62
+ self[store_key] = AridCache::Store::Blueprint.new(key, object, proc, opts)
63
+ end
64
+
65
+ def find_or_create(object, key)
66
+ store_key = object_store_key(object, key)
67
+ if self.include?(store_key)
68
+ self[store_key]
69
+ else
70
+ self[store_key] = AridCache::Store::Blueprint.new(key, object)
71
+ end
72
+ end
73
+
74
+ protected
75
+
76
+ def initialize
77
+ end
78
+
79
+ def object_store_key(object, key)
80
+ (object.is_a?(Class) ? object.name.downcase : object.class.name.pluralize.downcase) + '-' + key.to_s
81
+ end
82
+ memoize :object_store_key
83
+ end
84
+ end
data/lib/arid_cache.rb ADDED
@@ -0,0 +1,47 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
3
+
4
+ require 'arid_cache/helpers'
5
+ require 'arid_cache/store'
6
+ require 'arid_cache/active_record'
7
+ require 'arid_cache/cache_proxy'
8
+
9
+ module AridCache
10
+ extend AridCache::Helpers
11
+ class Error < StandardError; end
12
+
13
+ def self.cache
14
+ AridCache::CacheProxy
15
+ end
16
+
17
+ def self.clear_all_caches
18
+ AridCache::CacheProxy.clear_all_caches
19
+ end
20
+
21
+ def self.clear_class_caches(object)
22
+ AridCache::CacheProxy.clear_class_caches(object)
23
+ end
24
+
25
+ def self.clear_instance_caches(object)
26
+ AridCache::CacheProxy.clear_instance_caches(object)
27
+ end
28
+
29
+ def self.store
30
+ AridCache::Store.instance
31
+ end
32
+
33
+ # The old method of including this module, if you don't want to
34
+ # extend active record. Just add 'include AridCache' to your
35
+ # model class.
36
+ def self.included(base)
37
+ base.send(:include, AridCache::ActiveRecord)
38
+ end
39
+
40
+ # Initializes ARID Cache for Rails.
41
+ #
42
+ # This method is called by `init.rb`,
43
+ # which is run by Rails on startup.
44
+ def self.init_rails
45
+ ::ActiveRecord::Base.send(:include, AridCache::ActiveRecord)
46
+ end
47
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "AridCache" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'arid_cache'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ require 'rubygems'
8
+ require 'will_paginate'
9
+
10
+ Spec::Runner.configure do |config|
11
+
12
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :arid_cache do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,232 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class AridCacheTest < ActiveSupport::TestCase
4
+ def setup
5
+ Rails.cache.clear
6
+ AridCache.store.delete!
7
+ get_user
8
+ end
9
+
10
+ test "initializes needed objects" do
11
+ assert_instance_of AridCache::Store, AridCache.store
12
+ assert_same AridCache::CacheProxy, AridCache.cache
13
+ end
14
+
15
+ test "should respond to methods" do
16
+ assert User.respond_to?(:clear_cache)
17
+ assert User.first.respond_to?(:clear_cache)
18
+ assert_instance_of AridCache::Store, AridCache.store
19
+ end
20
+
21
+ test "should not clobber model methods" do
22
+ assert_respond_to User.first, :name
23
+ assert_respond_to Company.first, :name
24
+ assert_nothing_raised { User.first.name }
25
+ assert_nothing_raised { Company.first.name }
26
+
27
+ # Shouldn't mess with your model's method_missing
28
+ assert_nothing_raised { User.first.is_high? }
29
+ assert User.first.is_high?
30
+ end
31
+
32
+ test "should allow me to cache on the model" do
33
+ assert_nothing_raised do
34
+ define_model_cache(User)
35
+ end
36
+ #assert_instance_of(Proc, AridCache.store[User.arid_cache_key('companies')].proc)
37
+ end
38
+
39
+ test "should allow me to cache on the instance" do
40
+ assert_nothing_raised do
41
+ define_instance_cache(@user)
42
+ end
43
+ #assert_instance_of(Proc, AridCache.store[AridCache.store.store_key@user.arid_cache_key('companies')].proc)
44
+ end
45
+
46
+ test "should raise an error on invalid dynamic caches" do
47
+ assert_raises ArgumentError do
48
+ @user.cached_invalid_companies
49
+ end
50
+ end
51
+
52
+ test "should create dynamic caches given valid arguments" do
53
+ assert_nothing_raised { @user.cached_companies }
54
+ #assert_instance_of(Proc, AridCache.store[@user.arid_cache_key('companies')].proc)
55
+ end
56
+
57
+ test "counts queries correctly" do
58
+ assert_queries(1) { User.all }
59
+ end
60
+
61
+ test "returns valid results" do
62
+ @one = @user.cached_companies
63
+ assert_equal @user.companies, @one
64
+ assert_equal @user.companies.count, @one.size
65
+ end
66
+
67
+ test "paginates results" do
68
+ results = @user.cached_companies(:page => 1, :per_page => 3)
69
+ assert_kind_of WillPaginate::Collection, results
70
+ assert_equal 3, results.size
71
+ assert_equal @user.companies.count, results.total_entries
72
+ assert_equal 1, results.current_page
73
+ end
74
+
75
+ test "overrides default paginate options" do
76
+ results = @user.cached_companies(:page => 1, :per_page => 3)
77
+ assert_kind_of WillPaginate::Collection, results
78
+ assert_equal 3, results.size
79
+ assert_equal @user.companies.count, results.total_entries
80
+ end
81
+
82
+ test "works for different pages" do
83
+ results = @user.cached_companies(:page => 2, :per_page => 3)
84
+ assert_kind_of WillPaginate::Collection, results
85
+ assert results.size <= 3
86
+ assert_equal @user.companies.count, results.total_entries
87
+ assert_equal 2, results.current_page
88
+ end
89
+
90
+ test "ignores random parameters" do
91
+ result = @user.cached_companies(:invalid => :params, 'random' => 'values', :user_id => 3)
92
+ assert_equal @user.companies, result
93
+ end
94
+
95
+ test "passes on options to find" do
96
+ actual = @user.cached_companies(:order => 'users.id DESC')
97
+ expected = @user.companies
98
+ assert_equal expected, actual
99
+ assert_equal expected.first, actual.first
100
+ end
101
+
102
+ test "caches the count when it gets records" do
103
+ assert_queries(1) do
104
+ @user.cached_companies
105
+ @user.cached_companies_count
106
+ end
107
+ end
108
+
109
+ test "gets the count only if it's requested first" do
110
+ count = @user.companies.count
111
+ assert_queries(1) do
112
+ assert_equal count, @user.cached_companies_count
113
+ assert_equal count, @user.cached_companies_count
114
+ end
115
+ assert_queries(1) do
116
+ assert_equal count, @user.cached_companies.size
117
+ assert_equal count, @user.cached_companies_count
118
+ end
119
+ end
120
+
121
+ test "calling cache_ defines methods on the object" do
122
+ assert !User.method_defined?(:cached_favorite_companies)
123
+ User.cache_favorite_companies(:order => 'name DESC') do
124
+ User.companies
125
+ end
126
+ assert User.respond_to?(:cached_favorite_companies)
127
+ assert_nothing_raised do
128
+ User.method(:cached_favorite_companies)
129
+ end
130
+ end
131
+
132
+ test "applies limit and offset" do
133
+ @user.cached_limit_companies do
134
+ companies
135
+ end
136
+ assert_equal 2, @user.cached_limit_companies(:limit => 2).size
137
+ assert_equal 3, @user.cached_limit_companies(:limit => 3).size
138
+ assert_equal @user.companies.all(:limit => 2, :offset => 1), @user.cached_limit_companies(:limit => 2, :offset => 1)
139
+ assert_equal @user.companies.size, @user.cached_limit_companies.size
140
+ User.cached_successful_limit_companies do
141
+ User.successful
142
+ end
143
+ raise User.cached_successful_limit_companies.inspect
144
+ assert_equal 2, User.cached_successful_limit_companies(:limit => 2).size
145
+ assert_equal 3, User.cached_successful_limit_companies(:limit => 3).size
146
+ assert_equal User.successful.all(:limit => 2, :offset => 1), User.cached_successful_limit_companies(:limit => 2, :offset => 1)
147
+ assert_equal User.successful.size, User.cached_successful_limit_companies.size
148
+ end
149
+
150
+ test "pagination should not result in an extra query" do
151
+ assert_queries(1) do
152
+ @user.cached_big_companies(:page => 1)
153
+ end
154
+ assert_queries(1) do
155
+ User.cached_companies(:page => 1)
156
+ end
157
+ end
158
+
159
+ test "should support a 'force' option" do
160
+ # ActiveRecord caches the result of the proc, so we need to
161
+ # use different instances of the user to test the force option.
162
+ uncached_user = User.first
163
+ companies = @user.companies
164
+ size = companies.size
165
+ assert_queries(1) do
166
+ assert_equal companies, @user.cached_companies
167
+ assert_equal size, @user.cached_companies_count
168
+ assert_equal size, uncached_user.cached_companies_count
169
+ end
170
+ assert_queries(2) do
171
+ assert_equal companies, uncached_user.cached_companies(:force => true)
172
+ assert_equal size, uncached_user.cached_companies_count(:force => true)
173
+ end
174
+ end
175
+
176
+ test "should handle various different model instances" do
177
+ one = User.first
178
+ two = User.first :offset => 1
179
+ assert_not_same one, two
180
+ assert_equal one.companies, one.cached_companies
181
+ assert_equal two.companies, two.cached_companies
182
+ end
183
+
184
+ test "should handle arrays of non-active record instances" do
185
+ assert_equal @user.pet_names, @user.cached_pet_names
186
+ assert_equal @user.pet_names, @user.cached_pet_names
187
+ assert_equal @user.pet_names.count, @user.cached_pet_names_count
188
+ end
189
+
190
+ test "should empty the Rails cache" do
191
+ define_model_cache(User)
192
+ @user.cached_companies
193
+ User.cached_companies
194
+ assert Rails.cache.exist?(@user.arid_cache_key('companies'))
195
+ assert Rails.cache.exist?(User.arid_cache_key('companies'))
196
+ User.clear_cache
197
+ assert Rails.cache.exist?(@user.arid_cache_key('companies'))
198
+ assert Rails.cache.exist?(User.arid_cache_key('companies'))
199
+ end
200
+
201
+ protected
202
+
203
+ def get_user
204
+ @user = User.first
205
+ @user.clear_cache
206
+ define_instance_cache(@user)
207
+ @user
208
+ end
209
+
210
+ def assert_queries(num = 1)
211
+ $query_count = 0
212
+ yield
213
+ ensure
214
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
215
+ end
216
+
217
+ def assert_no_queries(&block)
218
+ assert_queries(0, &block)
219
+ end
220
+
221
+ def define_instance_cache(user)
222
+ user.cache_companies(:per_page => 2) do
223
+ user.companies
224
+ end
225
+ end
226
+
227
+ def define_model_cache(model)
228
+ model.cache_companies(:per_page => 2) do
229
+ model.companies
230
+ end
231
+ end
232
+ end
data/test/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
3
+ libs = []
4
+
5
+ libs << 'irb/completion'
6
+ libs << 'test_helper'
7
+
8
+ exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
@@ -0,0 +1,34 @@
1
+ require 'active_record'
2
+ require 'active_record/fixtures'
3
+
4
+ # Create an in-memory test database and load the fixures into it
5
+ ActiveRecord::Base.establish_connection(
6
+ :adapter => "sqlite3",
7
+ :database => ":memory:"
8
+ )
9
+
10
+ # Schema
11
+ ActiveRecord::Base.silence do
12
+ ActiveRecord::Migration.verbose = false
13
+ load(File.join(File.dirname(__FILE__), 'schema.rb'))
14
+ end
15
+
16
+ # Fixtures
17
+ ActiveRecord::Base.silence do
18
+ Fixtures.create_fixtures(File.join(File.dirname(__FILE__), '..', 'fixtures'), ActiveRecord::Base.connection.tables)
19
+ end
20
+
21
+ # Models
22
+ Dir[File.join(File.dirname(__FILE__), '..', 'models', '*.rb')].each { |f| require f }
23
+
24
+ class << ActiveRecord::Base.connection
25
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /]
26
+
27
+ def execute_with_counting(sql, name = nil, &block)
28
+ $query_count ||= 0
29
+ $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
30
+ execute_without_counting(sql, name, &block)
31
+ end
32
+
33
+ alias_method_chain :execute, :counting
34
+ end
data/test/db/schema.rb ADDED
@@ -0,0 +1,13 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table "users", :force => true do |t|
3
+ t.column "name", :text
4
+ t.column "email", :text
5
+ end
6
+
7
+ create_table "companies", :force => true do |t|
8
+ t.column "name", :text
9
+ t.column "owner_id", :integer
10
+ t.column "country_id", :integer
11
+ t.column "employees", :integer
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ <% for digit in 1..15 %>
2
+ acme_<%= digit %>:
3
+ name: Acme <%= digit %>
4
+ owner_id: <%= rand(10) %>
5
+ employees: <%= rand(200) %>
6
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% for digit in 1..10 %>
2
+ user_<%= digit %>:
3
+ name: Bob <%= digit %>
4
+ email: bob<%= digit %>@ibm.com
5
+ <% end %>
File without changes
@@ -0,0 +1,5 @@
1
+
2
+ class Company < ActiveRecord::Base
3
+ named_scope :owned, :conditions => ['owner_id is not null']
4
+ belongs_to :owner, :class_name => :User
5
+ end
@@ -0,0 +1,23 @@
1
+ require 'arid_cache'
2
+
3
+ class User < ActiveRecord::Base
4
+ has_many :companies, :foreign_key => :owner_id
5
+ named_scope :companies, :joins => :companies
6
+ named_scope :successful, :joins => :companies, :conditions => 'companies.employees > 50'
7
+
8
+ def big_companies
9
+ companies.find :all, :conditions => [ 'employees > 20' ]
10
+ end
11
+
12
+ def pet_names
13
+ ['Fuzzy', 'Peachy']
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if method == :is_high?
18
+ true
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'active_support'
7
+ require 'active_support/test_case'
8
+ require 'test/unit' # required by ActiveSupport::TestCase
9
+ require 'will_paginate'
10
+ require 'ruby-debug'
11
+
12
+ # Activate ARID Cache
13
+ require 'arid_cache'
14
+ AridCache.init_rails
15
+
16
+ # Setup logging
17
+ log = File.expand_path(File.join(File.dirname(__FILE__), 'log', 'test.log'))
18
+ RAILS_DEFAULT_LOGGER = ENV["STDOUT"] ? Logger.new(STDOUT) : Logger.new(log)
19
+
20
+ # Setup an in memory-cache
21
+ RAILS_CACHE = ActiveSupport::Cache.lookup_store(:memory_store)
22
+
23
+ # Mock Rails
24
+ Rails = Class.new do
25
+ cattr_accessor :logger, :cache
26
+ def self.cache
27
+ return RAILS_CACHE
28
+ end
29
+ def self.logger
30
+ return RAILS_DEFAULT_LOGGER
31
+ end
32
+ end
33
+
34
+ # Set loggers for all frameworks
35
+ for framework in ([ :active_record, :action_controller, :action_mailer ])
36
+ if Object.const_defined?(framework.to_s.camelize)
37
+ framework.to_s.camelize.constantize.const_get("Base").logger = Rails.logger
38
+ end
39
+ end
40
+ ActiveSupport::Dependencies.logger = Rails.logger
41
+ Rails.cache.logger = Rails.logger
42
+
43
+ # Include this last otherwise the logger isn't set properly
44
+ require 'db/prepare'
45
+
46
+ ActiveRecord::Base.logger.info("#{"="*25} RUNNING UNIT TESTS #{"="*25}\n\t\t\t#{Time.now.to_s}\n#{"="*70}")
47
+
48
+
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arid_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Karl Varga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-25 00:00:00 +08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: will_paginate
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.9
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: will_paginate
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: |
46
+ ARID Cache makes caching easy and effective. ARID cache supports caching on all your model named scopes, class methods and instance methods right out of the box. ARID cache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
47
+ ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
48
+
49
+ email: kjvarga@gmail.com
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - LICENSE
56
+ - README.rdoc
57
+ files:
58
+ - .gitignore
59
+ - LICENSE
60
+ - README.rdoc
61
+ - Rakefile
62
+ - VERSION
63
+ - arid_cache.gemspec
64
+ - init.rb
65
+ - lib/arid_cache.rb
66
+ - lib/arid_cache/active_record.rb
67
+ - lib/arid_cache/cache_proxy.rb
68
+ - lib/arid_cache/helpers.rb
69
+ - lib/arid_cache/store.rb
70
+ - rails/init.rb
71
+ - spec/arid_cache_spec.rb
72
+ - spec/spec.opts
73
+ - spec/spec_helper.rb
74
+ - tasks/arid_cache_tasks.rake
75
+ - test/arid_cache_test.rb
76
+ - test/console
77
+ - test/db/prepare.rb
78
+ - test/db/schema.rb
79
+ - test/fixtures/companies.yml
80
+ - test/fixtures/users.yml
81
+ - test/log/.gitignore
82
+ - test/models/company.rb
83
+ - test/models/user.rb
84
+ - test/test_helper.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/kjvarga/arid_cache
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options:
91
+ - --charset=UTF-8
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: "0"
99
+ version:
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ version:
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.3.5
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.
113
+ test_files:
114
+ - spec/arid_cache_spec.rb
115
+ - spec/spec_helper.rb
116
+ - test/arid_cache_test.rb
117
+ - test/db/prepare.rb
118
+ - test/db/schema.rb
119
+ - test/models/company.rb
120
+ - test/models/user.rb
121
+ - test/test_helper.rb