plus-plus 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/Rakefile +1 -0
- data/lib/plus_plus/base.rb +71 -0
- data/lib/plus_plus/version.rb +3 -0
- data/lib/plus_plus.rb +16 -0
- data/plus_plus.gemspec +27 -0
- data/spec/base_spec.rb +177 -0
- data/spec/factories.rb +58 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/models.rb +25 -0
- data/spec/support/schema.rb +29 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 75636e78a69609289209b2e8faf897cf7ec13233
|
4
|
+
data.tar.gz: 887868671b37715daf9e5f3b4a61cc97550485b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 20b8a936ff4232df2a37776e8b92fa4132f8b5201882beabd0b393310f3c7cedc9fb20e8338342885bb8b07e662f24141b752ccc879452c5cd978c5c61b91d36
|
7
|
+
data.tar.gz: 70f2cc035ed493b8ad174f8d3ae6673ed981f0e1db66a0e453a9abdd435fdfbb5693b24b2a3c06eac7e19795d4eb298429217139a82b4304497fb018de01d8ce
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jamie Davidson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# PlusPlus (++)
|
2
|
+
|
3
|
+
Automatically increment/decrement integer columns with any value obeying any condition. Essentially, a more powerful form of Rails' counter_cache.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
```ruby
|
9
|
+
gem 'plus-plus'
|
10
|
+
```
|
11
|
+
|
12
|
+
## Getting Started
|
13
|
+
The simplest use case for ++ is to simply increment/decrement a column keeping track of the count of an association (just like counter_cache):
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class Article < ActiveRecord::Base
|
17
|
+
has_many :comments
|
18
|
+
end
|
19
|
+
|
20
|
+
class Comment < ActiveRecord::Base
|
21
|
+
include PlusPlus
|
22
|
+
belongs_to :article
|
23
|
+
|
24
|
+
plus_plus :article, :comments_count
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
article = Article.create! content: "What an article!"
|
30
|
+
puts article.comments_count # 0
|
31
|
+
comment = Comment.create! content: "You're right, that's one hell of an article!", article: article
|
32
|
+
puts article.comments_count # 1
|
33
|
+
comment.destroy
|
34
|
+
puts article.comments_count # 0
|
35
|
+
```
|
36
|
+
|
37
|
+
Simple enough! But what if we wanted to ignore a comment based on some condition?
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class Comment < ActiveRecord::Base
|
41
|
+
include PlusPlus
|
42
|
+
belongs_to :article
|
43
|
+
|
44
|
+
plus_plus :article, :comments_count, unless: proc { fake_comment } # Only increase if the comment is legit. fake_comment can be an instance method, another column, etc.
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Well, isn't that swell! But what if the owner of the comment toggles their comment to no longer be fake after it's already been created? Fine, geez!
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class Comment < ActiveRecord::Base
|
52
|
+
include PlusPlus
|
53
|
+
belongs_to :article
|
54
|
+
|
55
|
+
plus_plus :article, :comments_count, unless: proc { fake_comment } # Only increase if the comment is legit
|
56
|
+
plus_plus_on_change :article, :comments_count, changed: :fake_comment, plus: false, minus: true
|
57
|
+
```
|
58
|
+
|
59
|
+
Ok ok, I see where you're going with this. But hey, I want to work in some gamification and up the user's score when they write articles and comments. Can I do that good sir?
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class User < ActiveRecord::Base
|
63
|
+
has_many :articles
|
64
|
+
has_many :comments
|
65
|
+
end
|
66
|
+
|
67
|
+
class Article < ActiveRecord::Base
|
68
|
+
include PlusPlus
|
69
|
+
belongs_to :user
|
70
|
+
has_many :comments
|
71
|
+
|
72
|
+
plus_plus :user, :score, value: proc { content.length }
|
73
|
+
plus_plus :user, :articles_count, if: proc { published }
|
74
|
+
plus_plus_on_change :user, :articles_count, changed: :published, plus: true, minus: false
|
75
|
+
end
|
76
|
+
|
77
|
+
class Comment < ActiveRecord::Base
|
78
|
+
include PlusPlus
|
79
|
+
belongs_to :user
|
80
|
+
belongs_to :article
|
81
|
+
|
82
|
+
plus_plus :user, :comments_count
|
83
|
+
plus_plus :user, :score, value: 5, update_method: :update_attributes, unless: proc { fake_comment }
|
84
|
+
plus_plus :article, :comments_count, unless: proc { fake_comment }
|
85
|
+
plus_plus_on_change :article, :comments_count, changed: :fake_comment, plus: false, minus: true
|
86
|
+
plus_plus_on_change :user, :score, changed: :fake_comment, plus: proc { !fake_comment }, minus: proc { fake_comment }, value: 5, update_method: :update_attributes
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
### Options
|
91
|
+
Let's bring it all home now:
|
92
|
+
|
93
|
+
**plus_plus**
|
94
|
+
- value: Defaults to 1. Can be set to a static integer value or a proc
|
95
|
+
- if: Only update if this condition is satisifed
|
96
|
+
- unless: Update unless this condition is satisifed
|
97
|
+
- update_method: Defaults to update_columns to avoid triggering callbacks and to be as fast as possible. Set to update_attributes or your own custom method if you prefer callbacks or something different.
|
98
|
+
|
99
|
+
**plus_plus_on_change**
|
100
|
+
- changed: (Required) The column to monitor for a change
|
101
|
+
- plus: (Required) The condition that must be satisfied in order to increment the column. Can be a proc (that evaluates to true/false) or a static value that will be checked for equality against the changed column
|
102
|
+
- minus: (Required) The condition that must be satisfied in order to decrement the column. Can be a proc (that evaluates to true/false) or a static value that will be checked for equality against the changed column
|
103
|
+
- value: Defaults to 1. Can be set to a static integer value or a proc
|
104
|
+
- update_method: Defaults to update_columns to avoid triggering callbacks and to be as fast as possible. Set to update_attributes or your own custom method if you prefer callbacks or something different.
|
105
|
+
|
106
|
+
The MIT License (MIT)
|
107
|
+
---------------------
|
108
|
+
Copyright (c) 2014 Pathgather
|
109
|
+
|
110
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
111
|
+
this software and associated documentation files (the "Software"), to deal in
|
112
|
+
the Software without restriction, including without limitation the rights to
|
113
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
114
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
115
|
+
subject to the following conditions:
|
116
|
+
|
117
|
+
The above copyright notice and this permission notice shall be included in all
|
118
|
+
copies or substantial portions of the Software.
|
119
|
+
|
120
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
121
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
122
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
123
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
124
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
125
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module PlusPlus
|
2
|
+
module Base
|
3
|
+
def plus_plus(*args)
|
4
|
+
options = args.extract_options!
|
5
|
+
association, column = args
|
6
|
+
|
7
|
+
self.after_create {
|
8
|
+
self.plus_plus_on_create_or_destroy association, column, options
|
9
|
+
}
|
10
|
+
|
11
|
+
self.after_destroy {
|
12
|
+
self.plus_plus_on_create_or_destroy association, column, options
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def plus_plus_on_change(*args)
|
17
|
+
options = args.extract_options!
|
18
|
+
association, column = args
|
19
|
+
|
20
|
+
self.after_update do
|
21
|
+
association_model = self.send(association)
|
22
|
+
raise "No association #{association}" if association_model.nil?
|
23
|
+
raise "No :changed option specified" if options[:changed].nil?
|
24
|
+
raise "No :plus option specified" if options[:plus].nil?
|
25
|
+
raise "No :minus option specified" if options[:minus].nil?
|
26
|
+
return unless self.changes.include?(options[:changed])
|
27
|
+
|
28
|
+
dup = self.dup
|
29
|
+
changed = options[:changed]
|
30
|
+
offset = if options[:value]
|
31
|
+
options[:value].respond_to?(:call) ? self.instance_exec(&options[:value]) : options[:value]
|
32
|
+
else
|
33
|
+
1
|
34
|
+
end
|
35
|
+
|
36
|
+
self.changes.each { |k, v| dup[k] = v.first } # Create a 'snapshot' of what the model did look like
|
37
|
+
prev_satisfied_for_minus = options[:minus].respond_to?(:call) ? dup.instance_exec(&options[:minus]) : dup.send(changed) == options[:minus]
|
38
|
+
self_satisfied_for_plus = options[:plus].respond_to?(:call) ? self.instance_exec(&options[:plus]) : self.send(changed) == options[:plus]
|
39
|
+
self_satisfied_for_minus = options[:minus].respond_to?(:call) ? self.instance_exec(&options[:minus]) : self.send(changed) == options[:minus]
|
40
|
+
prev_satisfied_for_plus = options[:plus].respond_to?(:call) ? dup.instance_exec(&options[:plus]) : dup.send(changed) == options[:plus]
|
41
|
+
|
42
|
+
updated_val = if prev_satisfied_for_minus && self_satisfied_for_plus
|
43
|
+
association_model.send(column) + offset
|
44
|
+
elsif prev_satisfied_for_plus && self_satisfied_for_minus
|
45
|
+
association_model.send(column) - offset
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
association_model.send options[:update_method] || :update_columns, {column => updated_val} if updated_val
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module Model
|
56
|
+
def plus_plus_on_create_or_destroy(association, column, options)
|
57
|
+
association_model = self.send(association)
|
58
|
+
raise "No association #{association}" unless association_model
|
59
|
+
return if options.has_key?(:if) && !self.instance_exec(&options[:if])
|
60
|
+
return if options.has_key?(:unless) && self.instance_exec(&options[:unless])
|
61
|
+
value = if options[:value]
|
62
|
+
options[:value].respond_to?(:call) ? self.instance_exec(&options[:value]) : options[:value]
|
63
|
+
else
|
64
|
+
1
|
65
|
+
end
|
66
|
+
offset = self.destroyed? ? -(value) : value
|
67
|
+
new_val = association_model.send(column) + offset
|
68
|
+
association_model.send options[:update_method] || :update_columns, {column => new_val}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/plus_plus.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "plus_plus/base"
|
2
|
+
|
3
|
+
module PlusPlus
|
4
|
+
def self.extended(model_class)
|
5
|
+
return if model_class.respond_to? :plus_plus
|
6
|
+
model_class.class_eval do
|
7
|
+
extend Base
|
8
|
+
include Model
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Allow developers to `include` PlusPlus or `extend` it.
|
13
|
+
def self.included(model_class)
|
14
|
+
model_class.extend self
|
15
|
+
end
|
16
|
+
end
|
data/plus_plus.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'plus_plus/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "plus-plus"
|
8
|
+
spec.version = PlusPlus::VERSION
|
9
|
+
spec.authors = ["Pathgather"]
|
10
|
+
spec.email = ["tech@pathgather.com"]
|
11
|
+
spec.description = spec.summary = %q{counter_cache, but much more powerful}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "rspec-rails", "~> 2.14"
|
21
|
+
spec.add_development_dependency "factory_girl", "~> 4.4.0"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "activerecord", "~> 4.1.4"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PlusPlus do
|
4
|
+
let(:article) { FactoryGirl.create(:article) }
|
5
|
+
let(:article_with_comment) { FactoryGirl.create(:article_with_comment) }
|
6
|
+
let(:article_with_subcomment) { FactoryGirl.create(:article_with_subcomment) }
|
7
|
+
let(:user) { FactoryGirl.create(:user) }
|
8
|
+
let(:user_with_comment) { FactoryGirl.create(:user_with_comment) }
|
9
|
+
let(:user_with_published_article) { FactoryGirl.create(:user_with_published_article) }
|
10
|
+
let(:user_with_unpublished_article) { FactoryGirl.create(:user_with_unpublished_article) }
|
11
|
+
|
12
|
+
describe "plus_plus" do
|
13
|
+
context "with the minimal configuration" do
|
14
|
+
it "increases the column by 1 on create" do
|
15
|
+
expect { FactoryGirl.create :comment, user: user }.to change{user.comments_count}.from(0).to(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "decreases the column by 1 on destroy" do
|
19
|
+
expect { user_with_comment.comments[0].destroy }.to change{user_with_comment.comments_count}.from(1).to(0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "updates the association with update_columns by default on create" do
|
23
|
+
user.should_receive(:update_columns).with({comments_count: 1})
|
24
|
+
FactoryGirl.create :comment, user: user
|
25
|
+
end
|
26
|
+
|
27
|
+
it "updates the association with update_columns by default on destroy" do
|
28
|
+
user_with_comment.should_receive(:update_columns).with({comments_count: 0})
|
29
|
+
user_with_comment.comments[0].destroy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with a specified value" do
|
34
|
+
it "increases the column by that value on create" do
|
35
|
+
expect { FactoryGirl.create :comment, user: user }.to change{user.score}.from(0).to(5)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "decreases the column by that value on destroy" do
|
39
|
+
expect { user_with_comment.comments[0].destroy }.to change{user_with_comment.score}.from(5).to(0)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with a dynamic value" do
|
44
|
+
it "increases the column by that value on create" do
|
45
|
+
expect { FactoryGirl.create :article, user: user, content: 'Test' }.to change{user.score}.from(0).to(4)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "decreases the column by that value on destroy" do
|
49
|
+
expect {
|
50
|
+
user_with_published_article.articles[0].destroy
|
51
|
+
}.to change{user_with_published_article.score}.from(user_with_published_article.articles[0].content.length).to(0)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with a different update method" do
|
56
|
+
it "increases the column with that update method on create" do
|
57
|
+
user.should_receive(:update_attributes).with({score: 5})
|
58
|
+
FactoryGirl.create :comment, user: user
|
59
|
+
end
|
60
|
+
|
61
|
+
it "decreases the column with that update method on destroy" do
|
62
|
+
user_with_comment.should_receive(:update_attributes).with({score: 0})
|
63
|
+
user_with_comment.comments[0].destroy
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with an if condition" do
|
68
|
+
context "when statisfied" do
|
69
|
+
it "increases the column by 1 on create" do
|
70
|
+
expect { FactoryGirl.create :published_article, user: user }.to change{user.articles_count}.from(0).to(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "decreases the column by 1 on destroy" do
|
74
|
+
expect { user_with_published_article.articles[0].destroy }.to change{user_with_published_article.articles_count}.from(1).to(0)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when not statisfied" do
|
79
|
+
it "does not increase the column on create" do
|
80
|
+
expect { FactoryGirl.create :article, user: user }.to_not change{user.articles_count}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "does not decrease the column on destroy" do
|
84
|
+
expect { user_with_unpublished_article.articles[0].destroy }.to_not change{user_with_unpublished_article.articles_count}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "with an unless condition" do
|
90
|
+
context "when statisfied" do
|
91
|
+
it "does not increase the column on create" do
|
92
|
+
expect { FactoryGirl.create :subcomment, article: article }.to_not change{article.comments_count}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not decrease the column on destroy" do
|
96
|
+
expect { article_with_subcomment.comments[0].destroy }.to_not change{article_with_subcomment.comments_count}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when not statisfied" do
|
101
|
+
it "increases the column by 1 on create" do
|
102
|
+
expect { FactoryGirl.create :comment, article: article }.to change{article.comments_count}.from(0).to(1)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "decreases the column by 1 on destroy" do
|
106
|
+
expect { article_with_comment.comments[0].destroy }.to change{article_with_comment.comments_count}.from(1).to(0)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "plus_plus_on_change" do
|
113
|
+
context "with the minimal configuration" do
|
114
|
+
context "when the :changed column is changed" do
|
115
|
+
it "updates the association with update_columns by default on create" do
|
116
|
+
subcomment = FactoryGirl.create :subcomment, article: article
|
117
|
+
article.should_receive(:update_columns).with({comments_count: 1})
|
118
|
+
subcomment.update_attributes subcomment: false
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when plus is a set value" do
|
122
|
+
it "increases the column by 1 if :plus is satisfied" do
|
123
|
+
subcomment = FactoryGirl.create :subcomment, article: article
|
124
|
+
article.reload
|
125
|
+
article.comments_count.should == 0
|
126
|
+
subcomment.update_attributes subcomment: false
|
127
|
+
article.reload
|
128
|
+
article.comments_count.should == 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "when plus is a proc" do
|
133
|
+
it "increases the column by the proc value if :plus is satisfied" do
|
134
|
+
subcomment = FactoryGirl.create :subcomment, user: user
|
135
|
+
user.reload
|
136
|
+
user.score.should == 0
|
137
|
+
subcomment.update_attributes subcomment: false
|
138
|
+
user.reload
|
139
|
+
user.score.should == 5
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when minus is a set valus" do
|
144
|
+
it "decreases the column by 1 if :minus is satisfied" do
|
145
|
+
article_with_comment.comments_count.should == 1
|
146
|
+
article_with_comment.comments[0].update_attributes subcomment: true
|
147
|
+
article_with_comment.reload
|
148
|
+
article_with_comment.comments_count.should == 0
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when minus is a proc" do
|
153
|
+
it "decreases the column by 1 if :minus is satisfied" do
|
154
|
+
user_with_comment.score.should == 5
|
155
|
+
user_with_comment.comments[0].update_attributes subcomment: true
|
156
|
+
user_with_comment.reload
|
157
|
+
user_with_comment.score.should == 0
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "with a different update method" do
|
164
|
+
context "when the :changed column is changed" do
|
165
|
+
it "updates the column with that update method" do
|
166
|
+
subcomment = FactoryGirl.create :subcomment, user: user
|
167
|
+
user.reload
|
168
|
+
user.should_receive(:update_attributes).with({score: user.score + 5})
|
169
|
+
subcomment.update_attributes subcomment: false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should raise an error if the association is unknown"
|
176
|
+
it "should raise an error if a column is not specified"
|
177
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
factory :article do
|
5
|
+
content 'Test Article'
|
6
|
+
user
|
7
|
+
published false
|
8
|
+
|
9
|
+
factory :published_article do
|
10
|
+
published true
|
11
|
+
end
|
12
|
+
|
13
|
+
factory :article_with_comment do
|
14
|
+
after(:build) do |article, evaluator|
|
15
|
+
article.comments << FactoryGirl.build_list(:comment, 1, article: nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
factory :article_with_subcomment do
|
20
|
+
after(:build) do |article, evaluator|
|
21
|
+
article.comments << FactoryGirl.build_list(:subcomment, 1, article: nil)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
factory :comment do
|
27
|
+
message 'Test Message'
|
28
|
+
user
|
29
|
+
article
|
30
|
+
subcomment false
|
31
|
+
|
32
|
+
factory :subcomment do
|
33
|
+
subcomment true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
factory :user do
|
38
|
+
name 'Test User'
|
39
|
+
|
40
|
+
factory :user_with_published_article do
|
41
|
+
after(:build) do |user, evaluator|
|
42
|
+
user.articles << FactoryGirl.build_list(:published_article, 1, user: nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
factory :user_with_unpublished_article do
|
47
|
+
after(:build) do |user, evaluator|
|
48
|
+
user.articles << FactoryGirl.build_list(:article, 1, user: nil)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
factory :user_with_comment do
|
53
|
+
after(:build) do |user, evaluator|
|
54
|
+
user.comments << FactoryGirl.build_list(:comment, 1, user: nil)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
has_many :articles
|
3
|
+
has_many :comments
|
4
|
+
end
|
5
|
+
|
6
|
+
class Article < ActiveRecord::Base
|
7
|
+
include PlusPlus
|
8
|
+
belongs_to :user
|
9
|
+
has_many :comments
|
10
|
+
|
11
|
+
plus_plus :user, :score, value: proc { content.length }
|
12
|
+
plus_plus :user, :articles_count, if: proc { published }
|
13
|
+
end
|
14
|
+
|
15
|
+
class Comment < ActiveRecord::Base
|
16
|
+
include PlusPlus
|
17
|
+
belongs_to :user
|
18
|
+
belongs_to :article
|
19
|
+
|
20
|
+
plus_plus :user, :comments_count
|
21
|
+
plus_plus :user, :score, value: 5, update_method: :update_attributes, unless: proc { subcomment }
|
22
|
+
plus_plus :article, :comments_count, unless: proc { subcomment }
|
23
|
+
plus_plus_on_change :article, :comments_count, changed: :subcomment, plus: false, minus: true
|
24
|
+
plus_plus_on_change :user, :score, changed: :subcomment, plus: proc { !subcomment }, minus: proc { subcomment }, value: 5, update_method: :update_attributes
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
self.verbose = false
|
3
|
+
|
4
|
+
create_table :articles, :force => true do |t|
|
5
|
+
t.integer :user_id
|
6
|
+
t.text :content
|
7
|
+
t.boolean :published, default: false
|
8
|
+
t.integer :comments_count, default: 0
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :comments, :force => true do |t|
|
13
|
+
t.integer :user_id
|
14
|
+
t.integer :article_id
|
15
|
+
t.text :message
|
16
|
+
t.boolean :subcomment, default: false
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :users, :force => true do |t|
|
21
|
+
t.string :key
|
22
|
+
t.string :name
|
23
|
+
t.integer :articles_count, default: 0
|
24
|
+
t.integer :comments_count, default: 0
|
25
|
+
t.integer :score, default: 0
|
26
|
+
t.timestamps
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: plus-plus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pathgather
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec-rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: factory_girl
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.4.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.4.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.1.4
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.1.4
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: counter_cache, but much more powerful
|
112
|
+
email:
|
113
|
+
- tech@pathgather.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- lib/plus_plus.rb
|
124
|
+
- lib/plus_plus/base.rb
|
125
|
+
- lib/plus_plus/version.rb
|
126
|
+
- plus_plus.gemspec
|
127
|
+
- spec/base_spec.rb
|
128
|
+
- spec/factories.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/models.rb
|
131
|
+
- spec/support/schema.rb
|
132
|
+
homepage: ''
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.1.11
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: counter_cache, but much more powerful
|
156
|
+
test_files:
|
157
|
+
- spec/base_spec.rb
|
158
|
+
- spec/factories.rb
|
159
|
+
- spec/spec_helper.rb
|
160
|
+
- spec/support/models.rb
|
161
|
+
- spec/support/schema.rb
|