class_eval doesn't find const_missing

Discussion in 'Ruby' started by Andrew Berkeley, Apr 21, 2011.

  1. I am trying to write a DSL which uses method_missing and const_missing
    to catch upper and lowercase names (specified in isolation) and fetch
    the asscoated (already initialized) objects. This works great until I
    need to use class_eval which seemingly doesn't follow the custom
    const_missing method.

    Here's a more simple example of a class with a constant and
    #method_missing and #const_missing methods.

    class DummyClass

    NAME = 'dummy constant'

    def self.method_missing(method,*args,&block)
    return "this #{method} was captured by #method_missing"
    end

    def self.const_missing(constant)
    return "this #{constant} was captured by #const_missing"
    end

    end

    Accessing the constant is no problem
    > DummyClass::NAME

    => "dummy constant"

    Unknown methods and constants are handled as expected
    > DummyClass::ANOTHER_CONSTANT

    => "this ANOTHER_CONSTANT was captured by #const_missing"
    > DummyClass.some_method

    => "this some_method was captured by #method_missing"

    Accessing the constant via class_eval is variable - it works when
    evaluating strings but not blocks.
    > DummyClass.class_eval "NAME"

    => "dummy constant"
    > DummyClass.class_eval {NAME}

    NameError: uninitialized constant NAME
    from (irb):14
    from (irb):14:in `class_eval'
    from (irb):14
    from :0

    This is partly understood since blocks are scoped to the context in
    which they were created. But even if the explicit constant is not
    recognised, why isn't the class #const_missing method invoked?

    Does the class_eval method allow the method_missing method to be used?
    > DummyClass.class_eval "dummy_method"

    => "this dummy_method was captured by #method_missing"
    > DummyClass.class_eval {dummy_method}

    => "this dummy_method was captured by #method_missing"

    Yes. #method_missing works as expected, in both string and block
    formats. So only #const_missing is causing a problem.

    Interestingly, an explicit call to #const_missing inside a class_eval
    block DOES work.
    > DummyClass.class_eval {const_missing :ANOTHER_CONSTANT}

    => "this ANOTHER_CONSTANT was captured by #const_missing"

    Anybody understand this, and is there a way to get around it?

    Cheers

    --
    Posted via http://www.ruby-forum.com/.
    Andrew Berkeley, Apr 21, 2011
    #1
    1. Advertising

  2. If you add this code:

    class Object
    def self.const_missing(constant)
    return "this #{constant} found in obj!"
    end
    end

    Then you get the following:

    >> DummyClass.class_eval "NAME"

    => "dummy constant"
    >> DummyClass.class_eval {NAME}

    => "this NAME found in obj!"
    >> NAME

    => "this NAME found in obj!"

    Not sure if that helps you much, but it seems that class_eval doesn't
    affect the constant lookup in the way that you'd like.

    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Apr 21, 2011
    #2
    1. Advertising

  3. http://stackoverflow.com/questions/...stant-lookup-work-in-instance-eval-class-eval

    Const lookup varies between 1.8, 1.9.1, and 1.9.2.

    Basically, constants are lexically scoped.

    You can use "const_get" I believe to lookup your constant in the
    current binding rather than in the lexical scope.

    stephen

    On Apr 21, 2011, at 2:20 PM, Andrew Berkeley wrote:

    > I am trying to write a DSL which uses method_missing and const_missing
    > to catch upper and lowercase names (specified in isolation) and fetch
    > the asscoated (already initialized) objects. This works great until I
    > need to use class_eval which seemingly doesn't follow the custom
    > const_missing method.
    >
    > Here's a more simple example of a class with a constant and
    > #method_missing and #const_missing methods.
    >
    > class DummyClass
    >
    > NAME = 'dummy constant'
    >
    > def self.method_missing(method,*args,&block)
    > return "this #{method} was captured by #method_missing"
    > end
    >
    > def self.const_missing(constant)
    > return "this #{constant} was captured by #const_missing"
    > end
    >
    > end
    >
    > Accessing the constant is no problem
    >> DummyClass::NAME

    > => "dummy constant"
    >
    > Unknown methods and constants are handled as expected
    >> DummyClass::ANOTHER_CONSTANT

    > => "this ANOTHER_CONSTANT was captured by #const_missing"
    >> DummyClass.some_method

    > => "this some_method was captured by #method_missing"
    >
    > Accessing the constant via class_eval is variable - it works when
    > evaluating strings but not blocks.
    >> DummyClass.class_eval "NAME"

    > => "dummy constant"
    >> DummyClass.class_eval {NAME}

    > NameError: uninitialized constant NAME
    > from (irb):14
    > from (irb):14:in `class_eval'
    > from (irb):14
    > from :0
    >
    > This is partly understood since blocks are scoped to the context in
    > which they were created. But even if the explicit constant is not
    > recognised, why isn't the class #const_missing method invoked?
    >
    > Does the class_eval method allow the method_missing method to be used?
    >> DummyClass.class_eval "dummy_method"

    > => "this dummy_method was captured by #method_missing"
    >> DummyClass.class_eval {dummy_method}

    > => "this dummy_method was captured by #method_missing"
    >
    > Yes. #method_missing works as expected, in both string and block
    > formats. So only #const_missing is causing a problem.
    >
    > Interestingly, an explicit call to #const_missing inside a class_eval
    > block DOES work.
    >> DummyClass.class_eval {const_missing :ANOTHER_CONSTANT}

    > => "this ANOTHER_CONSTANT was captured by #const_missing"
    >
    > Anybody understand this, and is there a way to get around it?
    >
    > Cheers
    >
    > --
    > Posted via http://www.ruby-forum.com/.
    >
    >
    Stephen Prater, Apr 21, 2011
    #3
  4. Andrew Berkeley

    7stud -- Guest

    Andrew Berkeley wrote in post #994369:
    >
    > Interestingly, an explicit call to #const_missing inside a class_eval
    > block DOES work.
    >> DummyClass.class_eval {const_missing :ANOTHER_CONSTANT}

    > => "this ANOTHER_CONSTANT was captured by #const_missing"
    >
    > Anybody understand this, and is there a way to get around it?
    >


    This line:

    DummyClass.class_eval {const_missing :ANOTHER_CONSTANT}

    is equivalent to:

    DummyClass.class_eval {
    self.const_missing:)ANOTHER_CONSTANT)
    }

    And because class_eval() changes self inside the block to be equal to
    the receiver, the above is equivalent to:

    DummyClass.class_eval {
    DummyClass.const_missing:)ANOTHER_CONSTANT)
    }

    ...producing the output you see.


    Also, note the output here:

    DummyClass.class_eval do
    puts self::NAME
    end

    --output:--
    dummy constant

    --
    Posted via http://www.ruby-forum.com/.
    7stud --, Apr 21, 2011
    #4
  5. Andrew Berkeley

    7stud -- Guest

    Stephen Prater wrote in post #994391:
    >

    http://stackoverflow.com/questions/...stant-lookup-work-in-instance-eval-class-eval
    >
    > Const lookup varies between 1.8, 1.9.1, and 1.9.2.
    >
    > Basically, constants are lexically scoped.
    >
    > You can use "const_get" I believe to lookup your constant in the
    > current binding rather than in the lexical scope.
    >


    That works for me in ruby 1.9.2:

    DummyClass.class_eval do
    puts const_get:)NAME)
    end

    --output:--
    dummy constant

    That makes sense because the call is really:

    puts self.const_get:)NAME)

    and because self is equal to DummyClass, that is equivalent to:

    puts DummyClass.const_get:)NAME)

    which like class_eval'ing a string does the lookup in the "directory"
    DummyClass.

    --
    Posted via http://www.ruby-forum.com/.
    7stud --, Apr 22, 2011
    #5
  6. Andrew Berkeley

    7stud -- Guest

    7stud -- wrote in post #994396:
    >
    > In "The Well-Grounded Rubyist" David Black explains that constants are
    > like files in a file system, and depending on what "directory" you are
    > currently in, it will determine the "path name" to the constant that you
    > are interested in retrieving. It seems that class_eval does not affect
    > the path name to a constant. But then I don't understand how
    > class_eval'ing a string changes that.
    >



    Here is a blog post from 2007 asking the exact same question:

    http://www.pgrs.net/2007/9/12/ruby-constants-have-weird-behavior-in-class_eval

    As one person replied, constants are looked up by the parser, so their
    lexical scope determines the proper 'path' to the constant; while
    eval'ing a string happens in the 'dynamic scope' at runtime.

    --
    Posted via http://www.ruby-forum.com/.
    7stud --, Apr 22, 2011
    #6
  7. Thanks People - very helpful and seems pretty clear what's going on
    then. A lesson learned for me!

    The Object monkeypatch appears to work well for what I was wanting to
    do..

    --
    Posted via http://www.ruby-forum.com/.
    Andrew Berkeley, Apr 22, 2011
    #7
    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. Hal E. Fulton

    const_missing

    Hal E. Fulton, Aug 21, 2003, in forum: Ruby
    Replies:
    2
    Views:
    96
    Christoph R.
    Aug 21, 2003
  2. Thomas
    Replies:
    1
    Views:
    97
    Thomas
    Sep 1, 2003
  3. Elias Athanasopoulos

    const_missing

    Elias Athanasopoulos, Jul 11, 2004, in forum: Ruby
    Replies:
    5
    Views:
    112
    Elias Athanasopoulos
    Jul 14, 2004
  4. T. Sawyer

    Obtaining object in const_missing

    T. Sawyer, Aug 19, 2004, in forum: Ruby
    Replies:
    3
    Views:
    77
    T. Onoma
    Aug 20, 2004
  5. El Barto
    Replies:
    2
    Views:
    103
    Simon Kröger
    Oct 5, 2005
Loading...

Share This Page