Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add support for validation contexts #246

Closed
wants to merge 1 commit into from

6 participants

Samuel Cochran Melissa Xie Masahiro Saito Jason Draper Anuj Biyani Elliot Winkler
Samuel Cochran

There's no easy way to test validation contexts with the current shoulda matchers. The context must be provided in the call to the instance's valid? method. This patch adds a simple way to optionally pass a context.

Example:

class User
  include ActiveModel::Model
  validates_presence_of :email, on: :emailable
end

describe User do
  it { should_not validate_presence_of(:email) }
  it { should validate_presence_of(:email).on(:emailable) }
end

Previously this would have to involve stubbing the implementation detail validation_context, simply setting it is not enough as it is overridden by every run of valid?:

describe User do
  it { should_not validate_presence_of(:email) }
  context do
    before { subject.stub(validation_context: :emailable) }
    it { should validate_presence_of(:email) }
  end
end

This can also be used for the idiomatic activerecord contexts:

class User < ActiveRecord::Base
  validates_presence_of :password, on: :create
end

describe User do
  it { should validate_presence_of(:password).on(:create) }
  it { should_not validate_presence_of(:password).on(:update) }
end
spec/shoulda/active_model/allow_value_matcher_spec.rb
@@ -25,6 +25,30 @@
end
end
+ context "an attribute with a context-dependent validation" do
+ let(:model) do
+ define_model :example, :attr => :string do
+ validates_format_of :attr, :with => /abc/, :on => :customisable
+ end.new
+ end
+
+ context "without the validation context" do
+ it "allows a bad value" do
+ model.should allow_value("xyz").for(:attr)
Melissa Xie Admin
mxie added a note

Instead of using your let above and model.should here (and lines 43, 47), take advantage of the validating_format method that's already available (from spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb). It'll look something like this:

 validating_format(:with => /abc/, :on => :customisable).should allow_value("xyz").for(:attr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Melissa Xie
Admin

I like this idea, and it looks like this would fulfill #131 as well, so that's awesome! However, please rebase and check out my comment on the spec.

Melissa Xie
Admin

Also, I just noticed that your allow_value_matcher_spec.rb file is in a different directory structure than it's supposed to be in...definitely rebase and resolve that please. Thanks!

Samuel Cochran

Sorry for the delays, but I've rebased and fixed up those specs—master has moved fast! Should be good to go now.

Jason Draper
Admin

Thanks @sj26! Merged.

Jason Draper drapergeek closed this
Anuj Biyani

Any reason why this functionality wasn't added to validate_numericality_of?

Elliot Winkler
Admin

I believe this should work with all matchers, is this not working with validate_numericality_of?

Anuj Biyani
Failures:

  1) Upload 
     Failure/Error: it { should validate_numericality_of(:picture_count).on(:create).is_greater_than(0) }
     NoMethodError:
       undefined method `on' for #<Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher:0x007fa324d82c40>
     # ./spec/models/upload_spec.rb:11:in `block (2 levels) in <top (required)>'

This is on version 2.3.0.

I'll try and open a PR (or at least a separate issue).

Elliot Winkler
Admin

Okay, thanks.

Samuel Cochran

@anujbiyani did #313 fix your issue? There was an oversight which means some matchers weren't accepting #on.

Samuel Cochran

Ah, no, I see— ValidateNumericalityOfMatcher doesn't inherit from ValidationMatcher since 6ba8c9f. Have you submitted a pull yet, or shall I?

(I don't see anything in your activity so I'll have a stab.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 4, 2013
  1. Samuel Cochran
This page is out of date. Refresh to see the latest.
3  NEWS.md
View
@@ -1,5 +1,8 @@
# HEAD
+* Support validation contexts for testing validations `on: :create` and when
+ using custom contexts like `model.valid?(:my_context)`.
+
# v 1.5.6
* Revert previous change in `AllowValueMatcher` that added a check for a
properly-set attribute.
9 lib/shoulda/matchers/active_model/allow_value_matcher.rb
View
@@ -43,6 +43,11 @@ def for(attribute)
self
end
+ def on(context)
+ @context = context
+ self
+ end
+
def with_message(message)
self.options[:expected_message] = message
self
@@ -78,7 +83,7 @@ def description
private
attr_accessor :values_to_match, :message_finder_factory,
- :instance, :attribute, :value, :matched_error
+ :instance, :attribute, :context, :value, :matched_error
def errors_match?
has_messages? && errors_for_attribute_match?
@@ -161,7 +166,7 @@ def model_name
end
def message_finder
- message_finder_factory.new(instance, attribute)
+ message_finder_factory.new(instance, attribute, context)
end
end
end
5 lib/shoulda/matchers/active_model/exception_message_finder.rb
View
@@ -3,9 +3,10 @@ module Matchers
module ActiveModel
# Finds message information from exceptions thrown by #valid?
class ExceptionMessageFinder
- def initialize(instance, attribute)
+ def initialize(instance, attribute, context=nil)
@instance = instance
@attribute = attribute
+ @context = context
end
def allow_description(allowed_values)
@@ -39,7 +40,7 @@ def expected_message_from(attribute_message)
private
def validate_and_rescue
- @instance.valid?
+ @instance.valid?(@context)
[]
rescue ::ActiveModel::StrictValidationFailed => exception
[exception.message]
5 lib/shoulda/matchers/active_model/validation_matcher.rb
View
@@ -9,6 +9,11 @@ def initialize(attribute)
@strict = false
end
+ def on(context)
+ @context = context
+ self
+ end
+
def strict
@strict = true
self
5 lib/shoulda/matchers/active_model/validation_message_finder.rb
View
@@ -6,9 +6,10 @@ module ActiveModel
class ValidationMessageFinder
include Helpers
- def initialize(instance, attribute)
+ def initialize(instance, attribute, context=nil)
@instance = instance
@attribute = attribute
+ @context = context
end
def allow_description(allowed_values)
@@ -58,7 +59,7 @@ def validated_instance
end
def validate_instance
- @instance.valid?
+ @instance.valid?(@context)
@instance
end
end
18 spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb
View
@@ -56,6 +56,24 @@
end
end
+ context "an attribute with a context-dependent validation" do
+ context "without the validation context" do
+ it "allows a bad value" do
+ validating_format(:with => /abc/, :on => :customisable).should allow_value("xyz").for(:attr)
+ end
+ end
+
+ context "with the validation context" do
+ it "allows a good value" do
+ validating_format(:with => /abc/, :on => :customisable).should allow_value("abcde").for(:attr).on(:customisable)
+ end
+
+ it "rejects a bad value" do
+ validating_format(:with => /abc/, :on => :customisable).should_not allow_value("xyz").for(:attr).on(:customisable)
+ end
+ end
+ end
+
context 'an attribute with several validations' do
let(:model) do
define_model :example, :attr => :string do
Something went wrong with that request. Please try again.