Ruby / Foundations: values, methods, text, and valid objects / Lesson 1
Understand Ruby objects, bindings, and numeric meaning
Read small scripts through object references, dynamic values, integer division, mutable aliases, and shallow freezing.
On this page
Lesson goals
- Distinguish reassignment of a local variable from mutation of its referenced object.
- Predict Ruby arithmetic without importing another language’s rounding or immutability assumptions.
State the language and execution assumptions
Ruby expressions work with objects, and variables hold references rather than declared static element types. A local variable can later refer to an object of another class; that flexibility does not guarantee the next method call is meaningful. This course selects Ruby 3.3 documentation as its language baseline and uses synthetic archive records. It does not assume Rails or an installed gem set. Ruby was unavailable during authoring, so all programs are complete or explicitly illustrative source with runnable:false. Expected output is reasoned from the stated inputs, not an observed interpreter result.
Integer division follows Ruby’s numeric contract
Integer division with / returns an integer and rounds toward negative infinity, so a negative non-divisible case differs from truncation toward zero. fdiv requests floating-point division. Ruby integers can grow beyond a fixed machine-word value as resources permit, but that does not make arbitrary calculations free or all target integrations exact. Float uses approximate arithmetic and cannot represent every decimal fraction. Choose units and rounding before formatting output. A displayed decimal is not a complete accounting model, and converting a result after integer division cannot recover the fraction already lost.
puts 7 / 2
puts(-7 / 2)
puts 7.fdiv(2)
value = 7
value = 'archive'
puts value.class
3
-4
3.5
String
Expected lines are 3, -4, 3.5, String. The negative quotient exposes floor division, and the final assignment changes the referenced object. No Ruby interpreter executed this source.
Assignment shares a reference unless a copy is made
Assigning an array or mutable string to another variable normally gives another reference to the same object. A mutation through either reference can therefore be visible through both. Reassignment only changes one binding. dup and clone have specific copy behavior, and an ordinary copy is generally shallow: nested objects can remain shared. Do not infer a deep snapshot from a new outer object. Decide who owns mutation and whether a caller receives a live object or an independent value, especially when a model is reused by previews, exports, or later processing.
Freeze protects one object, not its entire graph
freeze prevents supported mutation of the receiver object but does not recursively freeze referenced elements. The example freezes an outer array while deliberately changing a contained string. String.new makes the mutable string explicit rather than depending on a file’s frozen-string-literal setting. A constant name is also not a deep-immutability mechanism; constants reference objects and have their own reassignment conventions. Use immutable value elements or an explicit deeper ownership policy when a stable snapshot is required. Keep freezing, copying, and validating content separate in both implementation and documentation.
inner = String.new('raw')
outer = [inner].freeze
inner << '-checked'
puts outer.first
puts outer.frozen?
raw-checked
true
Expected output is raw-checked then true. The array is frozen while its element remains mutable. This is a shallow-freeze demonstration, not a recommended immutable model, and it was not run.
Output and labels do not validate a value
puts writes a textual representation with line-oriented behavior, while p uses inspect for diagnostic representation. Interpolation in a double-quoted string evaluates the enclosed expression; single-quoted strings have different interpolation and escape rules. Symbols are name-like objects and are distinct from strings, which matters for hash keys. Keep machine codes independent from display wording and use explicit units in names. A script printing a plausible label has not verified an identifier, permission, or external record. Start with deterministic local values before introducing input, files, or concurrent work with additional failure states.
Common errors and fixes
A negative quotient differs from expected truncation.
- Why it happens
- Ruby integer floor division was confused with another language’s rule.
- How to fix it
- Choose the numeric operation and rounding policy explicitly.
- Prevent it next time
- Include negative non-divisible examples in arithmetic checks.
Changing one string changes another apparent value.
- Why it happens
- Both variables reference the same mutable object.
- How to fix it
- Use an intentional copy or restrict mutation to an owner.
- Prevent it next time
- Review aliasing before promising independent state.
A frozen array’s nested object still changes.
- Why it happens
- Shallow freezing was mistaken for recursive immutability.
- How to fix it
- Choose immutable elements or a documented deeper snapshot policy.
- Prevent it next time
- Inspect every mutable level that the contract exposes.
Check your understanding
1. Predict -9 / 2 and -9.fdiv(2). Why does converting the first result afterward not produce the second?
Show worked solution
The integer quotient is -5 and the floating result is -4.5. Integer division already applied its floor rule, so later converting -5 to a float yields -5.0 rather than recovering the discarded fraction. Select the arithmetic semantics before presentation, and document the intended treatment of remainders.
2. Does assigning an archive array to a constant make its nested strings independent from a caller?
Show worked solution
No. The constant is another binding to an object, and even freezing the outer array would leave nested mutable strings shared. Establish a copy and ownership contract appropriate to the model. If independent immutable values are required, validate and build those values explicitly instead of relying on naming convention.
Key takeaways
- Ruby bindings, object mutation, copying, and freezing are different mechanisms.
- Reasoned output is not an executed Ruby result.