ruby constructor return value?

Discussion in 'Ruby' started by Brendan Stennett, Apr 2, 2008.

  1. 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.
    --
    Posted via http://www.ruby-forum.com/.
     
    Brendan Stennett, Apr 2, 2008
    #1
    1. Advertising

  2. Brendan Stennett

    Lyle Johnson Guest

    On Wed, Apr 2, 2008 at 4:08 PM, Brendan Stennett <> wrote:

    > 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
     
    Lyle Johnson, Apr 2, 2008
    #2
    1. Advertising

  3. >
    > 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.
    --
    Posted via http://www.ruby-forum.com/.
     
    Brendan Stennett, Apr 2, 2008
    #3
  4. Brendan Stennett

    Tim Hunter Guest

    Brendan Stennett wrote:
    >> 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.


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

    --
    RMagick: http://rmagick.rubyforge.org/
    RMagick 2: http://rmagick.rubyforge.org/rmagick2.html
     
    Tim Hunter, Apr 2, 2008
    #4
  5. Brendan Stennett

    Thomas Hurst Guest

    * Brendan Stennett () wrote:

    > 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.

    --
    Thomas 'Freaky' Hurst
    http://hur.st/
     
    Thomas Hurst, Apr 3, 2008
    #5
  6. > 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?
    --
    Posted via http://www.ruby-forum.com/.
     
    Brendan Stennett, Apr 3, 2008
    #6
  7. Brendan Stennett

    Paul McMahon Guest

    Brendan Stennett wrote:
    >
    > 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
     
    Paul McMahon, Apr 3, 2008
    #7
  8. 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
    --
    Posted via http://www.ruby-forum.com/.
     
    Brendan Stennett, Apr 3, 2008
    #8
  9. Brendan Stennett

    Thom Wharton Guest

    Thomas Hurst wrote:
    > * Brendan Stennett () wrote:
    >
    >> 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.


    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
    --
    Posted via http://www.ruby-forum.com/.
     
    Thom Wharton, Jan 7, 2010
    #9
  10. On Thursday 07 January 2010, Thom Wharton wrote:

    > |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
     
    Stefano Crocco, Jan 7, 2010
    #10
  11. On 01/07/2010 09:13 PM, Thom Wharton wrote:
    > Thomas Hurst wrote:
    >> * Brendan Stennett () wrote:
    >>
    >>> 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.

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

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Jan 7, 2010
    #11
  12. On 01/08/2010 12:39 PM, I V wrote:
    > On Thu, 07 Jan 2010 15:13:30 -0500, Thom Wharton wrote:
    >> 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.

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


    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Jan 8, 2010
    #12
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Greenhorn
    Replies:
    15
    Views:
    881
    Keith Thompson
    Mar 6, 2005
  2. Replies:
    3
    Views:
    359
    Victor Bazarov
    Jul 29, 2005
  3. Neelesh Bodas
    Replies:
    3
    Views:
    338
    Alf P. Steinbach
    Nov 16, 2005
  4. SzH
    Replies:
    10
    Views:
    665
    James Kanze
    Apr 26, 2007
  5. Generic Usenet Account
    Replies:
    10
    Views:
    2,341
Loading...

Share This Page