Reason #98 • April 8th, 2026

ActiveSupport: Hash#deep_merge

Regular Hash#merge is perfect until the values themselves are hashes. At that point, we either start writing recursive merge code by hand, or we use ActiveSupport's deep_merge and get on with our lives.

Ruby
defaults = {
  http: {
    open_timeout: 5,
    headers: {
      "Accept" => "application/json",
      "User-Agent" => "Loving Ruby"
    }
  }
}

overrides = {
  http: {
    headers: {
      "User-Agent" => "James Bond"
    }
  }
}

defaults.deep_merge(overrides)
# => {
#   http: {
#     open_timeout: 5,
#     headers: {
#       "Accept" => "application/json",
#       "User-Agent" => "James Bond"
#     }
#   }
# }
      
JavaScript
const deepMerge = (left, right) => {
  const result = { ...left };

  for (const [key, value] of Object.entries(right)) {
    if (
      value &&
      typeof value === "object" &&
      !Array.isArray(value) &&
      result[key] &&
      typeof result[key] === "object" &&
      !Array.isArray(result[key])
    ) {
      result[key] = deepMerge(result[key], value);
    } else {
      result[key] = value;
    }
  }

  return result;
};

const defaults = {
  http: {
    open_timeout: 5,
    headers: {
      "Accept": "application/json",
      "User-Agent": "Loving Ruby"
    }
  }
};

const overrides = {
  http: {
    headers: {
      "User-Agent": "James Bond"
    }
  }
};

deepMerge(defaults, overrides);
// => {
//      http: {
//        open_timeout: 5,
//        headers: {
//          "Accept": "application/json",
//          "User-Agent": "James Bond"
//        }
//      }
//    }
      

Just what the doctor ordered!

History

Hash#deep_merge landed in Rails 2.2.0, released in 2008.

Reason #99 ?