dzema_dm-counter-cache 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ === 0.9.12 / 2009-11-15
2
+
3
+ * Version without class_eval
4
+
5
+ === 0.9.8 / 2008-12-07
6
+
7
+ * Initial commit
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 Saimon Moore, Dmitriy Dzema
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/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO
7
+ lib/dm-counter-cache.rb
8
+ lib/dm-counter-cache/version.rb
9
+ spec/integration/dm-counter-cache_spec.rb
10
+ spec/integration/multiple_define_spec.rb
11
+ spec/spec.opts
12
+ spec/spec_helper.rb
13
+ tasks/install.rb
14
+ tasks/spec.rb
data/README.txt ADDED
@@ -0,0 +1,23 @@
1
+ == README
2
+
3
+ DataMapper::CounterCacheable automates the dec/incrementing of association counter fields. This is
4
+ similar to counter caches in ActiveRecord.
5
+
6
+ Example:
7
+
8
+ class Post
9
+ include DataMapper::Resource
10
+
11
+ has n :comments
12
+
13
+ property :id, Integer, :serial => true
14
+ property :comments_count, Integer, :default => 0
15
+ end
16
+
17
+ class Comment
18
+ include DataMapper::Resource
19
+ include DataMapper::CounterCacheable
20
+
21
+ belongs_to :post, :counter_cache => true
22
+
23
+ end
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'hoe'
4
+
5
+ ROOT = Pathname(__FILE__).dirname.expand_path
6
+ JRUBY = RUBY_PLATFORM =~ /java/
7
+ WINDOWS = Gem.win_platform?
8
+ SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
9
+
10
+ require ROOT + 'lib/dm-counter-cache/version'
11
+
12
+ AUTHOR = ['Saimon Moore', 'Dmitriy Dzema']
13
+ EMAIL = ['daimonmoore [a] gmail [d] com', 'dima [a] dzema [d] name']
14
+ GEM_NAME = 'dzema_dm-counter-cache'
15
+ GEM_VERSION = DataMapper::CounterCacheable::VERSION
16
+ GEM_DEPENDENCIES = [['dm-core', "=#{GEM_VERSION}"]]
17
+ GEM_CLEAN = %w[ log pkg coverage ]
18
+ GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO History.txt ] }
19
+
20
+ PROJECT_NAME = 'datamapper'
21
+ PROJECT_URL = "http://github.com/DimaD/dm-counter-cache"
22
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY = 'DataMapper plugin for counter caches ala ActiveRecord. Original idea and implementation by Saimon Moore (daimonmoore [a] gmail [d] com)'
23
+
24
+ [ ROOT, ROOT.parent ].each do |dir|
25
+ Pathname.glob(dir.join('tasks/**/*.rb').to_s).each { |f| require f }
26
+ end
27
+
28
+ require 'tasks/hoe'
data/TODO ADDED
File without changes
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module CounterCacheable
3
+ VERSION = '0.9.12'
4
+ end
5
+ end
@@ -0,0 +1,109 @@
1
+ module DataMapper
2
+ # CounterCacheable allows you to transparently maintain counts on collection association of this model on the parent model.
3
+ # You can also specify a custom counter cache column by providing a column name instead of a true/false value
4
+ # to this option (e.g., :counter_cache => :my_custom_counter.)
5
+ module CounterCacheable
6
+
7
+ def self.included(klass)
8
+ DataMapper::Associations::ManyToOne.module_eval do
9
+ extend DataMapper::CounterCacheable::ClassMethods
10
+
11
+ (class << self; self; end).class_eval do
12
+ unless method_defined?(:setup_without_counter_caching)
13
+ alias_method :setup_without_counter_caching, :setup
14
+ alias_method :setup, :setup_with_counter_caching
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ def setup_with_counter_caching(name, model, options = {})
24
+ perform_counter_cache = options.delete(:counter_cache)
25
+
26
+ relationship = setup_without_counter_caching(name, model, options)
27
+
28
+ if perform_counter_cache
29
+ counter_cache_attribute = case perform_counter_cache
30
+ when String, Symbol
31
+ perform_counter_cache.to_sym
32
+ else
33
+ "#{model.storage_name}_count".to_sym
34
+ end
35
+
36
+ model.extend(ModelClassMethods)
37
+ model.__send__(:include, InstanceMethods)
38
+
39
+ model.define_counter_cache_callbacks_for(relationship, counter_cache_attribute)
40
+ end
41
+
42
+ relationship
43
+ end
44
+
45
+ end # ClassMethods
46
+
47
+ module ModelClassMethods
48
+ def define_counter_cache_callbacks_for(relationship, counter_cache_attribute)
49
+ return if defined_counter_cache_callbacks_for?(counter_cache_attribute)
50
+
51
+ self.after(:create) { adjust_counter_cache_for(relationship, counter_cache_attribute, +1) }
52
+ self.after(:destroy) { adjust_counter_cache_for(relationship, counter_cache_attribute, -1) }
53
+
54
+ counter_caches[counter_cache_attribute] = true
55
+ end # register_counter_cache_callbacks_for(relationship, counter_cache_attribute)
56
+
57
+ def defined_counter_cache_callbacks_for?(attribute)
58
+ !counter_caches[attribute].nil?
59
+ end # defined_counter_cache_callbacks_for?(attribute)
60
+
61
+ def counter_caches
62
+ @_counter_caches ||= {}
63
+ end # counter_caches
64
+ end
65
+
66
+ module InstanceMethods
67
+ protected
68
+ def adjust_counter_cache_for(relationship, counter_cache_attribute, amount)
69
+ association = get_association(relationship)
70
+
71
+ return unless relationship.parent_model.properties.has_property?(counter_cache_attribute)
72
+ association.update_attributes(counter_cache_attribute => association.reload.__send__(counter_cache_attribute) + amount)
73
+ end
74
+
75
+ def get_association(relationship)
76
+ self.__send__("#{relationship.name}_association".to_sym)
77
+ end # get_association(name)
78
+ end # InstanceMethods
79
+
80
+ end # CounterCacheable
81
+ end # DataMapper
82
+
83
+ if $0 == __FILE__
84
+ require 'rubygems'
85
+
86
+ gem 'dm-core', '~>0.9.8'
87
+ require 'dm-core'
88
+
89
+ FileUtils.touch(File.join(Dir.pwd, "migration_test.db"))
90
+ DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/migration_test.db")
91
+
92
+ class Post
93
+ include DataMapper::Resource
94
+
95
+ property :id, Integer, :serial => true
96
+ has n, :comments
97
+ end
98
+ Post.auto_migrate!
99
+
100
+ class Comment
101
+ include DataMapper::Resource
102
+ include DataMapper::CounterCacheable
103
+
104
+ belongs_to :post, :counter_cache => true
105
+ end
106
+ Comments.auto_migrate!
107
+
108
+ Post.create.comments.create
109
+ end
@@ -0,0 +1,87 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ describe DataMapper::CounterCacheable do
5
+ before :all do
6
+ class Post
7
+ include DataMapper::Resource
8
+
9
+ property :id, Integer, :serial => true
10
+ property :comments_count, Integer, :default => 0
11
+ has n, :comments
12
+ end
13
+
14
+ class Comment
15
+ include DataMapper::Resource
16
+ include DataMapper::CounterCacheable
17
+
18
+ property :id, Integer, :serial => true
19
+ belongs_to :post, :counter_cache => true
20
+
21
+ end
22
+
23
+ class User
24
+ include DataMapper::Resource
25
+
26
+ property :id, Integer, :serial => true
27
+ property :groups_count, Integer, :default => 0
28
+
29
+ has n, :group_memberships
30
+ has n, :groups, :through => :group_memberships, :class_name => "Group", :remote_name => :group, :parent_key => [:id], :child_key => [:user_id]
31
+ end
32
+
33
+ class Group
34
+ include DataMapper::Resource
35
+
36
+ property :id, Integer, :serial => true
37
+ property :members_count, Integer, :default => 0
38
+ has n, :group_memberships
39
+ has n, :members, :through => :group_memberships, :class_name => "User", :remote_name => :user, :parent_key => [:id], :child_key => [:group_id]
40
+ end
41
+
42
+ class GroupMembership
43
+ include DataMapper::Resource
44
+ include DataMapper::CounterCacheable
45
+
46
+ property :id, Serial
47
+
48
+ belongs_to :group, :counter_cache => :members_count
49
+ belongs_to :member, :class_name => "User", :child_key => [:user_id], :counter_cache => :groups_count
50
+ end
51
+
52
+ GroupMembership.auto_migrate!
53
+ User.auto_migrate!
54
+ Group.auto_migrate!
55
+ Comment.auto_migrate!
56
+ Post.auto_migrate!
57
+ end
58
+
59
+ before(:each) do
60
+ @post = Post.create
61
+ @user = User.create
62
+ @group = Group.create
63
+ end
64
+
65
+ it "should increment comments_count" do
66
+ @post.comments.create
67
+ @post.reload.comments_count.should == 1
68
+
69
+ @user.group_memberships.create(:group => @group)
70
+ @user.reload.groups_count.should == 1
71
+ @group.reload.members_count.should == 1
72
+ end
73
+
74
+ it "should decrement comments_count" do
75
+ comment1 = @post.comments.create
76
+ comment2 = @post.comments.create
77
+ comment2.destroy
78
+ @post.reload.comments_count.should == 1
79
+
80
+ gm1 = @user.group_memberships.create(:group => @group)
81
+ gm2 = @user.group_memberships.create(:group => @group)
82
+ gm2.destroy
83
+ @user.reload.groups_count.should == 1
84
+ @group.reload.members_count.should == 1
85
+ end
86
+
87
+ end
@@ -0,0 +1,50 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe DataMapper::CounterCacheable do
4
+ context "when attribute with counter_cache declared twice" do
5
+ module Multiple
6
+ class Post
7
+ include DataMapper::Resource
8
+
9
+ property :id, Integer, :serial => true
10
+ property :comments_count, Integer, :default => 0
11
+ has n, :comments, :class_name => 'Multiple::Comment'
12
+ end
13
+
14
+ class Comment
15
+ include DataMapper::Resource
16
+ include DataMapper::CounterCacheable
17
+
18
+ property :id, Integer, :serial => true
19
+ belongs_to :post, :class_name => 'Multiple::Post', :counter_cache => :comments_count
20
+ belongs_to :post, :class_name => 'Multiple::Post', :counter_cache => :comments_count
21
+ end
22
+
23
+ Comment.auto_migrate!
24
+ Post.auto_migrate!
25
+ end
26
+
27
+ before :each do
28
+ @post = Multiple::Post.create
29
+ end
30
+
31
+ it "should increment counter only once" do
32
+ lambda do
33
+ @post.comments.create
34
+ @post.reload
35
+ end.should change(@post, :comments_count).by(+1)
36
+ end
37
+
38
+ it "should decrement counter only once" do
39
+ comment1 = @post.comments.create
40
+ comment2 = @post.comments.create
41
+ @post.reload
42
+
43
+ lambda do
44
+ comment1.destroy
45
+ @post.reload
46
+ end.should change(@post, :comments_count).by(-1)
47
+ end
48
+
49
+ end
50
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,29 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+
4
+ gem 'rspec', '>1.1.11'
5
+ require 'spec'
6
+
7
+ gem 'dm-core', '0.9.12'
8
+ require 'dm-core'
9
+
10
+ require Pathname(__FILE__).dirname.parent.expand_path + 'lib/dm-counter-cache'
11
+
12
+ def load_driver(name, default_uri)
13
+ return false if ENV['ADAPTER'] != name.to_s
14
+
15
+ begin
16
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
17
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
18
+ true
19
+ rescue LoadError => e
20
+ warn "Could not load do_#{name}: #{e}"
21
+ false
22
+ end
23
+ end
24
+
25
+ ENV['ADAPTER'] ||= 'sqlite3'
26
+
27
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
28
+ HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
29
+ HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
data/tasks/install.rb ADDED
@@ -0,0 +1,13 @@
1
+ def sudo_gem(cmd)
2
+ sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
3
+ end
4
+
5
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
6
+ task :install => [ :package ] do
7
+ sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
8
+ end
9
+
10
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
11
+ task :uninstall => [ :clobber ] do
12
+ sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -Ix"
13
+ end
data/tasks/spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ begin
2
+ gem 'rspec', '~>1.1.11'
3
+ require 'spec'
4
+ require 'spec/rake/spectask'
5
+
6
+ task :default => [ :spec ]
7
+
8
+ desc 'Run specifications'
9
+ Spec::Rake::SpecTask.new(:spec) do |t|
10
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
11
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
12
+
13
+ begin
14
+ gem 'rcov', '~>0.8'
15
+ t.rcov = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
16
+ t.rcov_opts << '--exclude' << 'spec'
17
+ t.rcov_opts << '--text-summary'
18
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
19
+ rescue LoadError
20
+ # rcov not installed
21
+ end
22
+ end
23
+ rescue LoadError
24
+ # rspec not installed
25
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dzema_dm-counter-cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.12
5
+ platform: ruby
6
+ authors:
7
+ - Saimon Moore
8
+ - Dmitriy Dzema
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-11-15 00:00:00 +10:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: dm-core
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.9.12
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: hoe
28
+ type: :development
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.3.3
35
+ version:
36
+ description: DataMapper plugin for counter caches ala ActiveRecord. Original idea and implementation by Saimon Moore (daimonmoore [a] gmail [d] com)
37
+ email:
38
+ - daimonmoore [a] gmail [d] com
39
+ - dima [a] dzema [d] name
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - README.txt
46
+ - LICENSE
47
+ - TODO
48
+ - History.txt
49
+ files:
50
+ - History.txt
51
+ - LICENSE
52
+ - Manifest.txt
53
+ - README.txt
54
+ - Rakefile
55
+ - TODO
56
+ - lib/dm-counter-cache.rb
57
+ - lib/dm-counter-cache/version.rb
58
+ - spec/integration/dm-counter-cache_spec.rb
59
+ - spec/integration/multiple_define_spec.rb
60
+ - spec/spec.opts
61
+ - spec/spec_helper.rb
62
+ - tasks/install.rb
63
+ - tasks/spec.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/DimaD/dm-counter-cache
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --main
71
+ - README.txt
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ requirements: []
87
+
88
+ rubyforge_project: datamapper
89
+ rubygems_version: 1.3.4
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: DataMapper plugin for counter caches ala ActiveRecord. Original idea and implementation by Saimon Moore (daimonmoore [a] gmail [d] com)
93
+ test_files: []
94
+