aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activesupport/CHANGELOG.md5
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb21
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb49
4 files changed, 76 insertions, 0 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index a2a5da4afb..efaa6cd2a3 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Add `Hash#transform_values` to simplify a common pattern where the values of a
+ hash must change, but the keys are left the same.
+
+ *Sean Griffin*
+
* Always instrument `ActiveSupport::Cache`.
Since `ActiveSupport::Notifications` only instrument items when there
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index f68e1662f9..af4d1da0eb 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -6,3 +6,4 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/hash/transform_values'
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
new file mode 100644
index 0000000000..6ff7e91212
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -0,0 +1,21 @@
+class Hash
+ # Returns a new hash with the results of running +block+ once for every value.
+ # The keys are unchanged.
+ #
+ # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
+ # # => { a: 2, b: 4, c: 6 }
+ def transform_values(&block)
+ result = self.class.new
+ each do |key, value|
+ result[key] = yield(value)
+ end
+ result
+ end
+
+ # Destructive +transform_values+
+ def transform_values!
+ each do |key, value|
+ self[key] = yield(value)
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
new file mode 100644
index 0000000000..4449c94701
--- /dev/null
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -0,0 +1,49 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/hash/transform_values'
+
+class TransformValuesTest < ActiveSupport::TestCase
+ test "transform_values returns a new hash with the values computed from the block" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a: 'a!', b: 'b!' }, mapped)
+ end
+
+ test "transform_values! modifies the values of the original" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values! { |v| v + '!' }
+
+ assert_equal({ a: 'a!', b: 'b!' }, original)
+ assert_same original, mapped
+ end
+
+ test "indifferent access is still indifferent after mapping values" do
+ original = { a: 'a', b: 'b' }.with_indifferent_access
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_equal 'a!', mapped['a']
+ end
+
+ # This is to be consistent with the behavior of Ruby's built in methods
+ # (e.g. #select, #reject) as of 2.2
+ test "default values do not persist during mapping" do
+ original = Hash.new('foo')
+ original[:a] = 'a'
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_nil mapped[:b]
+ end
+
+ test "default procs do not persist after mapping" do
+ original = Hash.new { 'foo' }
+ original[:a] = 'a'
+ mapped = original.transform_values { |v| v + '!' }
+
+ assert_equal 'a!', mapped[:a]
+ assert_nil mapped[:b]
+ end
+end