This won’t give you all the benefits of language level support for immutable objects and doesn’t really prevent you from mutating state… It just kind of a fun hack to work with objects like you would values in immutable languages. There’s way better options (Hamster, Adamantium) if you’re looking for something a little more substantial.

class Person
  attr_reader :name, :age, :favorite_color

  def initialize(attrs)
    @name           = attrs[:name]
    @age            = attrs[:age]
    @favorite_color = attrs[:favorite_color]
  end

  def self.alter(person, attrs)
    new(person.send(:attributes).merge(attrs))
  end

  def alter(attrs)
    self.class.alter(self, attrs)
  end

  private
  def attributes
    instance_variables.each_with_object(Hash.new) { |ivar, h|
      h[key_from(ivar)] = instance_variable_get(ivar)
    }
  end

  def key_from(ivar)
    String(ivar).gsub('@', '').to_sym
  end
end

Usage:

nate = Person.new(name: "Nessa", age: 25, favorite_color: "green")

# when Nessa's age changes
older_nate = nate.alter(age: 26)