From 31a0913d7f4766278e32c430e9baabd314870484 Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Wed, 19 Mar 2014 11:13:28 +0800 Subject: [PATCH] + Add inverse of matcher for active record association --- NEWS.md | 2 + lib/shoulda/matchers/active_record.rb | 1 + .../active_record/association_matcher.rb | 7 ++++ .../inverse_of_matcher.rb | 40 +++++++++++++++++++ .../active_record/association_matcher_spec.rb | 15 +++++++ 5 files changed, 65 insertions(+) create mode 100644 lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb diff --git a/NEWS.md b/NEWS.md index 3b8de566..030a9c1c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -47,6 +47,8 @@ `has_secure_password` defines #password= such that `nil` will be ignored, which interferes with how `validate_presence_of` works. +* Add ability to test `belongs_to` associations defined with `:inverse_of`. + # v 2.5.0 * Fix Rails/Test::Unit integration to ensure that the test case classes we are diff --git a/lib/shoulda/matchers/active_record.rb b/lib/shoulda/matchers/active_record.rb index 6d98735c..04d3ab11 100644 --- a/lib/shoulda/matchers/active_record.rb +++ b/lib/shoulda/matchers/active_record.rb @@ -1,5 +1,6 @@ require 'shoulda/matchers/active_record/association_matcher' require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher' +require 'shoulda/matchers/active_record/association_matchers/inverse_of_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 a4975da7..c7622fb2 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -125,6 +125,13 @@ module Shoulda # :nodoc: self end + def inverse_of(inverse_of) + inverse_of_matcher = + AssociationMatchers::InverseOfMatcher.new(inverse_of, name) + add_submatcher(inverse_of_matcher) + self + end + def source(source) source_matcher = AssociationMatchers::SourceMatcher.new(source, name) add_submatcher(source_matcher) diff --git a/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb new file mode 100644 index 00000000..e6021c27 --- /dev/null +++ b/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb @@ -0,0 +1,40 @@ +module Shoulda # :nodoc: + module Matchers + module ActiveRecord # :nodoc: + module AssociationMatchers + class InverseOfMatcher + attr_accessor :missing_option + + def initialize(inverse_of, name) + @inverse_of = inverse_of + @name = name + @missing_option = '' + end + + def description + "inverse_of => #{inverse_of}" + end + + def matches?(subject) + self.subject = ModelReflector.new(subject, name) + + if option_verifier.correct_for_string?(:inverse_of, inverse_of) + true + else + self.missing_option = "#{name} should have #{description}" + false + end + end + + private + + attr_accessor :subject, :inverse_of, :name + + def option_verifier + @option_verifier ||= OptionVerifier.new(subject) + end + 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 9454e0db..13c320d6 100644 --- a/spec/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/shoulda/matchers/active_record/association_matcher_spec.rb @@ -66,6 +66,21 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do expect(belonging_to_parent).not_to belong_to(:parent).counter_cache end + it 'accepts an association with a valid :inverse_of option' do + expect(belonging_to_parent(inverse_of: :children)) + .to belong_to(:parent).inverse_of(:children) + end + + it 'rejects an association with a bad :inverse_of option' do + expect(belonging_to_parent(inverse_of: :other_children)) + .not_to belong_to(:parent).inverse_of(:children) + end + + it 'rejects an association that has no :inverse_of option' do + expect(belonging_to_parent) + .not_to belong_to(:parent).inverse_of(:children) + end + it 'accepts an association with a valid :conditions option' do define_model :parent, adopter: :boolean define_model(:child, parent_id: :integer).tap do |model|