In Ruby, what do instance_variable_get and instance_variable_set do, and why can instance_variables omit an attribute declared with attr_accessor?
answer
- the @ is part of the name
- unset reads as nil
- exists only once assigned
- bypasses encapsulation
- FrozenError on a frozen object
basics
~10 sThey read and write an object's instance variable by name, such as :@total, bypassing its methods. instance_variables lists only variables already assigned, and attr_accessor defines methods without creating the variable.
solid answer
~40 s`obj.instance_variable_get(:@total)` returns the variable's value, or `nil` if it was never set; `instance_variable_set(:@total, 5)` assigns it, creating it if needed, and returns the value. The name must include the `@`: `instance_variable_get(:total)` raises `NameError` because `total` is not a valid instance variable name. `instance_variables` returns the names that currently exist on that object, and an instance variable exists only after something assigns it. `attr_accessor :total` defines `total` and `total=` methods; it creates no `@total` until the writer or other code runs. Both methods ignore visibility and bypass any validation in setters, so they suit serializers, debuggers and tests, not ordinary code. On a frozen object `instance_variable_set` raises `FrozenError`.
code
ruby · 17 linesclass Invoice
attr_accessor :total
end
inv = Invoice.new
inv.instance_variables # => []
inv.total # => nil
inv.instance_variable_defined?(:@total) # => false
inv.total = 5
inv.instance_variables # => [:@total]
inv.instance_variable_get(:@total) # => 5
inv.instance_variable_set(:@note, "late") # => "late"
inv.instance_variables # => [:@total, :@note]
inv.instance_variable_get(:total) # NameError: 'total' is not allowed as an instance variable name
inv.freeze.instance_variable_set(:@total, 9) # FrozenErrorgo deeper
Recall that instance_variable_get and _set read and write @variables by name, with the @ included, and that unset variables read as nil.
Explain why instance_variables depends on what has been assigned, why attr_accessor adds nothing to it, and how instance_variable_defined? differs from a nil read.
Keep these calls in serializers, debuggers and test setup, and flag production code that uses them to skip setters or read private state.
Treat widespread instance_variable_get in a codebase as a missing-interface smell and plan the readers or value objects that remove it.
## Instance variables belong to objects, not classes In Ruby, an instance variable such as `@total` is stored on an **individual object** and comes into existence the first time that object assigns it. A class does not declare a fixed set of fields. Two instances of the same class can therefore hold different sets of instance variables, depending on which code paths ran. ## The introspection methods | Call | Returns | Notes | |---|---|---| | `obj.instance_variables` | `Array` of symbols such as `[:@total]` | only variables assigned so far | | `obj.instance_variable_get(:@total)` | the value, or `nil` if unset | name must start with `@` | | `obj.instance_variable_set(:@total, 5)` | the assigned value, `5` | creates the variable if absent | | `obj.instance_variable_defined?(:@total)` | `true` / `false` | distinguishes unset from set to `nil` | | `obj.remove_instance_variable(:@total)` | the removed value | raises `NameError` if absent | All of them accept a `Symbol` or a `String` name. An invalid name, such as one without the `@`, raises `NameError` with a message saying it is not allowed as an instance variable name. ## Why attr_accessor does not show up `attr_accessor :total` generates two **methods**: a reader that returns `@total` and a writer that assigns it. It does not assign anything. The `instance_variables` documentation says it explicitly: *simply defining an accessor does not create the corresponding instance variable*. So: - right after `Invoice.new`, `instance_variables` may be `[]`; - after `invoice.total = 5`, it is `[:@total]`; - `invoice.total` before any assignment returns `nil`, because reading an unset instance variable returns `nil`. That is also why `instance_variable_get(:@total)` returning `nil` does not tell you whether the variable exists; `instance_variable_defined?` does. ## What bypassing means These methods are public and work from outside the object, so they **ignore the class's interface**: 1. A writer that validates or converts (`def total=(v) = @total = Integer(v)`) is skipped by `instance_variable_set`. 2. Private state is readable by any caller. 3. A typo such as `:@totl` silently creates a new variable instead of failing. The one guard that still applies is **freezing**: `instance_variable_set` on a frozen object raises `FrozenError`, like any other attempt to modify it. ## A generic copy with these methods The typical reason to use them is code that must work for **any** object without knowing its fields: ```ruby def snapshot(obj) obj.instance_variables.to_h do |name| [name.to_s.delete_prefix("@"), obj.instance_variable_get(name)] end end ``` This is useful and dangerous in equal measure: it copies whatever the object holds right now, including caches, credentials or half-built state, and it silently changes shape when a code path assigns a new variable. A snapshot taken before and after a method call can differ in its keys, not just its values. ## Where they belong - **Serializers and inspectors** that copy every field generically; `Kernel#inspect` itself shows instance variables. - **Debugging** in a console, to see an object's state without adding readers. - **Tests** that must set up hard-to-reach state, sparingly, since they couple the test to private names. - **Frameworks** that inject dependencies or restore objects from storage. In ordinary application code, a missing reader or writer is the design signal; reaching past it with `instance_variable_get` hides that. ## Mistakes to avoid - Omitting the `@` in the name. - Assuming `attr_accessor` creates the variable. - Treating a `nil` result as proof the variable is absent. - Using `instance_variable_set` to skip a setter's validation in production code. - Expecting every instance of a class to report the same `instance_variables`.
- How do you tell an unset instance variable from one explicitly set to nil?Use `obj.instance_variable_defined?(:@total)`. `instance_variable_get` returns `nil` in both cases, while `instance_variable_defined?` returns `false` until something assigns the variable, even if the value assigned was `nil`.
- Why can two instances of the same class report different instance_variables?Instance variables are created per object on first assignment, not declared by the class. If one instance ran a method that assigns `@discount` and the other did not, only the first has it, so the two lists differ.
saying these in an interview costs you the question
- attr_accessor creates the instance variable when the class loads
- instance_variable_get(:total) works without the @
- instance_variable_set runs the class's setter method
- A nil from instance_variable_get proves the variable does not exist
- instance_variable_set can modify a frozen object