In Ruby, what does writing `class String` with a new method do when String already exists, and which code sees that method?
answer
- class keyword looks up the constant
- same class object, not a subclass
- existing instances see it too
- process-wide, not per file
- different superclass: TypeError
basics
~20 sWriting class String again reopens the existing String class instead of creating one, so the def adds a method to String itself. Every string in the process, including older strings and strings inside gems, can then call it.
solid answer
~40 sIn Ruby, `class String` looks up the constant `String`; because it already names a class, Ruby runs the body inside that same class object rather than defining a new one. Each `def` adds or replaces an entry in `String`'s method table. Method lookup happens at call time, so strings created before the reopening gain the method too, and so does every file and gem in the process — there is no per-file scope. Reopening with a conflicting superclass (`class String < Array`) raises `TypeError` (superclass mismatch), and reopening a constant that holds a non-class raises `TypeError` as well. That global reach is exactly why patches to core classes need care.
code
ruby · 12 linesgreeting = "hi"
class String
def shout = upcase + "!"
end
greeting.shout # => "HI!" -- created before the reopen
"ok".shout # => "OK!"
class String < Array
end
# => TypeError: superclass mismatch for class Stringgo deeper
Recall that class String reopens the existing class, and that the new method is available on every string in the process.
Explain the constant lookup behind the class keyword, dynamic method lookup for existing objects, and the superclass-mismatch TypeError.
Connect the global reach to real risks: collisions between libraries and replaced core behaviour inside code you never read.
Decide where core patches may live, who reviews them, and when a wrapper or refinement must replace a global patch.
## What the class keyword really does In many languages a class declaration defines a type once, at compile time. In Ruby, `class Name ... end` is executable code that runs top to bottom: 1. Ruby looks up the constant `Name` in the current scope. 2. If it is **not defined**, Ruby creates a new class, assigns it to the constant, and runs the body inside it. 3. If it **is defined and is a class**, Ruby runs the body inside **that existing class**. This is called **reopening** the class; Ruby classes are therefore called **open classes**. 4. If it is defined but holds something else (an integer, a module), Ruby raises `TypeError`. So writing `class String` in your own file does not create a new `String`; it gives you the real, built-in `String` to modify. Adding or changing methods on a class you do not own this way is **monkey patching**. ## Who sees the new method A `def` inside the reopened body adds an entry to `String`'s **method table**. Because Ruby looks methods up on every call, the effect is immediate and global: - **Existing instances**: a string created before the reopening can call the method afterwards. - **Every file**: there is no import boundary; code that never required your file still sees the method once your file has run. - **Every gem**: library code inside your dependencies sees it too, and so do other patches. - **Subclasses**: any subclass of `String` inherits the method. | Situation | Result | |---|---| | `class String; def shout = upcase + "!"; end` | `"hi".shout # => "HI!"` for every string | | Same name as an existing method | replaces it for everyone (warning only under `-w`) | | `class String < Object` | allowed: Object is already String's superclass | | `class String < Array` | `TypeError`: superclass mismatch for class String | ## Why this matters for core classes The global reach is the whole point — and the whole risk: - A helper like `String#titleize` becomes available everywhere without passing anything around, which is why libraries historically added convenience methods to core classes. - Two libraries defining the same method on `String` collide, and the one loaded last wins. - Replacing an existing method such as `String#length` changes the behaviour of code you never read, including the standard library. Because of this, most teams limit core patches to a few reviewed files, often a `core_ext` directory with one file per patched class, and prefer a helper method or a small wrapper class when the convenience is only needed in their own code. ## Related forms - `module Comparable ... end` reopens an existing module the same way. - Writing the body inside `String.class_eval { ... }` also modifies the existing class, with different scoping rules; that is its own topic. - A **refinement** limits a patch to the files that activate it with `using`, the main alternative when a global change is too risky. ## Common mistakes - **Assuming a new class was created.** There is no second `String` to find; the added method sits on the real one, next to `upcase` and `length`. - **Forgetting load order.** The method exists only after the reopening file has run, so code that runs earlier, for example during boot, raises `NoMethodError`. - **Replacing instead of adding.** Reusing a name `String` already has, such as `strip` or `length`, silently changes a core method for every library, and the `method redefined` warning appears only under `-w`. ## What interviewers are checking This is a screening question about Ruby's object model: a class is an ordinary object that stays mutable at run time. A good answer says that reopening modifies the existing class, that the change is process-wide and affects existing objects, and names the risk that follows — collisions and surprising behaviour in code you did not write.
- Can you reopen String to change its superclass?No. Reopening with an explicit superclass is allowed only when it matches the existing one; `class String < Array` raises `TypeError` with 'superclass mismatch for class String'. A class's superclass is fixed when the class is created.
- If a gem reopens String, does code that never requires that gem see the new method?Yes, once the gem's file has been loaded by anything in the process. `require` loads code into the one shared runtime; there is no per-file visibility for methods added to a class.
saying these in an interview costs you the question
- Reopening String creates a new class that shadows the built-in one.
- Only strings created after the reopening get the new method.
- The new method is visible only in the file that reopened the class.
- You can reopen String with a different superclass to change its parent.