Problem
You want to prevent any further changes to the state of an object.
Solution
Freeze the object with Object#freeze:
frozen_string = 'Brrrr!' frozen_string.freeze frozen_string.gsub('r', 'a') # => "Baaaa!" frozen_string.gsub!('r', 'a') # TypeError: can't modify frozen string
Discussion
When an object is frozen, its instance variables are permanently bound to their current values. The values themselves are not frozen: their instance variables can still be modified, to the extent they were modifiable before:
sequences = [[1,2,3], [1,2,4], [1,4,9]].freeze sequences << [2,3,5] # TypeError: can't modify frozen array sequences[2] << 16 # => [1, 4, 9, 16]
A frozen object cannot be unfrozen, and if cloned, the clone will also be frozen. Calling Object#dup (as opposed to Object#clone) on a frozen object yields an unfrozen object with the same instance variables.
frozen_string.clone.frozen? # => true frozen_string.dup.frozen? # => false
Freezing an object does not prevent reassignment of any variables bound to that object.
frozen_string = 'A new string.' frozen_string.frozen? # => false
To prevent objects from changing in ways confusing to the user or to the Ruby interpreter, Ruby sometimes copies objects and freezes the copies. When you use a string as a hash key, Ruby actually copies the string, freezes the copy, and uses the copy as the hash key: that way, if the original string changes later on, the hash key isn't affected.
Constant objects are often frozen as a second line of defense against the object being modified in place. You can freeze an object whenever you need a permanent reference to an object; this is most commonly seen with strings:
API_KEY = "100f7vo4gg".freeze API_KEY[0] = 4 # TypeError: can't modify frozen string API_KEY = "400f7vo4gg" # warning: already initialized constant API_KEY
Frozen objects are also useful in multithreaded code. For instance, Ruby's internal file operations work from a frozen copy of a filename instead of using the filename directly. If another thread modifies the original filename in the middle of an operation that's supposed to be atomic, there's no problem: Ruby wasn't relying on the original filename anyway. You can adopt this copy-and-freeze pattern in multithreaded code to prevent a data structure you're working on from being changed by another thread.
Another common programmer-level use of this feature is to freeze a class in order to prevent future modifications to it (by yourself, other code running in the same environment, or other people who use your code as a library). This is not quite the same as the final construct in C# and Java, because you can still subclass a frozen class, and override methods in the subclass. Calling freeze only stops the in-place modification of a class. The simplest way to do it is to call freeze as the last statement in the class definition:
class MyClass def my_method puts "This is the only method allowed in MyClass." end freeze end class MyClass def my_method "I like this implementation of my_method better." end end # TypeError: can't modify frozen class class MyClass def my_other_method "Oops, I forgot to implement this method." end end # TypeError: can't modify frozen class class MySubclass < MyClass def my_method "This is only one of the methods available in MySubclass." end def my_other_method "This is the other one." end end MySubclass.new.my_method # => "This is only one of the methods available in MySubclass."
See Also
Strings
Numbers
Date and Time
Arrays
Hashes
Files and Directories
Code Blocks and Iteration
Objects and Classes8
Modules and Namespaces
Reflection and Metaprogramming
XML and HTML
Graphics and Other File Formats
Databases and Persistence
Internet Services
Web Development Ruby on Rails
Web Services and Distributed Programming
Testing, Debugging, Optimizing, and Documenting
Packaging and Distributing Software
Automating Tasks with Rake
Multitasking and Multithreading
User Interface
Extending Ruby with Other Languages
System Administration