2008-11-14 5 views
46

루비에서 method_missing 메서드를 정의 할 때주의해야 할 사항이 있습니까? 상속, 예외 던지기, 퍼포먼스 또는 그 밖의 다른 것들과의 명백한 상호 작용이 있는지 궁금합니다.루비에서 gotchas를 method_missing

답변

57

다소 명백한 것 : method_missing을 다시 정의하면 항상 respond_to?을 다시 정의하십시오. method_missing(:sym)이 작동하면 respond_to?(:sym)은 항상 true를 반환해야합니다. 이 라이브러리에는 많은 라이브러리가 있습니다.

나중에

:

예 : 당신이 방법 이름을 예상 할 수 있다면, 동적 method_missing는 성능 저하를 초래하기 때문에 method_missing에 의존하는 것보다 그들에게 선언하는 것이 좋습니다

# Wrap a Foo; don't expose the internal guts. 
# Pass any method that starts with 'a' on to the 
# Foo. 
class FooWrapper 
    def initialize(foo) 
    @foo = foo 
    end 
    def some_method_that_doesnt_start_with_a 
    'bar' 
    end 
    def a_method_that_does_start_with_a 
    'baz' 
    end 
    def respond_to?(sym, include_private = false) 
    pass_sym_to_foo?(sym) || super(sym, include_private) 
    end 
    def method_missing(sym, *args, &block) 
    return foo.call(sym, *args, &block) if pass_sym_to_foo?(sym) 
    super(sym, *args, &block) 
    end 
    private 
    def pass_sym_to_foo?(sym) 
    sym.to_s =~ /^a/ && @foo.respond_to?(sym) 
    end 
end 

class Foo 
    def argh 
    'argh' 
    end 
    def blech 
    'blech' 
    end 
end 

w = FooWrapper.new(Foo.new) 

w.respond_to?(:some_method_that_doesnt_start_with_a) 
# => true 
w.some_method_that_doesnt_start_with_a 
# => 'bar' 

w.respond_to?(:a_method_that_does_start_with_a) 
# => true 
w.a_method_that_does_start_with_a 
# => 'baz' 

w.respond_to?(:argh) 
# => true 
w.argh 
# => 'argh' 

w.respond_to?(:blech) 
# => false 
w.blech 
# NoMethodError 

w.respond_to?(:glem!) 
# => false 
w.glem! 
# NoMethodError 

w.respond_to?(:apples?) 
w.apples? 
# NoMethodError 
+0

재미 있습니다."정상적인"메서드와 "동적 인"메서드 (method_missing을 통해 구현)로 구성된 클래스에 대해 어떻게 구현하겠습니까? –

+0

@Christoph : 당신의'pass_sym_to_foo? '메쏘드는이 요청을 처리하려고 시도하는지'super'의'method_missing'에게 넘겨 줄지 결정하는 일반적인'handle'메쏘드가됩니다. –

+16

Ruby 1.9.2에서'respond_to_missing? '을 재정의하는 것이 더 좋습니다. 내 블로그 게시물을 참조하십시오. http://blog.marc-andre.ca/2010/11/methodmissing-politely.html –

9

. 예를 들어,이 구문을 사용하여 데이터베이스 뷰에 액세스 할 수 있도록 데이터베이스 핸들을 확장하고 싶어한다고 가정

selected_view_rows = @dbh.viewname(:column => value, ...) 

대신의 이름으로 데이터베이스에 메소드 이름을 데이터베이스 핸들에 method_missing에 의존하고 파견 이상 뷰를 사용하면 데이터베이스의 모든 뷰를 미리 확인한 다음 반복하여 @dbh에 "viewname"메서드를 만들 수 있습니다.

5

Pistos's point에 건물 : method_missing은 적어도 내가 시도한 모든 Ruby 구현을 호출하는 일반 메소드보다 훨씬 느립니다. 그는 method_missing로 전화하는 것을 피할 수있을 때를 예견하는 것이 옳습니다.

모험을 느끼는 경우, Ruby의 잘 알려지지 않은 Delegator 클래스를 확인하십시오.

11

메소드 누락 메소드가 특정 메소드 이름만을 찾고있는 경우, 찾고있는 것을 찾지 못하면 super를 호출하여 다른 메소드 누락이 해당 메소드를 수행 할 수 있도록하십시오.

+1

예 - 그렇지 않으면 메서드 호출이 자동으로 실패하고 오류가 없는데도 메서드가 작동하지 않는 이유를 파악하는 데 몇 시간을 소비하게됩니다. (그런 일은하지 않았을 것입니다) – PhillipKregg

0

또 다른 잡았다 :

method_missing 다르게 사이 obj.call_methodobj.send(:call_method) 동작합니다. 본질적으로 이전의 메소드는 모든 비공개 및 비 정의 메소드를 놓치고 나중에는 개인 메소드를 놓치지 않습니다.

따라서 send을 통해 개인 방법을 호출 할 때 method_missing은 전화를 걸러 내지 않습니다.

0

제임스의 대답은 현대 루비에, 중대하다하지만 (1.9+), 마크 - 앙드레는 말처럼, 당신은 방법 자체를 반환 method(:method_name)처럼, 당신은 respond_to? 위에 다른 방법에 액세스 할 수 있기 때문에 respond_to_missing?를 재정의 할 .

예, 정의 된 다음 클래스 : method_missing를 오버라이드 (override) 할 때

irb(main):015:0> u = UserWrapper.new 
=> #<UserWrapper:0x00007fac7b0d3c28 @json_user={:first_name=>"Jean", :last_name=>"Dupont"}> 
irb(main):016:0> u.first_name 
=> "Jean" 
irb(main):017:0> u.respond_to?(:first_name) 
=> true 
irb(main):018:0> u.method(:first_name) 
=> #<Method: UserWrapper#first_name> 
irb(main):019:0> u.foo 
NoMethodError (undefined method `foo' for #<UserWrapper:0x00007fac7b0d3c28>) 

그래서, 항상 respond_to_missing?을 정의의

class UserWrapper 
    def initialize 
    @json_user = { first_name: 'Jean', last_name: 'Dupont' } 
    end 

    def method_missing(sym, *args, &block) 
    return @json_user[sym] if @json_user.keys.include?(sym) 
    super 
    end 

    def respond_to_missing?(sym, include_private = false) 
    @json_user.keys.include?(sym) || super 
    end 
end 

결과.