aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activesupport/CHANGELOG.md23
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb14
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb24
3 files changed, 58 insertions, 3 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 16b0a06d44..58ec6fb4fe 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,26 @@
+* `Array#sum` compat with Ruby 2.4's native method.
+
+ Ruby 2.4 introduces `Array#sum`, but it only supports numeric elements,
+ breaking our `Enumerable#sum` which supports arbitrary `Object#+`.
+ To fix, override `Array#sum` with our compatible implementation.
+
+ Native Ruby 2.4:
+
+ %w[ a b ].sum
+ # => TypeError: String can't be coerced into Fixnum
+
+ With `Enumerable#sum` shim:
+
+ %w[ a b ].sum
+ # => 'ab'
+
+ We tried shimming the fast path and falling back to the compatible path
+ if it fails, but that ends up slower even in simple causes due to the cost
+ of exception handling. Our only choice is to override the native `Array#sum`
+ with our `Enumerable#sum`.
+
+ *Jeremy Daer*
+
* `ActiveSupport::Duration` supports ISO8601 formatting and parsing.
ActiveSupport::Duration.parse('P3Y6M4DT12H30M5S')
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 8a74ad4d66..f297214d0f 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -104,3 +104,17 @@ class Range #:nodoc:
end
end
end
+
+# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
+#
+# We tried shimming it to attempt the fast native method, rescue TypeError,
+# and fall back to the compatible implementation, but that's much slower than
+# just calling the compat method in the first place.
+if Array.instance_methods(false).include?(:sum) && (%w[a].sum rescue true)
+ class Array
+ def sum(*args) #:nodoc:
+ # Use Enumerable#sum instead.
+ super
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index f09b7d8850..976c8b2b81 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -10,22 +10,22 @@ class SummablePayment < Payment
end
class EnumerableTests < ActiveSupport::TestCase
-
class GenericEnumerable
include Enumerable
+
def initialize(values = [1, 2, 3])
@values = values
end
def each
- @values.each{|v| yield v}
+ @values.each { |v| yield v }
end
end
def test_sums
enum = GenericEnumerable.new([5, 15, 10])
assert_equal 30, enum.sum
- assert_equal 60, enum.sum { |i| i * 2}
+ assert_equal 60, enum.sum { |i| i * 2 }
enum = GenericEnumerable.new(%w(a b c))
assert_equal 'abc', enum.sum
@@ -70,6 +70,24 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 42, (10...10).sum(42)
end
+ def test_array_sums
+ enum = [5, 15, 10]
+ assert_equal 30, enum.sum
+ assert_equal 60, enum.sum { |i| i * 2 }
+
+ enum = %w(a b c)
+ assert_equal 'abc', enum.sum
+ assert_equal 'aabbcc', enum.sum { |i| i * 2 }
+
+ payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ]
+ assert_equal 30, payments.sum(&:price)
+ assert_equal 60, payments.sum { |p| p.price * 2 }
+
+ payments = [ SummablePayment.new(5), SummablePayment.new(15) ]
+ assert_equal SummablePayment.new(20), payments.sum
+ assert_equal SummablePayment.new(20), payments.sum { |p| p }
+ end
+
def test_index_by
payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },