views:

69

answers:

3
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 mehtod has defined or not?

+1  A: 

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
demas
+2  A: 

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
Gerhard
-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`. ...
Jörg W Mittag
... 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.
Jörg W Mittag
@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? :)
banister
@jorg, on your point 2: In ruby methods are invoked by the passing of messages, which is **fundamental** to understanding ruby.
Gerhard
@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.
Gerhard
+4  A: 

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
  unless method_defined? :hello
    define_method(:hello) do
      puts 'Hi Everyone'
    end
  end
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
  unless method_defined? :hello
    def hello
      puts 'Hi Everyone'
    end
  end
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)
        unless method_defined? :hello
          define_method(:hello) do
            who
          end
        end
      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
Jörg W Mittag