Skip to content

Commit

Permalink
Support passing record to uniqueness conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
eliotsykes committed Oct 5, 2020
1 parent ae5ecfe commit ab20be8
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 1 deletion.
13 changes: 13 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
* Support passing record to uniqueness validator `:conditions` callable:

```ruby
class Article < ApplicationRecord
validates_uniqueness_of :title, conditions: ->(article) {
published_at = article.published_at
where(published_at: published_at.beginning_of_year..published_at.end_of_year)
}
end
```

*Eliot Sykes*

* `BatchEnumerator#update_all` and `BatchEnumerator#delete_all` now return the
total number of rows affected, just like their non-batched counterparts.

Expand Down
22 changes: 21 additions & 1 deletion activerecord/lib/active_record/validations/uniqueness.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ def validate_each(record, attribute, value)
end
end
relation = scope_relation(record, relation)
relation = relation.merge(options[:conditions]) if options[:conditions]

if options[:conditions]
conditions = options[:conditions]

relation = if conditions.arity.zero?
relation.instance_exec(&conditions)
else
relation.instance_exec(record, &conditions)
end
end

if relation.exists?
error_options = options.except(:case_sensitive, :scope, :conditions)
Expand Down Expand Up @@ -126,6 +135,17 @@ module ClassMethods
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
# end
#
# To build conditions based on the record's state, define the conditions
# callable with a parameter, which will be the record itself. This
# example validates the title is unique for the year of publication:
#
# class Article < ActiveRecord::Base
# validates_uniqueness_of :title, conditions: ->(article) {
# published_at = article.published_at
# where(published_at: published_at.beginning_of_year..published_at.end_of_year)
# }
# end
#
# When the record is created, a check is performed to make sure that no
# record exists in the database with the given value for the specified
# attribute (that maps to a column). When the record is updated,
Expand Down
17 changes: 17 additions & 0 deletions activerecord/test/cases/validations/uniqueness_validation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,23 @@ def test_validate_uniqueness_with_non_callable_conditions_is_not_supported
}
end

def test_validate_uniqueness_with_conditions_with_record_arg
Topic.validates_uniqueness_of :title, conditions: ->(record) {
where(written_on: record.written_on.beginning_of_day..record.written_on.end_of_day)
}

today_midday = Time.current.midday

todays_topic = Topic.new(title: "Highlights of the Day", written_on: today_midday)
assert todays_topic.save, "1st topic written today with this title should save"

todays_topic_duplicate = Topic.new(title: "Highlights of the Day", written_on: today_midday + 1.minute)
assert todays_topic_duplicate.invalid?, "2nd topic written today with this title should be invalid"

tomorrows_topic = Topic.new(title: "Highlights of the Day", written_on: today_midday + 1.day)
assert tomorrows_topic.valid?, "1st topic written tomorrow with this title should be valid"
end

def test_validate_uniqueness_on_existing_relation
event = Event.create
assert_predicate TopicWithUniqEvent.create(event: event), :valid?
Expand Down

0 comments on commit ab20be8

Please sign in to comment.