cached-models 0.0.2

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/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