Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The == operator should be transitive #1562

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Bug Fixes:

* Regression: Preserve message-level charset when adding parts (related to Rails ActionMailer) @shields
* Regression: Adding a part should not reset the mail's charset to nil @railsbob
* == operator is now transitive; a==b and b==c implies a==c

Performance:

Expand Down
36 changes: 11 additions & 25 deletions lib/mail/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -339,37 +339,29 @@ def <=>(other)
end
end

# Two emails are the same if they have the same fields and body contents. One
# gotcha here is that Mail will insert Message-IDs when calling encoded, so doing
# mail1.encoded == mail2.encoded is most probably not going to return what you think
# as the assigned Message-IDs by Mail (if not already defined as the same) will ensure
# that the two objects are unique, and this comparison will ALWAYS return false.
# Two emails are the same if they have the same fields and body contents.
#
# So the == operator has been defined like so: Two messages are the same if they have
# the same content, ignoring the Message-ID field, unless BOTH emails have a defined and
# different Message-ID value, then they are false.
# Note that Mail will provide Message-ID and Date (if necessary) when calling encoded, so
# mail1 == mail2 does not imply mail1.encoded == mail2.encoded
# unless both Date and Message-Id have been provided.
#
# Note that Mail creates Date and Mime-Type fields if they don't exist.
# The Date field is derived from the current time, so this needs to be allowed for in comparisons.
# (Mime-type does not depend on dynamic data, so cannot affect equality)
#
# So, in practice the == operator works like this:
# The == operator works like this:
#
# m1 = Mail.new("Subject: Hello\r\n\r\nHello")
# m2 = Mail.new("Subject: Hello\r\n\r\nHello")
# m1 == m2 #=> true
# m1 == m2 #=> true; however encoded versions will not be equal
#
# m1 = Mail.new("Subject: Hello\r\n\r\nHello")
# m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
# m1 == m2 #=> true
# m1 == m2 #=> false
#
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
# m2 = Mail.new("Subject: Hello\r\n\r\nHello")
# m1 == m2 #=> true
# m1 == m2 #=> false
#
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
# m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
# m1 == m2 #=> true
# m1 == m2 #=> true; however encoded versions will not be equal if the time stamp changes
#
# m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
# m2 = Mail.new("Message-ID: <DIFFERENT@test>\r\nSubject: Hello\r\n\r\nHello")
Expand All @@ -378,14 +370,8 @@ def ==(other) # TODO could be more efficient
return false unless other.respond_to?(:encoded)

stamp = Mail::CommonDateField.normalize_datetime('')
# Note: must always dup the inputs so they are not altered by encoded
if self.message_id && other.message_id
dup.tap { |m| m.date ||= stamp }.encoded ==
other.dup.tap { |m| m.date ||= stamp }.encoded
else
dup.tap { |m| m.message_id = '<temp@test>'; m.date ||= stamp }.encoded ==
other.dup.tap { |m| m.message_id = '<temp@test>'; m.date ||= stamp }.encoded
end
dup.tap { |m| m.message_id ||= '<temp@test>'; m.date ||= stamp }.encoded ==
other.dup.tap { |m| m.message_id ||= '<temp@test>'; m.date ||= stamp }.encoded
end

def initialize_copy(original)
Expand Down
22 changes: 13 additions & 9 deletions spec/mail/message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1697,11 +1697,6 @@ def with_encoder(encoder)
describe "helper methods" do

describe "==" do
before(:each) do
# Ensure specs don't randomly fail due to messages being generated 1 second apart
time = DateTime.now
expect(DateTime).to receive(:now).at_least(:once).and_return(time)
end

it "should be implemented" do
expect { Mail.new == Mail.new }.not_to raise_error
Expand All @@ -1716,19 +1711,19 @@ def with_encoder(encoder)
expect(m2[:message_id]).to be_nil
end

it "should ignore the message id value if self has a nil message id" do
it "should not ignore the message id value if self has a nil message id" do
m1 = Mail.new("To: [email protected]\r\nSubject: Yo!\r\n\r\nHello there")
m2 = Mail.new("To: [email protected]\r\nMessage-ID: <[email protected]>\r\nSubject: Yo!\r\n\r\nHello there")
expect(m1).to eq m2
expect(m1).not_to eq m2
# confirm there are no side-effects in the comparison
expect(m1[:message_id]).to be_nil
expect(m2[:message_id].value).to eq '<[email protected]>'
end

it "should ignore the message id value if other has a nil message id" do
it "should not ignore the message id value if other has a nil message id" do
m1 = Mail.new("To: [email protected]\r\nMessage-ID: <[email protected]>\r\nSubject: Yo!\r\n\r\nHello there")
m2 = Mail.new("To: [email protected]\r\nSubject: Yo!\r\n\r\nHello there")
expect(m1).to eq m2
expect(m1).not_to eq m2
# confirm there are no side-effects in the comparison
expect(m1[:message_id].value).to eq '<[email protected]>'
expect(m2[:message_id]).to be_nil
Expand All @@ -1742,6 +1737,15 @@ def with_encoder(encoder)
expect(m1.message_id).to eq '[email protected]'
expect(m2.message_id).to eq '[email protected]'
end

it "should be transitive" do
m1 = Mail.new("To: [email protected]\r\nMessage-ID: <[email protected]>\r\nSubject: Yo!\r\n\r\nHello")
m2 = Mail.new("To: [email protected]\r\nMessage-ID: <[email protected]>\r\nSubject: Yo!\r\n\r\nHello")
m3 = Mail.new("To: [email protected]\r\nSubject: Yo!\r\n\r\nHello")
if m1 == m3 && m2 == m3
expect(m1).to eq m2 # Check transitivity
end
end
end

it "should implement the spaceship operator on the date field" do
Expand Down