Search code examples
ruby-on-railsrubymethodsdefined

How to judge whether a method has defined in a class?


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?


Solution

  • 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