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

Fix issues with ActiveSupport Range extensions on boundless Ranges #37178

Merged
merged 1 commit into from Sep 20, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions activesupport/CHANGELOG.md
@@ -1,3 +1,8 @@
* Fix `Range#===`, `Range#include?`, and `Range#cover?` to work with beginless (startless)
and endless range targets. Builds on work by Allen Hsu.

*Andrew Hodgkinson*

* Fix `Range#include?` to work with beginless and endless ranges.

*Allen Hsu*
Expand Down
12 changes: 9 additions & 3 deletions activesupport/lib/active_support/core_ext/range/compare_range.rb
Expand Up @@ -11,13 +11,15 @@ module CompareWithRange
# The native Range#=== behavior is untouched.
# ('a'..'f') === ('c') # => true
# (5..9) === (11) # => false
#
# The given range must be fully bounded, with both start and end.
def ===(value)
if value.is_a?(::Range)
# 1...10 includes 1..9 but it does not include 1..10.
# 1..10 includes 1...11 but it does not include 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
super(value.first) && value_max.send(operator, last)
super(value.first) && (self.end.nil? || value_max.send(operator, last))
else
super
end
Expand All @@ -32,13 +34,15 @@ def ===(value)
# The native Range#include? behavior is untouched.
# ('a'..'f').include?('c') # => true
# (5..9).include?(11) # => false
#
# The given range must be fully bounded, with both start and end.
def include?(value)
if value.is_a?(::Range)
# 1...10 includes 1..9 but it does not include 1..10.
# 1..10 includes 1...11 but it does not include 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
super(value.first) && value_max.send(operator, last)
super(value.first) && (self.end.nil? || value_max.send(operator, last))
else
super
end
Expand All @@ -53,13 +57,15 @@ def include?(value)
# The native Range#cover? behavior is untouched.
# ('a'..'f').cover?('c') # => true
# (5..9).cover?(11) # => false
#
# The given range must be fully bounded, with both start and end.
def cover?(value)
if value.is_a?(::Range)
# 1...10 covers 1..9 but it does not cover 1..10.
# 1..10 covers 1...11 but it does not cover 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
super(value.first) && value_max.send(operator, last)
super(value.first) && (self.end.nil? || value_max.send(operator, last))
else
super
end
Expand Down
56 changes: 56 additions & 0 deletions activesupport/test/core_ext/range_ext_test.rb
Expand Up @@ -64,12 +64,28 @@ def test_should_include_other_with_exclusive_end
def test_include_with_endless_range
assert(eval("1..").include?(2))
end

def test_should_include_range_with_endless_range
assert(eval("1..").include?(2..4))
end

def test_should_not_include_range_with_endless_range
assert_not(eval("1..").include?(0..4))
end
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
def test_include_with_beginless_range
assert(eval("..2").include?(1))
end

def test_should_include_range_with_beginless_range
assert(eval("..2").include?(-1..1))
end

def test_should_not_include_range_with_beginless_range
assert_not(eval("..2").include?(-1..3))
end
end

def test_should_compare_identical_inclusive
Expand All @@ -84,6 +100,26 @@ def test_should_compare_other_with_exclusive_end
assert((1..10) === (1...11))
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
def test_should_compare_range_with_endless_range
assert(eval("1..") === (2..4))
end

def test_should_not_compare_range_with_endless_range
assert_not(eval("1..") === (0..4))
end
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
def test_should_compare_range_with_beginless_range
assert(eval("..2") === (-1..1))
end

def test_should_not_compare_range_with_beginless_range
assert_not(eval("..2") === (-1..3))
end
end

def test_exclusive_end_should_not_include_identical_with_inclusive_end
assert_not_includes (1...10), 1..10
end
Expand All @@ -109,6 +145,26 @@ def test_should_cover_other_with_exclusive_end
assert((1..10).cover?(1...11))
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
def test_should_cover_range_with_endless_range
assert(eval("1..").cover?(2..4))
end

def test_should_not_cover_range_with_endless_range
assert_not(eval("1..").cover?(0..4))
end
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
def test_should_cover_range_with_beginless_range
assert(eval("..2").cover?(-1..1))
end

def test_should_not_cover_range_with_beginless_range
assert_not(eval("..2").cover?(-1..3))
end
end

def test_overlaps_on_time
time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00)
Expand Down