How to test whether a Ruby object is immutable?
There are no primitive objects in Ruby. This can therefore not be detected in a straightforward manner.
Can't you simply use Marshal or YAML for your versioned store? Then you'll get loading and saving of all object types for free. Why reinvent the wheel?
I don't know what you want to achieve exactly, but looking at the source of YAML may be interesting to see how they handle this problem. The Ruby YAML encoding implementation simply implements the to_yaml
method for all relevant classes. See yaml/rubytypes.rb.
Ruby: Why freeze mutable objects assigned to constants?
You should freeze the value assigned to IP
because you've declared IP
to be a constant. This indicates that you don't want the value assigned to IP
to be mutated.
The problem is that in ruby, assigning a value to a constant does not make the value immutable. You just get a warning if you mutate the value assigned to the constant. To make the value actually immutable, you need to .freeze
the value assigned to the constant. After you've frozen a value assigned to a constant, if you try to change the value, you will hit a runtime error.
What is the difference between being immutable and the fact that there can only be one instance of a Symbol?
Your sentence is fine; you're not sure of the common phrase used to describe a class with only one instance. I'll explain that as I go along.
An object that is immutable cannot change through any operations done on it. This means that any operation that would change a symbol would generate a new one instead.
:foo.object_id # 1520028
:foo.upcase.object_id # 70209716662240
:foo.capitalize.object_id # 70209719120060
You can certainly write objects that are immutable, or make them immutable (with some caveats) via freeze
, but you can always create a new instance of them.
f = "foo"
f.freeze
f1 = "foo"
puts f.object_id == f1.object_id # false
An object that only ever has one instance of itself is considered to be a singleton.
- If there's only one instance of it, then you only store it in memory once.
- If you attempt to create it, you only get the previously existing object back.
Are strings in Ruby mutable?
Yes, strings in Ruby, unlike in Python, are mutable.
s += "hello"
is not appending "hello"
to s
- an entirely new string object gets created. To append to a string 'in place', use <<
, like in:
s = "hello"
s << " world"
s # hello world
Ruby immutability of strings and symbols (What if we store them in variables)
Ruby variables are references to objects, so when you send a method to a variable, the object it references is the context in which it is evaluated. It's probably more clear to look at the first image in the top rated answer (below the accepted answer) here.
So, to figure out what's going on, let's dig into the documentation a bit and see what happens with your code snippet.
Ruby's Symbol
class documentation:
https://ruby-doc.org/core-2.5.0/Symbol.html
Symbol objects represent names and some strings inside the Ruby interpreter. They are generated using the :name and :"string" literals syntax, and by the various to_sym methods. The same Symbol object will be created for a given name or string for the duration of a program's execution, regardless of the context or meaning of that name. Thus if Fred is a constant in one context, a method in another, and a class in a third, the Symbol :Fred will be the same object in all three contexts.
Ruby's Object#object_id
documentation:
https://ruby-doc.org/core-2.5.1/Object.html#method-i-object_id
Returns an integer identifier for obj.
The same number will be returned on all calls to object_id for a given object, and no two active objects will share an id.
So here's what's happening step-by-step:
# We create two variables that refer to the same object, :foo
var1 = :foo
var2 = :foo
var1.object_id = 2598748
var2.object_id = 2598748
# Evaluated as:
# var1.object_id => :foo.object_id => 2598748
# var2.object_id => :foo.object_id => 2598748
As discussed in the first link above, Ruby is pass-by-value, but every value is an Object
, so your variables both evaluate to the same value. Since every symbol made of the same string ("foo"
in this case) refers to the same object, and Object#object_id
always returns the same id for the same object, you get the same id back.
Ruby CONSTANTS seem to be INVISIBLY ALTERABLE?
TL;DR
Short of monkey-patching Kernel#warn (see https://stackoverflow.com/a/662436/1301972) to raise an exception, you won't be able to prevent reassignment to the constant itself. This is generally not a pragmatic concern in idiomatic Ruby code where one expects to be able to do things like reopen classes, even though class names are also constants.
A Ruby constant isn't actually immutable, and you can't freeze a variable. However, you can get an exception to be raised when something attempts to modify the contents of a frozen object referenced by the constant.
Freezing Objects Deeply with Plain Ruby
Freezing an Array is easy:
CONSTANT_ONE = %w[one two three].freeze
but the strings stored in this Array are really references to String objects. So, while you can't modify this Array, you can still (for example) modify the String object referenced by index 0. To solve this problem, you need to freeze not just the Array, but the objects it holds, too. For example:
CONSTANT = %w[one two three].map(&:freeze).freeze
CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String
CONSTANT << 'five'
# RuntimeError: can't modify frozen Array
Freezing Objects Recursively with a Gem
Since freezing recursive references can be a bit unwieldy, it's good to know there's a gem for that. You can use ice_nine to deep-freeze most objects:
require 'ice_nine'
require 'ice_nine/core_ext/object'
OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze
OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array
OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array
A Better Way to Use Ruby Constants
Another option to consider is calling Object#dup when assigning the value of a constant to another variable, such as instance variables in your class initializers, in order to ensure you don't mutate your constant's references by accident. For example:
class Foo
CONSTANT = 'foo'
attr_accessor :variable
def initialize
@variable = CONSTANT.dup
end
end
foo = Foo.new
foo.variable << 'bar'
#=> "foobar"
Foo::CONSTANT
#=> "foo"
Related Topics
Has_Many and No Method Error Issue
How to Pass Multi Value Query Params in Swagger
Conditional Dependency in Ruby Gemspec
How to Set the Mechanize Page Encoding
How to Create a "Clone"-Able Enumerator for External Iteration
Error When Trying to Create Heroku App on Windows
How to Pass Value from One Resource to Another Resource in Chef Recipe
Rails 3.2 Force_Ssl Except on Landing Page
Rails 6: Only One Profile Per User Should Be Created
Openssl::Cipher::Ciphererror When Running Staging Db on Local
How to Force To_Yaml to Output Long Strings in Literal Block Style
Rails: Uninitialized Constant Just Happen on Production Server
Validating Phone Number in Ruby
Chrome Asks to "Select a Certificate" for Ssl on My Rails App Using Thin