Ruby Constructors and Exceptions
Asked Answered
C

5

10

New to Ruby, and I'm trying to figure out what idiom to use to restrict some integer values to the constructor of a class.

From what I've done so far, if I raise an exception in initialize(), the object is still created but will be in an invalid state (for example, some nil values in instance variables). I can't quite see how I'm supposed to restrict the values without going into what looks unnecessarily big steps like restricting access to new().

So my question is, by what mechanism can I restrict the range of values an object is instantiated with?

Codie answered 28/9, 2009 at 2:12 Comment(1)
Yeah, I confused myself on this problem. I was rescueing the exception in the constructor, so it stands to reason that the instance would be created.Codie
V
13

Huh, you are entirely correct that the object still lives even if initialize raises an exception. However, it will be quite hard for anyone to hang on to a reference unless you leak self out of initialize like the following code I just wrote does:

>> class X
>>   def initialize
>>     $me = self
>>     raise
>>   end
>>   def stillHere!
>>     puts "It lives!"
>>   end
>> end
=> nil
>> t = X.new
RuntimeError: 
    from (irb):14:in `initialize'
    from (irb):20:in `new'
    from (irb):20
>> t
=> nil
>> $me
=> #<X:0xb7ab0e70>
>> $me.stillHere!
It lives!
Valona answered 28/9, 2009 at 14:12 Comment(0)
F
4

I'm not sure about this statement:

From what I've done so far, if I raise an exception in initialize(), the object is still created but will be in an invalid state (for example, some nil values in instance variables).

class Foo

  def initialize(num)
    raise ArgumentError.new("Not valid number") if num > 1000
    @num = num
  end 

end

f = Foo.new(4000) #=> n `initialize': not valid (RuntimeError)
Fauman answered 28/9, 2009 at 3:13 Comment(2)
Hmm, I'm going to have to review my code and see if maybe I'm mistaken. Thanks for the answer.Codie
Actually, the object is created, it's just that .new won't return the reference, so unless the object links to itself during initialize it will get gc'ed.Valona
R
2

If I'm reading your question correctly, what you want is something like this:

class SerialNumber
  VALID_SERIAL_NUMBERS = (0..10,000,000,000)
  def initialize(n)
    raise ArgumentError.new("Serial numbers must be positive integers less than 10 Billion") unless VALID_SERIAL_NUMBERS.include?(n)
    @n = n
  end
end

Don't worry that SerialNumber.new creates an instance before that initialize method is called -- it'll get cleaned up if the error is raised.

Radiotransparent answered 28/9, 2009 at 3:14 Comment(0)
C
0

Using the validatable module seems like a really good fit in the context.

Here is an example for how to use it:

  class Person
    include Validatable
    validates_numericality_of :age
  end

For doing a numbers only in a particular range it would be:

  class Person
    include Validatable
    validates_numericality_of :age
    validates_true_for :age, :logic => lambda { (0..100).include?(age) }
  end

This of course will validate that age is within the range of 0 and 100.

Cowry answered 28/9, 2009 at 3:59 Comment(0)
P
0

I'm dropping in here because things have changed.

As of ruby version "3.2.1", the object will be instantiated and assigned to the variable. No "self reference leaking" needed, plus using global variables is bad design.

All of the instance variables that were assigned before the exception will be present in the variable reference to the object. The other instance variables will be nil.

Passport answered 21/5, 2024 at 12:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.