diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c86704..ecd6329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v1.1.1 + +* Add :if_update option in order to allow counter to be conditionally increment/decrement counter when an update is made to a referenced/embedded object. + ## v1.0.0 * Remove version dependency to work with rails 4. Breaks compatibility with ruby 1.9.2 and older diff --git a/README.md b/README.md index f404c03..f5dda73 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,28 @@ end comment_count will get incremented / decremented only when `:if` condition returns `true` +### Conditional Counter After Update + +In conjunction with the conditional counter, if you want to maintain counter after an update to an object, then you can specify it using `:if_update` + +Using same example as above, in the referrenced/Embedded document, add an additional condition for counter using `:if_update` + +````rb +class Comment + include Mongoid::Document + include Mongoid::MagicCounterCache + + belongs_to :post + + field :remark + field :is_published, type: Boolean, default: false + + counter_cache :post, :if => Proc.new { |act| (act.is_published) }, :if_update => Proc.new { |act| act.changes['is_published'] } +end +```` + +When a comment is saved, comment_count will get incremented / decremented if the is_published field is dirty. + ## TODO 1. Add additional options parameters diff --git a/lib/mongoid/magic-counter-cache/version.rb b/lib/mongoid/magic-counter-cache/version.rb index fb93060..b13be3c 100644 --- a/lib/mongoid/magic-counter-cache/version.rb +++ b/lib/mongoid/magic-counter-cache/version.rb @@ -1,5 +1,5 @@ module Mongoid module MagicCounterCache - VERSION = "1.1.0" + VERSION = "1.1.1" end end diff --git a/lib/mongoid/magic_counter_cache.rb b/lib/mongoid/magic_counter_cache.rb index 4fada2c..e76399f 100644 --- a/lib/mongoid/magic_counter_cache.rb +++ b/lib/mongoid/magic_counter_cache.rb @@ -69,6 +69,7 @@ def counter_cache(*args, &block) name = options[:class] || args.first.to_s counter_name = get_counter_name(options) condition = options[:if] + update_condition = options[:if_update] callback_proc = ->(doc, inc) do result = condition_result(condition, doc) @@ -86,8 +87,29 @@ def counter_cache(*args, &block) end end + update_callback_proc = ->(doc) do + return if condition.nil? + return if update_condition.nil? # Don't execute if there is no update condition. + return unless update_condition.call(doc) # Determine whether to execute update increment/decrements. + + inc = condition.call(doc) ? 1 : -1 + + if doc.embedded? + parent = doc._parent + if parent.respond_to?(counter_name) + increment_association(parent, counter_name.to_sym, inc) + end + else + relation = doc.send(name) + if relation && relation.class.fields.keys.include?(counter_name) + increment_association(relation, counter_name.to_sym, inc) + end + end + end + after_create( ->(doc) { callback_proc.call(doc, 1) }) after_destroy(->(doc) { callback_proc.call(doc, -1) }) + after_update( ->(doc) { update_callback_proc.call(doc) }) end diff --git a/lib/mongoid_magic_counter_cache/version.rb b/lib/mongoid_magic_counter_cache/version.rb index 5752241..ecd1439 100644 --- a/lib/mongoid_magic_counter_cache/version.rb +++ b/lib/mongoid_magic_counter_cache/version.rb @@ -1,3 +1,3 @@ module MongoidMagicCounterCache - VERSION = "1.1.0" + VERSION = "1.1.1" end diff --git a/spec/models/article.rb b/spec/models/article.rb index 50f10b2..270fd40 100644 --- a/spec/models/article.rb +++ b/spec/models/article.rb @@ -2,8 +2,10 @@ class Article include Mongoid::Document embeds_many :reviews + embeds_many :update_reviews field :title field :review_count, type: Integer, default: 0 + field :update_review_count, type: Integer, default: 0 end diff --git a/spec/models/post.rb b/spec/models/post.rb index d7ddccb..97c24cc 100644 --- a/spec/models/post.rb +++ b/spec/models/post.rb @@ -6,4 +6,6 @@ class Post has_many :comments + field :update_comment_count, :type => Integer, :default => 0 + has_many :update_comments end diff --git a/spec/models/update_comment.rb b/spec/models/update_comment.rb new file mode 100644 index 0000000..d5cc7cb --- /dev/null +++ b/spec/models/update_comment.rb @@ -0,0 +1,11 @@ +class UpdateComment + include Mongoid::Document + include Mongoid::MagicCounterCache + + belongs_to :post + + field :remark + field :is_published, type: Boolean, default: false + + counter_cache :post, :if => Proc.new { |act| (act.is_published) }, :if_update => Proc.new { |act| act.changes['is_published'] } +end diff --git a/spec/models/update_review.rb b/spec/models/update_review.rb new file mode 100644 index 0000000..47cdcef --- /dev/null +++ b/spec/models/update_review.rb @@ -0,0 +1,10 @@ +class UpdateReview + include Mongoid::Document + include Mongoid::MagicCounterCache + + embedded_in :article + counter_cache :article, :if => Proc.new { |act| (act.is_published) }, :if_update => Proc.new { |act| act.changes['is_published'] } + + field :comment + field :is_published, type: Boolean, default: false +end diff --git a/spec/mongoid/magic_counter_cache_spec.rb b/spec/mongoid/magic_counter_cache_spec.rb index 296bd77..6a3d568 100644 --- a/spec/mongoid/magic_counter_cache_spec.rb +++ b/spec/mongoid/magic_counter_cache_spec.rb @@ -6,7 +6,7 @@ module Mongoid describe ".counter_cache" do - context "when the document is associatedi without condition" do + context "when the document is associated without condition" do before do Library.delete_all @@ -279,6 +279,44 @@ module Mongoid Comment.where(:remark == "2nd comment").first.destroy post.comment_count.should == 2 end + + context "if update condition" do + it "should not increase counter when old unpublished comment is published" do + new_comment = Comment.new + post.comments << new_comment + post.comments.size.should == post.comment_count + 1 + + new_comment.is_published.should == false + new_comment.is_published = true + new_comment.save! + + post.comments.size.should == post.comment_count + 1 + end + + it "should not decrease counter when old published comment is unpublished" do + new_comment = Comment.new(:is_published => true) + post.comments << new_comment + post.comments.size.should == post.comment_count + + new_comment.is_published.should == true + new_comment.is_published = false + new_comment.save! + + post.comments.size.should == post.comment_count + end + + it "should not modify counter when publish field is not dirty" do + new_comment = Comment.new + post.comments << new_comment + post.comments.size.should == post.comment_count + 1 + + new_comment.is_published.should == false + new_comment.remark = 'New Remark' + new_comment.save! + + post.comments.size.should == post.comment_count + 1 + end + end end context "when the document is embedded and has condition for counter" do @@ -335,6 +373,264 @@ module Mongoid article.reviews.length.should == 6 article.review_count.should == 1 end + + context "if update condition" do + it "should not increase counter when old unpublished review is published" do + new_review = Review.new + article.reviews << new_review + article.reviews.size.should == article.review_count + 1 + + new_review.is_published.should == false + new_review.is_published = true + new_review.save! + + article.reviews.size.should == article.review_count + 1 + end + + it "should not decrease counter when old published review is unpublished" do + new_review = Review.new(:is_published => true) + article.reviews << new_review + article.reviews.size.should == article.review_count + + new_review.is_published.should == true + new_review.is_published = false + new_review.save! + + article.reviews.size.should == article.review_count + end + + it "should not modify counter when published field is not dirty" do + new_review = Review.new + article.reviews << new_review + article.reviews.size.should == article.review_count + 1 + + new_review.is_published.should == false + new_review.comment = 'New Comment' + new_review.save! + + article.reviews.size.should == article.review_count + 1 + end + end + end + end + + describe ".counter_cache with if_update" do + + context "when the document is associated with condition" do + + before do + Post.delete_all + end + + let(:post) do + Post.new + end + + let(:comment) do + UpdateComment.new(:is_published => true) + end + + before do + post.save + post.update_comments.create(:remark => "I agree with you", :is_published => true) + end + + it "sets the target of the relation" do + post.update_comments.first.remark.should == "I agree with you" + end + + it "should have 1 comment for post" do + post.update_comments.size.should == 1 + end + + it "should have 1 in comment counter" do + post.update_comment_count.should == 1 + end + + it "sets the counter cache equal to the relation count on addition" do + 5.times do |n| + post.update_comments << UpdateComment.new(:is_published => true) + post.update_comment_count.should == post.update_comments.size + end + end + + it "should increase counter when new books are added" do + post.update_comments.push( comment ) + post.update_comments.size.should == 2 + end + + it "should increase counter when new books are added" do + post.update_comments.push( comment ) + post.update_comments.size.should == post.update_comment_count + end + + it "should decrease counter when published comment is deleted" do + post.update_comments.push( comment ) + comment.destroy + post.update_comments.size.should == 1 + end + + it "should increase counter when new books are added" do + post.update_comments.push( comment ) + comment.destroy + post.update_comments.size.should == post.update_comment_count + end + + it "shouldnot increase counter when unpublished comment is added" do + post.update_comments << UpdateComment.new + post.update_comments.size.should == post.update_comment_count + 1 + end + + it "shouldnot decrease counter when unpublished comment is deleted" do + post.update_comments << UpdateComment.new(:remark => "2nd comment") + post.update_comments << UpdateComment.new(:remark => "3rd comment", :is_published => true) + UpdateComment.where(:remark == "2nd comment").first.destroy + post.update_comment_count.should == 2 + end + + context "if update condition" do + it "should increase counter when old unpublished comment is published" do + new_comment = UpdateComment.new + post.update_comments << new_comment + post.update_comments.size.should == post.update_comment_count + 1 + + new_comment.is_published.should == false + new_comment.is_published = true + new_comment.save! + + post.update_comments.size.should == post.update_comment_count + + new_comment.save! # Should not increment since is_published is not dirty. + post.update_comments.size.should == post.update_comment_count + end + + it "should decrease counter when old published comment is unpublished" do + new_comment = UpdateComment.new(:is_published => true) + post.update_comments << new_comment + post.update_comments.size.should == post.update_comment_count + + new_comment.is_published.should == true + new_comment.is_published = false + new_comment.save! + + post.update_comments.size.should == post.update_comment_count + 1 + + new_comment.save! # Should not increment since is_published is not dirty. + post.update_comments.size.should == post.update_comment_count + 1 + end + + it "should not modify counter when publish field is not dirty" do + new_comment = UpdateComment.new + post.update_comments << new_comment + post.update_comments.size.should == post.update_comment_count + 1 + + new_comment.is_published.should == false + new_comment.remark = 'New Remark' + new_comment.save! + + post.update_comments.size.should == post.update_comment_count + 1 + end + end + end + + context "when the document is embedded and has condition for counter" do + + before do + Article.delete_all + end + + let(:article) do + Article.new + end + + let(:review) do + UpdateReview.new(:comment => "This is nice article") + end + + before do + article.save + article.update_reviews.create(:comment => "This is very good article", :is_published => true) + end + + it "should have 1 review in reviews" do + article.update_reviews.length.should == 1 + end + + it "should have correct comment" do + article.update_reviews.first.comment.should == "This is very good article" + end + + it "should have 1 review in counter" do + article.update_review_count.should == 1 + end + + it "sets the counter cache equal to the relation count" do + article.update_reviews.length.should == article.update_review_count + end + + it "sets the counter cache equal to the relation count on addition" do + 5.times do |n| + article.update_reviews << UpdateReview.new(:is_published => true) + article.update_reviews.length.should == article.update_review_count + end + end + + it "decreases the counter cache when records are deleted" do + article.update_reviews.all.destroy + article.update_reviews.length.should == article.update_review_count + end + + it "counter should not get incremented if condition is not meet" do + 5.times do |n| + article.update_reviews << UpdateReview.new + end + article.update_reviews.length.should == 6 + article.update_review_count.should == 1 + end + + context "if update condition" do + it "should increase counter when old unpublished review is published" do + new_review = UpdateReview.new + article.update_reviews << new_review + article.update_reviews.size.should == article.update_review_count + 1 + + new_review.is_published.should == false + new_review.is_published = true + new_review.save! + + article.update_reviews.size.should == article.update_review_count + + new_review.save! # Should not increment since is_published is not dirty. + article.update_reviews.size.should == article.update_review_count + end + + it "should decrease counter when old published review is unpublished" do + new_review = UpdateReview.new(:is_published => true) + article.update_reviews << new_review + article.update_reviews.size.should == article.update_review_count + + new_review.is_published.should == true + new_review.is_published = false + new_review.save! + + article.update_reviews.size.should == article.update_review_count + 1 + + new_review.save! # Should not decrement since is_published is not dirty. + article.update_reviews.size.should == article.update_review_count + 1 + end + + it "should not modify counter when published field is not dirty" do + new_review = UpdateReview.new + article.update_reviews << new_review + article.update_reviews.size.should == article.update_review_count + 1 + + new_review.is_published.should == false + new_review.comment = 'New Comment' + new_review.save! + + article.update_reviews.size.should == article.update_review_count + 1 + end + end end end