Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### 4.1.1 (Next)

* [#239](https://github.com/mongoid/mongoid-rspec/pull/239): Add support for if/unless validator options - [@knovoselic](https://github.com/knovoselic).
* Your contribution here.

### 4.1.0 (6/12/2020)
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ RSpec.describe Person do
# should redefine the kind method to return :custom, i.e. "def self.kind() :custom end"
it { is_expected.to custom_validate(:ssn).with_validator(SsnValidator) }
end

# If you're using validators with if/unless conditionals, spec subject must be object instance
# This is supported on Mongoid 4 and newer
Rspec.describe User do
context 'when user has `admin` role' do
subject { User.new(role: 'admin') }

it { is_expected.to validate_length_of(:password).greater_than(20) }
end

context 'when user does not have `admin` role' do
subject { User.new(role: 'member') }

it { is_expected.not_to validate_length_of(:password) }
end
end
```

### Mass Assignment Matcher
Expand Down
28 changes: 27 additions & 1 deletion lib/matchers/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def matches?(actual)
@klass = actual.is_a?(Class) ? actual : actual.class

@validator = @klass.validators_on(@field).detect do |v|
(v.kind.to_s == @type) && (!v.options[:on] || on_options_matches?(v))
(v.kind.to_s == @type) && (!v.options[:on] || on_options_matches?(v)) &&
if_condition_matches?(actual, v) && unless_condition_matches?(actual, v)
end

if @validator
Expand Down Expand Up @@ -59,6 +60,31 @@ def with_message(message)

private

def if_condition_matches?(actual, validator)
return true unless validator.options[:if]

check_condition actual, validator.options[:if]
end

def unless_condition_matches?(actual, validator)
return true unless validator.options[:unless]

!check_condition actual, validator.options[:unless]
end

def check_condition(actual, filter)
raise ArgumentError, 'Spec subject must be object instance when testing validators with if/unless condition.' if actual.is_a?(Class)

case filter
when Symbol
actual.send filter
when ::Proc
actual.instance_exec(&filter)
else
raise ArgumentError, "Unexpected filter: #{filter.inspect}"
end
end

def check_on
validator_on_methods = [@validator.options[:on]].flatten

Expand Down
5 changes: 5 additions & 0 deletions spec/models/article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Article
field :number_of_comments, type: Integer
field :status, type: Symbol
field :deletion_date, type: DateTime, default: nil
field :reviewer, type: String, default: nil

embeds_many :comments, cascade_callbacks: true, inverse_of: :article
embeds_one :permalink, inverse_of: :linkable
Expand All @@ -24,6 +25,10 @@ class Article

validates_absence_of :deletion_date if Mongoid::Compatibility::Version.mongoid4_or_newer?

validates_presence_of :reviewer, unless: -> { status == :pending }

validates_absence_of :comments, unless: :allow_comments if Mongoid::Compatibility::Version.mongoid4_or_newer?

index({ title: 1 }, unique: true, background: true, drop_dups: true)
index(published: 1)
index('permalink._id' => 1)
Expand Down
10 changes: 9 additions & 1 deletion spec/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@ class User
validates :provider_uid, presence: true
validates :locale, inclusion: { in: ->(_user) { %i[en ru] } }

with_options if: :admin? do
validates :password, length: { minimum: 20 }
end

with_options if: -> { role == 'moderator' } do
validates :password, length: { minimum: 10 }
end

accepts_nested_attributes_for :articles, :comments

def admin?
false
role == 'admin'
end
end
60 changes: 60 additions & 0 deletions spec/unit/validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,63 @@
it { is_expected.to validate_format_of(:to).with_message('format') }
end
end

if Mongoid::Compatibility::Version.mongoid4_or_newer?
RSpec.describe 'Conditional validations' do
describe 'validations with if condition using symbol' do
context 'when the condition is met' do
subject { User.new(role: 'admin') }

it { is_expected.to validate_length_of(:password).greater_than(20) }
end

context 'when the condition is not met' do
subject { User.new(role: 'member') }

it { is_expected.not_to validate_length_of(:password) }
end
end

describe 'validations with if condition using lambda' do
context 'when the condition is met' do
subject { User.new(role: 'moderator') }

it { is_expected.to validate_length_of(:password).greater_than(10) }
end

context 'when the condition is not met' do
subject { User.new(role: 'member') }

it { is_expected.not_to validate_length_of(:password) }
end
end

describe 'validations with unless condition using symbol' do
context 'when the condition is met' do
subject { Article.new(allow_comments: false) }

it { is_expected.to validate_absence_of(:comments) }
end

context 'when the condition is not met' do
subject { Article.new(allow_comments: true) }

it { is_expected.not_to validate_absence_of(:comments) }
end
end

describe 'validations with unless condition using lambda' do
context 'when the condition is met' do
subject { Article.new(status: :rejected) }

it { is_expected.to validate_presence_of(:reviewer) }
end

context 'when the condition is not met' do
subject { Article.new(status: :pending) }

it { is_expected.not_to validate_presence_of(:reviewer) }
end
end
end
end