views:

247

answers:

4

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?

+1  A: 

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)
khelll
Hmm, I'm going to have to review my code and see if maybe I'm mistaken. Thanks for the answer.
phasetwenty
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.
DigitalRoss
+1  A: 

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.

James A. Rosen
A: 

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.

jkupferman
+4  A: 

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!
DigitalRoss