diff --git a/NEWS.md b/NEWS.md index ec859ccd..7f82391a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # HEAD +* Add a `counter_cache` submatcher for `belongs_to` associations + * Add a rescue_from matcher for Rails controllers which checks that the correct ActiveSupport call has been made and that the handlers exist without actually throwing an exception. diff --git a/lib/shoulda/matchers/active_record.rb b/lib/shoulda/matchers/active_record.rb index 16ea4088..d60a8157 100644 --- a/lib/shoulda/matchers/active_record.rb +++ b/lib/shoulda/matchers/active_record.rb @@ -1,4 +1,5 @@ require 'shoulda/matchers/active_record/association_matcher' +require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher' require 'shoulda/matchers/active_record/association_matchers/order_matcher' require 'shoulda/matchers/active_record/association_matchers/through_matcher' require 'shoulda/matchers/active_record/association_matchers/dependent_matcher' diff --git a/lib/shoulda/matchers/active_record/association_matcher.rb b/lib/shoulda/matchers/active_record/association_matcher.rb index bebab48d..1e4e106c 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -98,6 +98,12 @@ module Shoulda # :nodoc: self end + def counter_cache(counter_cache = true) + counter_cache_matcher = AssociationMatchers::CounterCacheMatcher.new(counter_cache, name) + add_submatcher(counter_cache_matcher) + self + end + def conditions(conditions) @options[:conditions] = conditions self diff --git a/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb new file mode 100644 index 00000000..b04049ad --- /dev/null +++ b/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb @@ -0,0 +1,35 @@ +module Shoulda # :nodoc: + module Matchers + module ActiveRecord # :nodoc: + module AssociationMatchers + class CounterCacheMatcher + attr_accessor :missing_option + + def initialize(counter_cache, name) + @counter_cache = counter_cache + @name = name + @missing_option = '' + end + + def description + "counter_cache => #{counter_cache}" + end + + def matches?(subject) + subject = ModelReflector.new(subject, name) + + if subject.option_set_properly?(counter_cache, :counter_cache) + true + else + self.missing_option = "#{name} should have #{description}" + false + end + end + + private + attr_accessor :counter_cache, :name + end + end + end + end +end diff --git a/spec/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/shoulda/matchers/active_record/association_matcher_spec.rb index 198117f3..8ee84652 100644 --- a/spec/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/shoulda/matchers/active_record/association_matcher_spec.rb @@ -46,6 +46,25 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do belonging_to_parent.should_not belong_to(:parent).dependent(:destroy) end + it 'accepts an association with a valid :counter_cache option' do + belonging_to_parent(:counter_cache => :attribute_count). + should belong_to(:parent).counter_cache(:attribute_count) + end + + it 'defaults :counter_cache to true' do + belonging_to_parent(:counter_cache => true). + should belong_to(:parent).counter_cache + end + + it 'rejects an association with a bad :counter_cache option' do + belonging_to_parent(:counter_cache => :attribute_count). + should_not belong_to(:parent).counter_cache(true) + end + + it 'rejects an association that has no :counter_cache option' do + belonging_to_parent.should_not belong_to(:parent).counter_cache + end + it 'accepts an association with a valid :conditions option' do define_model :parent, :adopter => :boolean define_model :child, :parent_id => :integer do