In Ruby, how does private_class_method :new force Salary objects through factory methods, and what can still create one without them?
answer
- new is a singleton-side method
- factories call new with no receiver
- subclasses inherit the private new
- allocate stays public
- a guard against accidents, not security
basics
~20 sprivate_class_method :new makes Salary.new private, so outside calls raise NoMethodError while class methods such as Salary.from_cents call new without a receiver. Salary.allocate, send, and dup or clone of an existing Salary still bypass the factories.
solid answer
~40 s`new` is a method called on the class object, so ordinary `private` does not reach it; `private_class_method :new` makes it private on the class's singleton side. Outside code calling `Salary.new(5_000)` then gets `NoMethodError` ("private method 'new' called for class Salary"), while factories such as `def self.from_cents(cents) = new(cents)` call it with an implicit receiver and succeed. Subclasses inherit the private `new` too, unless they call `public_class_method :new`. It is a guard against accidental construction, not a wall: `Salary.allocate` is still public and skips `initialize`, reflection with `send(:new)` ignores visibility, and `dup` or `clone` copy an existing Salary without calling `new`. Validation that must always hold therefore belongs in `initialize`, which every `new` call runs, factories included.
code
ruby · 18 linesclass Salary
p private_class_method(:new) # => Salary
def self.from_units(units) = new(units * 100)
def initialize(cents)
raise ArgumentError, "negative salary" if cents.negative?
@cents = cents
end
end
class Bonus < Salary; end
Salary.from_units(5) # => #<Salary:0x... @cents=500>
Bonus.from_units(1).class # => Bonus
Bonus.new(1) # NoMethodError: private method 'new' called for class Bonus
Salary.public_send(:new, 1) # NoMethodError: public_send respects visibility
Salary.allocate # => a Salary with no @cents; initialize skippedgo deeper
Recall that private_class_method :new hides the constructor and that class methods inside the class can still call new.
Explain why plain private does not reach new, how factories call it without a receiver, and that subclasses inherit the private new.
Show judgment about what private new does and does not guarantee: allocate, send and copying bypass it, so invariants belong in initialize and factories express intent.
Decide when a codebase should route construction through named factories, weighing clearer intent and caching against extra indirection and the false sense of enforcement it can create.
## Why private alone does not work `Salary.new` is a call on the **class object**. The method that runs is `Class#new`, found through the class's singleton side. A bare `private` section, or `private :new`, only affects **instance methods** of `Salary`, so neither hides `Salary.new`. `Module#private_class_method` is the tool its own documentation names for this: it makes existing class methods private and is "often used to hide the default constructor `new`". ## The factory pattern ```ruby class Salary private_class_method :new def self.from_cents(cents) = new(cents) def self.from_units(units) = new(units * 100) def initialize(cents) raise ArgumentError, "negative salary" if cents.negative? @cents = cents end end Salary.from_units(50) # works Salary.new(5_000) # NoMethodError: private method 'new' called for class Salary ``` How each piece behaves: 1. **Inside class methods**, `new(cents)` has no explicit receiver, so the private method is allowed. `self.new(cents)` works as well, since a literal `self` receiver is allowed for private methods. 2. **Outside the class**, any `Salary.new(...)` raises `NoMethodError`. 3. **The return value** of `private_class_method` is the class itself, not the list of names. 4. **Subclasses** inherit the private `new`: `class Bonus < Salary; end; Bonus.new(1)` raises too, while `Bonus.from_cents(1)` returns a `Bonus` because `new` runs on `Bonus`. A subclass that wants a public constructor can call `public_class_method :new`. ## What still bypasses it | Route | Why it gets through | |---|---| | `Salary.allocate` | `Class#allocate` stays public and never runs `initialize` | | `Salary.send(:new, 1)` | `send` ignores visibility | | `salary.dup`, `salary.clone` | copying allocates a new object and copies state without calling `new` | | deserialization libraries | they typically allocate and fill objects without calling `new` | `Salary.public_send(:new, 1)` does **not** get through: `public_send` respects visibility and raises `NoMethodError`. ## What that means for design - **Put invariants in `initialize`.** Every `new` call runs it, whichever factory made the call, so a check there cannot be skipped by adding a new factory. Only paths that bypass `new`, such as `allocate`, copying and deserialization, skip it. - **Use private `new` to express intent**: "construct me through these named methods", for example because the unit of the argument is ambiguous (`from_cents` versus `from_units`) or because instances are cached. - **Do not treat it as security.** Anything with access to the process can reach `allocate` or `send`; the goal is to stop honest mistakes. - **Keep the factories few and named for what differs.** Several factories that all call `new` with the same arguments add names without adding meaning. ## Where interviewers push - **"Why not raise in `initialize` to block direct construction?"** Because factories call `new`, which calls `initialize`; `initialize` has no clean way to tell who called `new`. Visibility on `new` is the tool that distinguishes callers. - **"Does the factory need `send`?"** No. Inside a class method, `self` is the class, so a bare `new(...)` is an implicit-receiver call that private allows. - **"Can you undo it?"** Yes: `public_class_method :new` makes it public again, in the same class or a subclass. ## Related uses - **Instance caches**: a factory returns an existing object for a key instead of building a new one, which only works if nobody calls `new` directly. - **The standard library's `Singleton` module** makes both `new` and `allocate` private in the same way, so that only its `instance` method creates the object.
- In Ruby, why does private :new inside the class body not hide Salary.new?`private :new` looks for an instance method named `new` on `Salary`, and instances have no such method, so it raises `NameError`. The constructor lives on the class object's side, which is why `private_class_method :new` exists.
- In Ruby, if Salary makes new private, where should the non-negative check live so no factory can skip it?In `initialize`. Every call to `new`, from any factory, runs `initialize`, so the check holds for all of them. Putting it in each factory means a future factory can forget it. Only paths that avoid `new` entirely, such as `allocate`, skip `initialize`.
saying these in an interview costs you the question
- A private section in the class body hides Salary.new
- private_class_method :new makes it impossible to create a Salary any other way
- Subclasses of Salary get a public new again automatically
- public_send(:new) bypasses a private new
- Factory methods must call self.class.send(:new) to reach a private new