Skip to content

Commit

Permalink
WIP: start using composite pattern.
Browse files Browse the repository at this point in the history
  • Loading branch information
drapergeek authored and Gabe Berke-Williams committed Sep 12, 2012
1 parent 481631c commit fd16caa
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 25 deletions.
2 changes: 2 additions & 0 deletions lib/shoulda/matchers/active_model.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require 'shoulda/matchers/active_model/helpers'
require 'shoulda/matchers/active_model/blank_value'
require 'shoulda/matchers/active_model/validation_matcher'
require 'shoulda/matchers/active_model/composite_matcher'
require 'shoulda/matchers/active_model/allow_value_matcher'
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
Expand Down
37 changes: 37 additions & 0 deletions lib/shoulda/matchers/active_model/blank_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
class BlankValue

def initialize(instance, attribute)
@instance = instance
@attribute = attribute
end

def value
if collection?
[]
else
nil
end
end

private
def collection?
if reflection
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
else
false
end
end

def reflection
@instance.class.respond_to?(:reflect_on_association) &&
@instance.class.reflect_on_association(@attribute)
end

end
end
end
end

37 changes: 37 additions & 0 deletions lib/shoulda/matchers/active_model/composite_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
class CompositeMatcher < ValidationMatcher
def initialize(attribute)
@sub_matchers = []
super
end

def add_matcher(matcher)
@sub_matchers << matcher
self
end

def matches?(subject)
sub_matchers_match?(subject)
end

def description
"No description"
end

def sub_matchers_match?(subject)
if @sub_matchers.empty?
true
else
@sub_matchers.all? { |matcher| matcher.matches?(subject) }
end
end

def sub_matcher_descriptions
@sub_matchers.map(&:description)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def validate_acceptance_of(attr)
ValidateAcceptanceOfMatcher.new(attr)
end

class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
class ValidateAcceptanceOfMatcher < CompositeMatcher # :nodoc:

def with_message(message)
if message
Expand Down
53 changes: 29 additions & 24 deletions lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:

# Ensures that the model is not valid if the given attribute is not
# present.
#
Expand All @@ -19,44 +18,50 @@ def validate_presence_of(attr)
ValidatePresenceOfMatcher.new(attr)
end

class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc:

class ValidatePresenceOfMatcher < CompositeMatcher # :nodoc:
def with_message(message)
@expected_message = message if message
self
add_matcher WithMessageMatcher.new(@attribute, message)
end

def matches?(subject)
super(subject)
@expected_message ||= :blank
disallows_value_of(blank_value, @expected_message)
@subject = subject
blank_value = BlankValue.new(subject, @attribute).value
disallows_value_of(blank_value) && super
end

def description
"require #{@attribute} to be set"
my_description = "require #{@attribute} to be set"
sub_descriptions = sub_matcher_descriptions
(sub_description + mydescriptions).join(" ")
end
end

private
class WithMessageMatcher
include Helpers

def blank_value
if collection?
[]
else
nil
end
def initialize(attribute, bad_value, message)
@attribute = attribute
@bad_value = bad_value
@expected_message = message
end

def collection?
if reflection
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
else
false
def matches?(subject)
@subject = subject
subject.send("#{@attribute}=", @bad_value)
subject.valid?
error_messages.any? do |error_message|
@expected_message.match(error_message).present?
end
end

def reflection
@subject.class.respond_to?(:reflect_on_association) &&
@subject.class.reflect_on_association(@attribute)
def failure_message
"Expected #{@expected_message} got #{pretty_error_messages(@subject)}"
end

private

def error_messages
@subject.errors[@attribute]
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions spec/shoulda/active_model/blank_value_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'spec_helper'


describe Shoulda::Matchers::ActiveModel::BlankValue do
it "returns an array for collections" do
model = define_model :parent do
has_many :children
end.new
BlankValue.new(model, :children).value.should == []
end

it "returns nil for non collections" do
model = define_active_model_class("Example", :accessors => [:attr]).new
BlankValue.new(model, :attr).value.should be nil
end
end

5 changes: 5 additions & 0 deletions spec/shoulda/active_model/composite_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'spec_helper'

describe Shoulda::Matchers::ActiveModel::CompositeMatcher do

end
4 changes: 4 additions & 0 deletions spec/shoulda/active_model/validation_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require 'spec_helper'

describe Shoulda::Matchers::ActiveModel::ValidationMatcher do
end
125 changes: 125 additions & 0 deletions spec/shoulda/active_model/with_message_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
require 'spec_helper'

describe Shoulda::Matchers::ActiveModel::WithMessageMatcher do
context 'when attribute is invalid' do
it 'matches when the error message matches' do
attribute = :age
non_numeric_value = 'a string'
expected_message = "oh no"

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => expected_message }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, expected_message)
matcher.matches?(model).should be_true
end

it 'does not match when the error message does not match' do
attribute = :age
non_numeric_value = 'a string'
actual_message = 'for real'
expected_message = 'not matching'

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => actual_message }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, expected_message)
matcher.matches?(model).should be_false
end

it 'does not match when the expected error message is a substring of the actual one' do
attribute = :age
non_numeric_value = 'a string'
actual_message = 'substring'
expected_message = 'sub'

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => actual_message }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, expected_message)
matcher.matches?(model).should be_false
end
end

context 'when attribute is valid' do
it 'does not match even when error message matches' do
attribute = :age
good_value = 1
message = "oh no"

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => message }
end.new

matcher = WithMessageMatcher.new(attribute, good_value, message)
matcher.matches?(model).should be_false
end

it 'does not match when error message does not match' do
attribute = :age
good_value = 1
actual_message = 'for real'
expected_message = 'not matching'

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => actual_message }
end.new

matcher = WithMessageMatcher.new(attribute, good_value, expected_message)
matcher.matches?(model).should be_false
end
end

context 'given a regex to match against' do
it 'matches when error message matches regex' do
attribute = :age
non_numeric_value = 'a string'
message = 'foo bar'
regex_matching_message = /foo/

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => message }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, regex_matching_message)
matcher.matches?(model).should be_true
end
end

context '#failure_message' do
it 'provides a failure message' do
attribute = :age
non_numeric_value = 'a string'
actual_message = 'for real'
expected_message = 'not matching'

model = define_active_model_class(:example, :accessors => [attribute]) do
validates attribute, :numericality => { :message => actual_message }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, expected_message)
matcher.matches?(model)
matcher.failure_message.should == "Expected #{expected_message} got #{actual_message}"
end

it 'is correct when model has more than one error' do
attribute = :age
other_attribute = :name
non_numeric_value = 'a string'
actual_message = 'for real'
expected_message = 'not matching'

model = define_active_model_class(:example, :accessors => [attribute, other_attribute]) do
validates attribute, :numericality => { :message => actual_message }
validates other_attribute, :presence => { :message => 'other message' }
end.new

matcher = WithMessageMatcher.new(attribute, non_numeric_value, expected_message)
matcher.matches?(model)
matcher.failure_message.should == "Expected #{expected_message} got #{actual_message}, other message"
end
end
end

0 comments on commit fd16caa

Please sign in to comment.