cached-models 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,84 @@
1
+ *0.0.2 (October 10th, 2008)*
2
+
3
+ * Updated README with new installation instructions
4
+
5
+ * Created separated folder for ActiveRecord
6
+
7
+ * Added dist related Rake tasks
8
+
9
+ * Added gem related files
10
+
11
+ * Added Git related Rake tasks
12
+
13
+ * Removed default configuration for cache lookup
14
+
15
+ * Make sure cache is always used by all the instances which reference the same record
16
+
17
+ * Made independent of Rails
18
+
19
+ * Allow test suite to work without any active cache server
20
+
21
+ * Enhanced AssociationCollection test coverage
22
+
23
+ * ActiveRecord::Base#expire_cache_for now uses the new cache access API
24
+
25
+ * Abstracted ActiveRecord::Base#cache_fetch in order to normalize cache access for <reflection_name>_ids
26
+
27
+ * Reduced the amount of cache hits, caching the status of cached relations with ActiveRecord::Base#cached_associations
28
+
29
+
30
+
31
+ *0.0.1 (September 10th, 2008)*
32
+
33
+ * Updated README with project informations
34
+
35
+ * Make sure 'test' is the default Rake task
36
+
37
+ * Added project description to README. Added about.yml.
38
+
39
+ * Updated README with informations about required environment settings
40
+
41
+ * Only load the plugin if the current environment has the cache turned on
42
+
43
+ * Added support for cache expiration on after_save callback
44
+
45
+ * Make sure to use ActiveRecord cache proxy for test suite
46
+
47
+ * Make sure test suite will run using RAILS_ENV in test mode
48
+
49
+ * Added support for scoped finders in AssociationCollection. Fixed cache renewal for AssociationCollection#delete.
50
+
51
+ * Added support for cache renewal on AssociationCollection methods
52
+
53
+ * Added support for cache expiration on direct associated objects updates
54
+
55
+ * Updated README example
56
+
57
+ * Removed CacheObserver. Fixed cache expiration for has_many relation.
58
+
59
+ * Introducing CacheObserver in order to transparently handle cache expiring for has_many macro
60
+
61
+ * Test enhancements for AssociationCollection#<< on polymorphic associations
62
+
63
+ * Test enhancements for AssociationCollection#<<. Make sure to expire caches when an associated object changes owner.
64
+
65
+ class Author < ActiveRecord::Base
66
+ has_many :posts, :cached => true
67
+ end
68
+
69
+ post = author.posts.last
70
+ another_author.posts << post # => refresh both author and another_author caches
71
+
72
+ * AssociationCollection#<< support
73
+
74
+ class Author < ActiveRecord::Base
75
+ has_many :posts, :cached => true
76
+ end
77
+
78
+ author.posts << post # => causes a refresh of cached posts
79
+
80
+ * has_many association support
81
+
82
+ class Author < ActiveRecord::Base
83
+ has_many :posts, :cached => true
84
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Luca Guidi
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.
data/README ADDED
@@ -0,0 +1,95 @@
1
+ = CachedModels
2
+
3
+ CachedModels provides to your models a transparent approach to use Rails internal caching mechanism.
4
+
5
+ Check for news and tutorials at the {project home page}[http://www.lucaguidi.com/pages/cached_models].
6
+
7
+
8
+
9
+ = Usage
10
+
11
+ Using Memcached and Rails 2.2.1
12
+
13
+ Make sure to configure your current environment with:
14
+
15
+ config.cache_classes = true
16
+ config.action_controller.perform_caching = true
17
+ config.cache_store = :mem_cache_store
18
+
19
+
20
+
21
+ class Project < ActiveRecord::Base
22
+ has_many :developers, :cached => true
23
+ has_many :tickets, :cached => true
24
+ has_many :recent_tickets, :limit => 5,
25
+ :order => 'id DESC', :cached => true
26
+
27
+ end
28
+
29
+ class Developer < ActiveRecord::Base
30
+ belongs_to :project, :cached => true
31
+ end
32
+
33
+
34
+ Example 1
35
+ project.developers # Database fetch and automatic cache storing
36
+
37
+ developer = project.developers.last
38
+ developer.update_attributes :first_name => 'Luca' # Database update and cache expiration for project cache
39
+
40
+ Example 2
41
+ project2.developers # Database fetch and automatic cache storing
42
+ project2.developers << developer # Database update and cache renewal for both project and project2 caches
43
+
44
+ Example 3
45
+ project.tickets # Database fetch and automatic cache storing
46
+ ticket = project.recent_tickets.first
47
+ ticket.update_attributes :state => 'solved' # Database update and cache expiration for both tickets and recent_tickets entries
48
+
49
+
50
+ = Install
51
+
52
+ There are three ways to install CachedModels
53
+
54
+ Gemified plugin:
55
+
56
+ environment.rb
57
+
58
+ Rails::Initializer.run do |config|
59
+ config.gem 'cached-models'
60
+ end
61
+
62
+ $ (sudo) rake gems:install
63
+ $ rake gems:unpack
64
+
65
+ Rails plugin:
66
+
67
+ $ ./script/plugin install git://github.com/jodosha/cached-models.git
68
+
69
+ Standalone:
70
+
71
+ $ (sudo) gem install cached-models
72
+
73
+ in your project:
74
+
75
+ require 'rubygems'
76
+ require 'activerecord'
77
+ require 'cached-models'
78
+
79
+ ActiveRecord::Base.rails_cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, 'localhost')
80
+
81
+
82
+
83
+ = Contribute
84
+
85
+ * Check out the code and test it:
86
+ $ git clone git://github.com/jodosha/cached-models.git
87
+ $ rake cached_models
88
+
89
+ * Create a ticket to the {Sushistar Lighthouse page}[http://sushistar.lighthouseapp.com]
90
+
91
+ * Create a patch and add as attachment to the ticket.
92
+
93
+
94
+
95
+ Copyright (c) 2008 Luca Guidi, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,81 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ version = '0.0.2'
6
+ repositories = %w( origin rubyforge )
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the cached_models plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the cached_models plugin.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'CachedModels'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ desc 'Build and install the gem (useful for development purposes).'
28
+ task :install do
29
+ system "gem build cached-models.gemspec"
30
+ system "sudo gem uninstall cached-models"
31
+ system "sudo gem install --local --no-rdoc --no-ri cached-models-#{version}.gem"
32
+ system "rm cached-models-*.gem"
33
+ end
34
+
35
+ desc 'Build and prepare files for release.'
36
+ task :dist => :clean do
37
+ require 'cached-models'
38
+ system "gem build cached-models.gemspec"
39
+ system "cd .. && tar -czf cached-models-#{version}.tar.gz cached_models"
40
+ system "cd .. && tar -cjf cached-models-#{version}.tar.bz2 cached_models"
41
+ system "cd .. && mv cached-models-* cached_models"
42
+ end
43
+
44
+ desc 'Clean the working copy from release files.'
45
+ task :clean do
46
+ system "rm cached-models-#{version}.gem" if File.exist? "cached-models-#{version}.gem"
47
+ system "rm cached-models-#{version}.tar.gz" if File.exist? "cached-models-#{version}.tar.gz"
48
+ system "rm cached-models-#{version}.tar.bz2" if File.exist? "cached-models-#{version}.tar.bz2"
49
+ end
50
+
51
+ desc 'Show the file list for the gemspec file'
52
+ task :files do
53
+ puts "Files:\n #{Dir['**/*'].reject {|f| File.directory?(f)}.sort.inspect}"
54
+ puts "Test files:\n #{Dir['test/**/*_test.rb'].reject {|f| File.directory?(f)}.sort.inspect}"
55
+ end
56
+
57
+ namespace :git do
58
+ desc 'Push local Git commits to all remote centralized repositories.'
59
+ task :push do
60
+ repositories.each do |repository|
61
+ puts "Pushing #{repository}...\n"
62
+ system "git push #{repository} master"
63
+ end
64
+ end
65
+
66
+ desc 'Perform a git-tag'
67
+ task :tag do
68
+ puts "Please enter the tag name: "
69
+ tag_name = STDIN.gets.chomp
70
+ exit(1) if tag_name.nil? or tag_name.empty?
71
+ system %(git tag -s #{tag_name} -m "Tagged #{tag_name}")
72
+ end
73
+
74
+ desc 'Push all the tags to remote centralized repositories.'
75
+ task :push_tags do
76
+ repositories.each do |repository|
77
+ puts "Pushing tags to #{repository}...\n"
78
+ system "git push --tags #{repository}"
79
+ end
80
+ end
81
+ end
data/about.yml ADDED
@@ -0,0 +1,8 @@
1
+ author: Luca Guidi
2
+ email: guidi.luca@gmail.com
3
+ homepage: http://lucaguidi.com/pages/cached_models
4
+ summary: CachedModels provides to your models a transparent approach to use Rails internal caching mechanism.
5
+ description: CachedModels provides to your models a transparent approach to use Rails internal caching mechanism.
6
+ license: MIT
7
+ rails_version: 2.1.1+
8
+ version: 0.0.1
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "cached-models"
3
+ s.version = "0.0.2"
4
+ s.date = "2008-10-10"
5
+ s.summary = "Transparent caching policy for your models"
6
+ s.author = "Luca Guidi"
7
+ s.email = "guidi.luca@gmail.com"
8
+ s.homepage = "http://lucaguidi.com/pages/cached_models"
9
+ s.description = "CachedModels provides to your ActiveRecord models a transparent approach to use ActiveSupport caching mechanism."
10
+ s.has_rdoc = true
11
+ s.rubyforge_project = %q{cached-models}
12
+ s.files = ["CHANGELOG", "MIT-LICENSE", "README", "Rakefile", "about.yml", "cached-models.gemspec", "init.rb", "install.rb", "lib/activerecord/lib/active_record.rb", "lib/activerecord/lib/active_record/associations.rb", "lib/activerecord/lib/active_record/associations/association_collection.rb", "lib/activerecord/lib/active_record/associations/association_proxy.rb", "lib/activerecord/lib/active_record/associations/has_many_association.rb", "lib/activerecord/lib/active_record/base.rb", "lib/cached-models.rb", "lib/cached_models.rb", "setup.rb", "tasks/cached_models_tasks.rake", "test/active_record/associations/has_many_association_test.rb", "test/active_record/base_test.rb", "test/fixtures/authors.yml", "test/fixtures/blogs.yml", "test/fixtures/comments.yml", "test/fixtures/posts.yml", "test/fixtures/tags.yml", "test/models/author.rb", "test/models/blog.rb", "test/models/comment.rb", "test/models/post.rb", "test/models/tag.rb", "test/test_helper.rb", "uninstall.rb"]
13
+ s.test_files = ["test/active_record/associations/has_many_association_test.rb",
14
+ "test/active_record/base_test.rb"]
15
+ s.extra_rdoc_files = ['README', 'CHANGELOG']
16
+
17
+ s.add_dependency("activesupport", ["> 2.1.0"])
18
+ s.add_dependency("activerecord", ["> 2.1.0"])
19
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require 'cached_models'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,95 @@
1
+ require 'set'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class AssociationCollection < AssociationProxy #:nodoc:
6
+ def find(*args)
7
+ expects_array = args.first.kind_of?(Array)
8
+ ids = args.flatten.compact.uniq.map(&:to_i)
9
+
10
+ if @reflection.options[:cached]
11
+ result = @owner.send(:cache_read, @reflection)
12
+ if result
13
+ result = result.select { |record| ids.include? record.id }
14
+ result = expects_array ? result : result.first
15
+ return result
16
+ end
17
+ end
18
+
19
+ options = args.extract_options!
20
+
21
+ # If using a custom finder_sql, scan the entire collection.
22
+ if @reflection.options[:finder_sql]
23
+ if ids.size == 1
24
+ id = ids.first
25
+ record = load_target.detect { |r| id == r.id }
26
+ expects_array ? [ record ] : record
27
+ else
28
+ load_target.select { |r| ids.include?(r.id) }
29
+ end
30
+ else
31
+ conditions = "#{@finder_sql}"
32
+ if sanitized_conditions = sanitize_sql(options[:conditions])
33
+ conditions << " AND (#{sanitized_conditions})"
34
+ end
35
+
36
+ options[:conditions] = conditions
37
+
38
+ if options[:order] && @reflection.options[:order]
39
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
40
+ elsif @reflection.options[:order]
41
+ options[:order] = @reflection.options[:order]
42
+ end
43
+
44
+ # Build options specific to association
45
+ construct_find_options!(options)
46
+
47
+ merge_options_from_reflection!(options)
48
+
49
+ # Pass through args exactly as we received them.
50
+ args << options
51
+ @reflection.klass.find(*args)
52
+ end
53
+ end
54
+
55
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
56
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
57
+ def <<(*records)
58
+ result = true
59
+ load_target if @owner.new_record?
60
+
61
+ @owner.transaction do
62
+ flatten_deeper(records).each do |record|
63
+ raise_on_type_mismatch(record)
64
+ add_record_to_target_with_callbacks(record) do |r|
65
+ result &&= insert_record(record) unless @owner.new_record?
66
+ end
67
+ end
68
+ end
69
+
70
+ @owner.send(:cache_write, @reflection, self) if @reflection.options[:cached]
71
+
72
+ result && self
73
+ end
74
+
75
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
76
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
77
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
78
+ def size
79
+ if @reflection.options[:cached]
80
+ result = @owner.send(:cache_read, @reflection)
81
+ return result.to_ary.size if result
82
+ end
83
+
84
+ if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
85
+ @target.size
86
+ elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
87
+ unsaved_records = @target.select { |r| r.new_record? }
88
+ unsaved_records.size + count_records
89
+ else
90
+ count_records
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,39 @@
1
+ require 'set'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class AssociationProxy
6
+ protected
7
+ def set_belongs_to_association_for(record)
8
+ reset_association_cache(record) if @reflection.options[:cached]
9
+
10
+ if @reflection.options[:as]
11
+ record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
12
+ record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
13
+ else
14
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
15
+ end
16
+ end
17
+
18
+ private
19
+ def reset_association_cache(record)
20
+ current_owner = current_owner(record)
21
+ return unless current_owner
22
+ current_owner.send(:cache_delete, @reflection)
23
+ end
24
+
25
+ def current_owner(record)
26
+ current_owner_id, current_owner_type = if @reflection.options[:as]
27
+ [ record["#{@reflection.options[:as]}_id"],
28
+ record["#{@reflection.options[:as]}_type"] ]
29
+ else
30
+ [ record[@reflection.primary_key_name],
31
+ @owner.class.base_class.name.to_s ]
32
+ end
33
+
34
+ return unless current_owner_id
35
+ current_owner_type.constantize.find(current_owner_id)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasManyAssociation < AssociationCollection #:nodoc:
4
+
5
+ end
6
+ end
7
+ end