Skip to content

Commit 210a79e

Browse files
authored
MONGOID-5150 Test and document that read primary is used on validates_uniqueness_of (#5451)
1 parent 0a2b1c9 commit 210a79e

File tree

4 files changed

+71
-6
lines changed

4 files changed

+71
-6
lines changed

docs/reference/queries.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,15 @@ Mongoid also has some helpful methods on criteria.
14871487
Band.all.pluck(:name, :likes)
14881488
#=> [ ["Daft Punk", 342], ["Aphex Twin", 98], ["Ween", 227] ]
14891489

1490+
* - ``Criteria#read``
1491+
1492+
*Sets the read preference for the criteria.*
1493+
1494+
-
1495+
.. code-block:: ruby
1496+
1497+
Band.all.read(mode: :primary)
1498+
14901499
* - ``Criteria#second``
14911500

14921501
*Get the second document for the given criteria.*

docs/reference/validation.txt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ validation plus an additional associated and uniqueness validator.
1616
See the `Active Record Validations
1717
<https://guides.rubyonrails.org/active_record_validations.html>`_
1818
Rails guide and `ActiveModel::Validations
19-
<https://api.rubyonrails.org/classes/ActiveModel/Validations.html>`_
19+
<https://api.rubyonrails.org/classes/ActiveModel/Validations.html>`_
2020
documentation for more information.
2121

2222
Mongoid behaves slightly differently to Active Record when using ``#valid?``
@@ -38,17 +38,27 @@ value of the respective field from the model. Consider the following example:
3838

3939
class Band
4040
include Mongoid::Document
41-
41+
4242
field :name, type: String
4343
field :year, type: Integer
44-
44+
4545
validates_uniqueness_of :name, conditions: -> { where(:year.gte => 2000) }
4646
end
47-
47+
4848
# OK
4949
Band.create!(name: "Sun Project", year: 2000)
50-
50+
5151
# Fails validation because there is a band with the "Sun Project" name
5252
# and year 2000 in the database, even though the model being created now
5353
# does not have a year.
5454
Band.create!(name: "Sun Project")
55+
56+
57+
Read preference with ``validates_uniqueness_of``
58+
================================================
59+
60+
In order to validate the uniqueness of an attribute, Mongoid must check that
61+
the value for that attribute does not already exist in the database. If Mongoid
62+
queries a secondary member of the replica set, there is a possibility that it
63+
is reading stale data. Because of this, the queries used to check a
64+
``validates_uniqueness_of`` validation always use read preference ``primary``.

lib/mongoid/validatable/uniqueness.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def validate_embedded(document, attribute, value)
242242
relation = document._parent.send(document.association_name)
243243
criteria = create_criteria(relation, document, attribute, value)
244244
criteria = criteria.merge(options[:conditions].call) if options[:conditions]
245-
criteria = criteria.limit(2)
245+
criteria = criteria.read(mode: :primary).limit(2)
246246
add_error(document, attribute, value) if criteria.count > 1
247247
end
248248

spec/mongoid/validatable/uniqueness_spec.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,28 @@
88

99
context "when the document is a root document" do
1010

11+
context "when setting the read preference to non-primary" do
12+
13+
before do
14+
Dictionary.validates_uniqueness_of :name
15+
end
16+
17+
after do
18+
Dictionary.reset_callbacks(:validate)
19+
end
20+
21+
it "reads from the primary" do
22+
expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args|
23+
crit = m.call(*args)
24+
expect(crit.view.options["read"]).to eq({ "mode" => :primary })
25+
crit
26+
end
27+
Dictionary.with(read: { mode: :secondary }) do |klass|
28+
klass.create!(name: "Websters")
29+
end
30+
end
31+
end
32+
1133
context "when adding custom persistence options" do
1234

1335
before do
@@ -1634,6 +1656,30 @@
16341656
word.definitions.create!(description: "2")
16351657
end
16361658

1659+
context "when setting the read preference to non-primary" do
1660+
1661+
before do
1662+
Definition.validates_uniqueness_of :description
1663+
end
1664+
1665+
after do
1666+
Definition.reset_callbacks(:validate)
1667+
end
1668+
1669+
let(:word) { Word.create! }
1670+
1671+
it "reads from the primary" do
1672+
expect_any_instance_of(Mongoid::Criteria).to receive(:read).once.and_wrap_original do |m, *args|
1673+
crit = m.call(*args)
1674+
expect(crit.options[:read]).to eq({ mode: :primary })
1675+
crit
1676+
end
1677+
Definition.with(read: { mode: :secondary }) do |klass|
1678+
word.definitions.create!
1679+
end
1680+
end
1681+
end
1682+
16371683
context "when a document is being destroyed" do
16381684

16391685
before do

0 commit comments

Comments
 (0)