How to judge whether a method has defined in a class?
Asked Answered
P

3

9
class C1
  unless method_defined? :hello  # Certainly, it's not correct. I am asking to find something to do this work.
    def_method(:hello) do
      puts 'Hi Everyone'
    end
  end
end

So, how to judge whether a method has defined or not?

Provenience answered 4/8, 2010 at 6:5 Comment(0)
M
19

The code you posted works just fine for checking whether the method is defined or not. Module#method_defined? is exactly the right choice. (There's also the variants Module#public_method_defined?, Module#protected_method_defined? and Module#private_method_defined?.) The problem is with your call to def_method, which doesn't exist. (It's called Module#define_method).

This works like a charm:

class C1      
  define_method(:hello) do
    puts 'Hi Everyone'
  end unless method_defined? :hello
end

However, since you already know the name in advance and don't use any closure, there is no need to use Module#define_method, you can just use the def keyword instead:

class C1
  def hello
    puts 'Hi Everyone'
  end unless method_defined? :hello
end

Or have I misunderstood your question and you are worried about inheritance? In that case, Module#method_defined? is not the right choice, because it walks the entire inheritance chain. In that case, you will have to use Module#instance_methods or one of its cousins Module#public_instance_methods, Module#protected_instance_methods or Module#private_instance_methods, which take an optional argument telling them whether to include methods from superclasses / mixins or not. (Note that the documentation is wrong: if you pass no arguments, it will include all the inherited methods.)

class C1
  unless instance_methods(false).include? :hello
    def hello
      puts 'Hi Everyone'
    end
  end
end

Here's a little test suite that shows that my suggestion works:

require 'test/unit'
class TestDefineMethodConditionally < Test::Unit::TestCase
  def setup
    @c1 = Class.new do
      def self.add_hello(who)
        define_method(:hello) do
          who
        end unless method_defined? :hello
      end
    end

    @o = @c1.new
  end

  def test_that_the_method_doesnt_exist_when_it_hasnt_been_defined_yet
    assert [email protected]_defined?(:hello)
    assert [email protected]_methods.include?(:hello)
    assert [email protected]?(:hello)
    assert [email protected]_to?(:hello)
    assert_raise(NoMethodError) { @o.hello }
  end

  def test_that_the_method_does_exist_after_it_has_been_defined
    @c1.add_hello 'one'

    assert @c1.method_defined?(:hello)
    assert @c1.instance_methods.include?(:hello)
    assert @o.methods.include?(:hello)
    assert_respond_to @o, :hello
    assert_nothing_raised { @o.hello }
    assert_equal 'one', @o.hello
  end

  def test_that_the_method_cannot_be_redefined
    @c1.add_hello 'one'

    assert @c1.method_defined?(:hello)
    assert @c1.instance_methods.include?(:hello)
    assert @o.methods.include?(:hello)
    assert_respond_to @o, :hello
    assert_nothing_raised { @o.hello }
    assert_equal 'one', @o.hello

    @c1.add_hello 'two'

    assert @c1.method_defined?(:hello)
    assert @c1.instance_methods.include?(:hello)
    assert @o.methods.include?(:hello)
    assert_respond_to @o, :hello
    assert_nothing_raised { @o.hello }
    assert_equal 'one', @o.hello, 'it should *still* respond with "one"!'
  end
end
Microdont answered 4/8, 2010 at 7:23 Comment(1)
The test passes in 1.9.2 but test_that_the_method_cannot_be_redefined and test_that_the_method_does_exist_after_it_has_been_defined fail under ruby 1.8.7.Vacillating
T
2

Look at the Ruby Object class. It has a methods function to get an list of methods and a respond_to? to check for a specific method. So you want code like this:

class C1
  def add_hello
    unless self.respond_to? "hello"
      def hello
        puts 'Hi Everyone'
      end
    end  
  end
end

cone.hello      #This would fail
cone.add_hello  
cone.hello      #This would work
Tripinnate answered 4/8, 2010 at 6:16 Comment(5)
-1, for 4 reasons: 1) it doesn't address the OPs problem. Using method_defined? is just fine, the problem is that he misspelled define_method. 2) respond_to? doesn't check for a specific method, it checks whether an object responds to a specific message. (Hint: the name sorta gives it away, don't you think?) Understanding the difference between methods and messages is fundamental to understanding Ruby and even OO in general. 3) In your code, you check whether the class object C1 responds to :hello, and based on that you define a hello method for instances of C1. ...Fabricant
... Again: understanding the difference between instances and classes is fundamental to understanding Ruby and class-based OO in general. 4) Your testsuite doesn't actually test the thing that the OP cares about, namely that you cannot define the method twice. You only test that you can define the method once, but that was not the question.Fabricant
@jorg, he put the respond_to? in an instance method (add_hello), so it does check the instance (and not the class). Also, just out of curiosity, what is the difference between sending a message and invoking a method in Ruby? :)Emplace
@jorg, on your point 2: In ruby methods are invoked by the passing of messages, which is fundamental to understanding ruby.Tripinnate
@jorg. But in general I agree that I miss understood the question. I have never done something like this but have seen from the documentation that it is possible so I quickly played around to find a solution.Tripinnate
S
1

The Object class has the method "methods": docs

 class Klass
   def kMethod()
   end
 end
 k = Klass.new
 k.methods[0..9]    #=> ["kMethod", "freeze", "nil?", "is_a?",
                    #    "class", "instance_variable_set",
                    #    "methods", "extend", "__send__", "instance_eval"]
 k.methods.length   #=> 42
Surfacetoair answered 4/8, 2010 at 6:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.