ruby constructor return value?

  • Thread starter Brendan Stennett
  • Start date
B

Brendan Stennett

How do I alert an error if it occurs in an object's constructor?

Example:
Say im creating an object that relies on a give record from the database
zipcodes.

In php...
<?php
class Zip {
private $zipcode;
function __construct(zipcode) {
//..code the looks up zipcode
$numOfRecords = $mysqli->affected_rows;
if ($numOfRecords == 0) {
//this takes advantage of dynamically typed languages and passes a
boolean
//value instead of the newly created object to the variables thats
being
//assigned
return false;
} else {
$this->zipcode = zipcode;
}
}
}

//then...
if ($o = new Zip('90210')) {
//code if zipcode exists
} else {
//code if it doesn't
}
?>

In ruby..
class Zip
def initialize(zipcode)
o = Zipcode.find_by_zipcode(zipcode)
if o.length == 0
return false
else
@zipcode = zipcode
end
end
end

//then...
if o = Zip.new('00000')
//this block execute whether or not that zip code exists
else
//never happens
end

..now i know there are easier ways to do this, but its just an example
to illustrate my point.
 
L

Lyle Johnson

How do I alert an error if it occurs in an object's constructor?

Raise an exception.

class Zip
def initialize(zipcode)
o = Zipcode.find_by_zipcode(zipcode)
if o.length == 0
raise ArgumentError, "no zip code specified"
else
@zipcode = zipcode
end
end
end

begin
o = Zip.new('00000')
rescue ArgumentError
# deal with the error here
end

Hope this helps,

Lyle
 
B

Brendan Stennett

begin
o = Zip.new('00000')
rescue ArgumentError
# deal with the error here
end

I was reading up on exception handling..but i dont understand that
difference between throw/catch blocks and begin/resuce blocks. My 2
backgrounds (PHP and VB.NET) both seem to have similar structures in
Ruby. PHP had try/throw/catch blocks while ruby has throw/catch and
VB.NET has try/catch/finally while Ruby has begin/rescue/ensure.
 
T

Tim Hunter

Brendan said:
I was reading up on exception handling..but i dont understand that
difference between throw/catch blocks and begin/resuce blocks. My 2
backgrounds (PHP and VB.NET) both seem to have similar structures in
Ruby. PHP had try/throw/catch blocks while ruby has throw/catch and
VB.NET has try/catch/finally while Ruby has begin/rescue/ensure.

begin/rescue is for handling exceptions. throw/catch is for jumping out
of a deeply nested construct during normal processing.
 
T

Thomas Hurst

* Brendan Stennett ([email protected]) said:
if o = Zip.new('00000')
//this block execute whether or not that zip code exists
else
//never happens
end

Normally you'd use an exception; e.g. make a ZipCodeNotFound class
inherited from StandardError and rescue it. You could even do:

o = Zip.new(..) rescue nil

And this will rescue the exception and return nil. It will also eat any
other StandardError, so beware. If you want users of the class to be
able to just check for nil/false, make a factory method:

class Zip
def self.find(zip)
new(zip)
rescue ZipCodeNotFound
false
end
end

o = Zip.find('..')

However, new is just a method like any other; you can override it if you
really want it to potentially return nil:

class Zip
def self.new(*args)
o = allocate
if o.__send__:)initialize, *args)
return o
else
return nil
end
end
end

Allocate will make a new object without calling the constructor, you can
then call initialize yourself (using __send__ as it's a private method)
and conditionally return your new object.

Since this may be surprising behavior for .new I don't really recommend
it, though.
 
B

Brendan Stennett

Normally you'd use an exception; e.g. make a ZipCodeNotFound class
inherited from StandardError and rescue it. You could even do:


Now...is it better to make a new exception for each type of error that
could happen or just use some standard exception (or general exception
that inherits StandardError that i could make) and change the error
message?
 
P

Paul McMahon

Brendan said:
In ruby..
class Zip
def initialize(zipcode)
o = Zipcode.find_by_zipcode(zipcode)
if o.length == 0
return false
else
@zipcode = zipcode
end
end
end

//then...
if o = Zip.new('00000')
//this block execute whether or not that zip code exists
else
//never happens
end

..now i know there are easier ways to do this, but its just an example
to illustrate my point.

Maybe I'm missing something, but why not just push the creation into
Zipcode. I can't see why you would want to do this check in the
constructor itself...

class Zipcode
def self.find_by_zipcode(zipcode)
zipcode = ... # query db
if zipcode.length == 0
nil
else
Zip.new(zipcode)
end
end
end

class Zip
def initialize(zipcode)
@zipcode = zipcode
end
end

//then...
if o = Zip.find_by_zipcode('00000')
// zipcode exists
else
// doesn't exist
end
 
B

Brendan Stennett

Hmm....well im not used to using Models so i didn't really think about
attacking it that way...i dont see why i couldnt though. I'm going to
play around with that...see how it goes
 
T

Thom Wharton

Thomas said:
Normally you'd use an exception; e.g. make a ZipCodeNotFound class
inherited from StandardError and rescue it. You could even do:

o = Zip.new(..) rescue nil

And this will rescue the exception and return nil. It will also eat any
other StandardError, so beware. If you want users of the class to be
able to just check for nil/false, make a factory method:

class Zip
def self.find(zip)
new(zip)
rescue ZipCodeNotFound
false
end
end

o = Zip.find('..')

However, new is just a method like any other; you can override it if you
really want it to potentially return nil:

class Zip
def self.new(*args)
o = allocate
if o.__send__:)initialize, *args)
return o
else
return nil
end
end
end

Allocate will make a new object without calling the constructor, you can
then call initialize yourself (using __send__ as it's a private method)
and conditionally return your new object.

Since this may be surprising behavior for .new I don't really recommend
it, though.

Is this info still valid? More specifically, would I have to create a
new method for my class to have it return nil? And would my class
initializer method have to raise an exception or could it return nil if
the construction fails?

Basically, I am trying to get new to return nil if construction of the
object fails. I have a strong background in C++ coding, and one thing
that has always irked me about the language is that constructors cant
fail -- the object is always created regardless of whether or not it can
be properly constructed.

Btw, I've noticed that the File class will return nil if you call the
new method with a filename that doesnt refer to an existing file. Does
the File class have its own new/initialize methods? If so, is that code
available online for examination?

Thanks,
Thom
 
S

Stefano Crocco

|Is this info still valid? More specifically, would I have to create a
|new method for my class to have it return nil?

Yes, it's still valid. Class.new, which is the method you actually invoke when
you write MyCls.new, always returns the new instance, regardless of the value
returned by initialize (if it were not so, every initialize method should
explicitly return self to have new return the new instance).
|And would my class
|initializer method have to raise an exception or could it return nil if
|the construction fails?

If you override new, you can have it treat the value returned by initialize
how you want. There's nothing special in the initialize method (aside from the
fact that it's always private). If you want, you can decide that your
initialize method will return nil if it failed and anything else if it
succeeds. Or it may return 5 if it fails and 2 if it succeeds. Then in the new
method you check by the return value of initialize and proceed accordingly.
|Btw, I've noticed that the File class will return nil if you call the
|new method with a filename that doesnt refer to an existing file. Does
|the File class have its own new/initialize methods? If so, is that code
|available online for examination?

It does, but they're written in C, so you'll have to understand a bit of ruby
C api to understand them. You can download the ruby source from
www.ruby-lang.org. The initialize method is implemented in rb_file_initialize
C function while I think (but I'm not sure) that the new method is implemented
in rb_io_s_new. Both functions are defined in io.c.

However, implementing a new method is truly quite easy:

class MyClass

def self.new *args
inst = allocate
res = inst.send :initialize, *args
if res < 5 then "Hello"
else res
end
end

def initialize x
x
end

end

i1 = MyClass.new 3
p i1
i2 = MyClass.new 6
p i2

MyClass.new returns the new instance if the initialize method returns a number
greater than 4 and the string "Hello" if initialize returns a number up to
four.

I hope this helps

Stefano
 
R

Robert Klemme

Is this info still valid? More specifically, would I have to create a
new method for my class to have it return nil? And would my class
initializer method have to raise an exception or could it return nil if
the construction fails?

For failed construction you need to throw an exception. Everything else
is pretty useless. Btw, even the "trick" shown above with evaluating
the return value of #initialize creates the object even in error cases.
IMHO there is really no point in doing that - the example above merely
demonstrates that you can react on the return value of #initialize if
you like to.
Basically, I am trying to get new to return nil if construction of the
object fails. I have a strong background in C++ coding, and one thing
that has always irked me about the language is that constructors cant
fail -- the object is always created regardless of whether or not it can
be properly constructed.

Hmm, that's only part of the story: if it is created on the stack nobody
will be able to access it in case the constructor throws. For objects
allocated on the heap I am not so sure but I believe the allocation
needs to be reversed in case of an exception as well; so even if you
would leak this to somewhere else (say a global variable) it would not
be of any use - in fact, that would be a dangerous thing to do.

On a more general level, there is no alternative approach: you cannot
test all the conditions beforehand. It's the same as testing whether a
remote server can be connected: the test can succeed yet the "real"
connection can still fail (e.g. because the server crashed after
checking). So, you have to create the object and in case of exceptions
revert it.
Btw, I've noticed that the File class will return nil if you call the
new method with a filename that doesnt refer to an existing file. Does
the File class have its own new/initialize methods? If so, is that code
available online for examination?

Not sure what you are referring to but File.new and File.open will throw
an exception if they fail:

robert@fussel:~$ ruby19 -e 'p File.new("not_existent")'
-e:1:in `initialize': No such file or directory - not_existent
(Errno::ENOENT)
from -e:1:in `new'
from -e:1:in `<main>'
robert@fussel:~$

Kind regards

robert
 
R

Robert Klemme

I'm not sure what you mean here. In both C++ and Ruby, constructors can
fail - they do so by raising an exception - and, if they do fail, no
object is created.

That's not true: the object is created and then will be GC'ed. You can
easily demonstrate it:

robert@fussel:~$ ruby19 f.rb
[83760838, false]
[83760376, true]
f.rb:9:in `initialize': Constructor error (RuntimeError)
from f.rb:14:in `new'
from f.rb:14:in `<main>'
83760376
83760838
robert@fussel:~$ cat f.rb
class X
def self.g(x)
ObjectSpace.define_finalizer(x) {|id| puts id}
end

def initialize(fail = false)
p [object_id, fail]
X.g(self)
raise "Constructor error" if fail
end
end

X.new
X.new true
robert@fussel:~$

There is no other alternative to this approach. The instance is
*always* created. It's only that you typically do not leak self from
#initialize and so in case of exceptions nobody has a reference to the
object and hence it is unusable.

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,038
Latest member
OrderProperKetocapsules

Latest Threads

Top