DEV Community

Aleksandr Korolev
Aleksandr Korolev

Posted on

Ruby: Pass-by-Reference-Value

Most Ruby developers are likely familiar with 'Pass-by-Reference-Value'. And if you see something like this:

var1 = "Hello, World"
var2 = var1
var2.gsub!("World", "Ruby")
print var1 # => Hello, Ruby
Enter fullscreen mode Exit fullscreen mode

You can easily figure out what's going on.
But how about a more complex example:

class DummyClass
  attr_accessor :value

  def initialize(value)
    @value = value
  end
end

dummy = DummyClass.new("Hello, World!")

puts "Before service action:"
puts dummy.value

class ServiceClass
  def initialize(value)
    @value = value
  end

  def perform_action
    local_value = @value
    local_value.gsub!("World", "Ruby")
  end
end

service = ServiceClass.new(dummy.value).perform_action

puts "After service action:"
puts dummy.value # => Hello, Ruby!
Enter fullscreen mode Exit fullscreen mode

In this particular example, it's still quite clear, especially after the first example. But it might be a totally different situation when you work on a real project.

The Real-World Trap

While the behavior is clear in a small snippet, it becomes a stealthy bug in a production environment. I recently encountered this while working with:

  • an ActiveRecord model with a string attribute;
  • a service for interpolation strings;

The goal was to replace placeholders (e.g., %%BRAND%%) within a template string. The implementation looked something like this:

Interpolator.custom_interpolate(source, brand_name)

Semantically, a method named interpolate suggests it returns a new string. However, because the service used a mutating method (like gsub!) on the source object, it accidentally updated the ActiveRecord model's attribute in memory before the model was even saved.

The Takeaway

Even if you understand Ruby's object model, it is incredibly easy to overlook side effects when passing objects through multiple layers of abstraction. When in doubt, use non-mutating methods or explicitly .dup your inputs to ensure your "read-only" data stays that way.

Top comments (0)