`

关于method_missing 和respond_to?的基本用法

阅读更多
method_missing is a well-known tool in the Ruby metaprogramming toolbox. It’s callback method you can implement that gets called when a object tries to call a method that’s, well, missing. A well known example of this is ActiveRecord dynamic finders. For example, if your User has an email attribute, you can User.find_by_email('joe@example.com') even though User nor ActiveRecord::Base have defined it.

method_missing’s cousin, respond_to?, is often forgotten though. respond_to? is used to determine if an object responds to a method. It is often used to check that an object knows about a method before actually calling it, in order to avoid an error at runtime about the method existing.

To have a consistent API when using method_missing, it’s important to implement a corresponding respond_to?.
Example

Imagine we have a Legislator class. We want a dynamic finder that will translate find_by_first_name('John') to find(:first_name => 'John'). Here’s a first pass:
class Legislator
  # Pretend this is a real implementation
  def find(conditions = {})
  end
  
  # Define on self, since it's  a class method
  def self.method_missing(method_sym, *arguments, &block)
    # the first argument is a Symbol, so you need to_s it if you want to pattern match
    if method_sym.to_s =~ /^find_by_(.*)$/
      find($1.to_sym => arguments.first)
    else
      super
    end
  end
end


That seems to do the trick. But Legislator.respond_to?(:find_by_first_name) would return false. Let’s add respond_to? to fix it.

class Legislator
  # ommitted
  
  # It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods
  # http://www.ruby-doc.org/core/classes/Object.html#M000333
  def self.respond_to?(method_sym, include_private = false)
    if method_sym.to_s =~ /^find_by_(.*)$/
      true
    else
      super
    end
  end
end


As commented, respond_to? takes two parameters. If you don’t do this, there’s a chance some library may expect to be able to invoke respond_to? with 2 parameters, resulting in an ArgumentError. In particular, the RSpec mocking library does this exactly this, and caused me a bit of befuddlement until I turned on full backtraces.
Possible refactorings
DRY

You might notice it’s not very DRY. We use the same pattern matching twice. This particular logic is simple, but it could be grow to be more complicated.

We can again look to ActiveRecord for inspiration. It encapsulates the logic in ActiveRecord::DynamicFinderMatch, to avoid repetition between method_missing and respond_to?, in addition to simplifying the methods.

class LegislatorDynamicFinderMatch
  attr_accessor :attribute
  def initialize(method_sym)
    if method_sym.to_s =~ /^find_by_(.*)$/
      @attribute = $1.to_sym
    end
  end
  
  def match?
    @attribute != nil
  end
end

class Legislator
  def self.method_missing(method_sym, *arguments, &block)
    match = LegislatorDynamicFinderMatch.new(method_sym)
    if match.match?
      find(match.attribute => arguments.first)
    else
      super
    end
  end

  def self.respond_to?(method_sym, include_private = false)
    if LegislatorDynamicFinderMatch.new(method_sym).match?
      true
    else
      super
    end
  end
end


Caching method_missing

Always passing through method_missing can be slow.

Another lesson we can take from is ActiveRecord is defining the method during method_missing, and then send to the now-defined method.

class Legislator    
  def self.method_missing(method_sym, *arguments, &block)
    match = LegislatorDynamicFinderMatch.new(method_sym)
    if match.match?
      define_dynamic_finder(method_sym, match.attribute)
      send(method_sym, arguments.first)
    else
      super
    end
  end
  
  protected
  
  def self.define_dynamic_finder(finder, attribute)
    class_eval <<-RUBY
      def self.#{finder}(#{attribute})        # def self.find_by_first_name(first_name)
        find(:#{attribute} => #{attribute})   #   find(:first_name => first_name)
      end                                     # end
    RUBY
  end
end


Testing

Being the test-minded individual that I am, no code would be complete without some testing.

Creating the LegislatorDynamicFinderMatch makes it straight forward to test your matching logic. An example using RSpec could look like:
describe LegislatorDynamicFinderMatch do
  describe 'find_by_first_name' do
    before do
      @match = LegislatorDynamicFinderMatch.new(:find_by_first_name)
    end
      
    it 'should have attribute :first_name' do
      @match.attribute.should == :first_name
    end
    
    it 'should be a match' do
      @match.should be_a_match
    end
  end
  
  describe 'zomg' do
    before do
      @match = LegislatorDynamicFinderMatch(:zomg)
    end
    
    it 'should have nil attribute' do
      @match.attribute.should be_nil
    end
    
    it 'should not be a match' do
      @match.should_not be_a_match
    end
  end
end


If you are creating dynamic methods which are just syntactic sugar around another method, like our finder, I think it is sufficient to use mocking to ensure the main method gets called with the correct arguments. Here’s an RSpec example:

describe Legislator, 'dynamic find_by_first_name' do
  it 'should call find(:first_name => first_name)' do
    Legislator.should_receive(:find).with(:first_name => 'John')
   
    Legislator.find_by_first_name('John')
  end
end

Summary

If you’re going to use method_missing, be sure to implement respond_to? in kind.

You can improve the DRYness of these methods by encapsulating the matching logic in a separate class, and you can improve performance by defining methods when they are missing.
分享到:
评论

相关推荐

    Ruby元编程之梦中情人method_missing方法详解

    总的来说,理解何时和如何使用`method_missing`和`define_method`是掌握Ruby元编程的关键。`method_missing`提供了一种强大的灵活性,但其代价是性能和可读性,而`define_method`则在保持代码整洁的同时提供了动态...

    ruby元编程之method_missing的一个使用细节

    ### Ruby元编程之method_missing的一个使用细节 #### 引言 在Ruby语言中,元编程是一种非常强大的功能,它允许程序在运行时修改自身的行为。其中`method_missing`方法是元编程的重要组成部分之一,用于处理未知的...

    慧表(NxCells)注册算法及method_2~method_6函数.rar

    总的来说,慧表(NxCells)的注册算法和method_2至method_6函数构成了一套完整的授权系统,保护了软件的知识产权并确保合法用户能正常使用其功能。了解这些函数的工作原理对于软件开发者来说,无论是为了自定义注册...

    method_not_missing:因为露比

    gem install method_not_missing 外部依赖 PhantomJS(在OSX上brew install phantomjs ) 例子 require "method_not_missing" object = MethodNotMissing :: OmnipotentObject . new object . update ( [ 3 ] ) ## ...

    ruby元编程之创建自己的动态方法

    本文将深入探讨如何利用`method_missing`和`respond_to?`来创建动态方法,这是一种在Ruby社区中广泛使用的技巧,尤其是在诸如Rails这样的框架中。 #### 二、`method_missing`详解 `method_missing`是Ruby中的一个...

    trussx.rar_The Method Method_truss

    《二维桁架问题的有限元方法解法——以"trussx.rar_The Method Method_truss"为例》 在工程领域,尤其是结构力学分析中,二维桁架问题的求解是一项基础而重要的任务。"trussx.rar_The Method Method_truss"是一个...

    split_step_method.zip_NLSE_The Method Method_split_split-step

    标题 "split_step_method.zip_NLSE_The_Method_...总之,"split_step_method.zip" 文件包提供的内容涵盖了使用MATLAB数值求解非线性薛定谔方程的核心概念和编程实践,对于理解和掌握这种重要数值方法具有重要意义。

    HW8.rar_The Method Method_dsp

    描述中明确提到“the overlap-Add method- DSP”,表明我们要讨论的是在数字信号处理领域中广泛使用的重叠添加方法。 重叠添加法是将长信号分解成较短的重叠段,并对每一段进行快速傅里叶变换(FFT),然后将这些...

    PSO2.rar_About Method_s method_swarm

    标题中的"PSO2.rar_About Method_s method_swarm"提到了“粒子群优化法”(Particle Swarm Optimization, PSO),这是一种基于群体智能的优化算法,由James Kennedy和Russell E. Eberhart在1995年提出。该算法模拟了...

    The_Finite_Volume_Method_in_Computational_Fluid_Dynamics2016

    《有限体积方法在计算流体动力学中...综上所述,《有限体积方法在计算流体动力学中的应用》这本书可能涵盖了从基本概念到高级应用的全面内容,旨在帮助读者理解和掌握这一强大的数值方法,以解决实际的流体动力学问题。

    minJT.zip_The Method Method_matlab_minJT_optimal_design

    标题中的"minJT.zip_The Method Method_matlab_minJT_optimal_design"暗示了这是一个关于使用MATLAB进行优化设计的项目,特别关注寻找最大值或最小值的问题。"The Method Method"可能指的是某种特定的数值优化算法,...

    cg.rar_The Method Method_adjoint_adjoint method_cg matlab

    《使用伴随方法求解线性方程组的MATLAB实现》 在计算机科学与工程领域,尤其是数值计算中,解决线性方程组是至关重要的任务。本文将深入探讨一种高效的方法——伴随方法(Adjoint Method),并介绍如何在MATLAB环境...

    method_of_moments.rar_method_of_moments_moment_电磁场矩量法_电磁积分方程

    在标题中提到的“method_of_moments.rar”,这可能是一个包含关于矩量法详细资料的压缩文件,包括理论介绍、算法实现和可能的实例应用。这个压缩包可能用于教学、研究或者工程实践,帮助用户理解和掌握矩量法的核心...

    CC_method.zip_CC_CC参数_cc method_cc-method_相空间重构

    "CC_method.zip"这个压缩包文件包含了与"CC方法"(可能是“Correlation Coefficient”或“Concurrent Causality”的简称)相关的资源,特别是关于"CC参数"的设置和应用,以及"cc-method",这可能是一个专门用于相...

    Python_implementation_of_deep_forest_method__gcFo_gcForest.zip

    Python_implementation_of_deep_forest_method__gcFo_gcForest

    Method_of_pull_testing_wire_conn

    美国专利Method_of_pull_testing_wire_conn

    Computational_method_Computational_Method_

    学习Matlab,首先要掌握其基本语法,包括变量定义、数据类型、控制结构(如循环和条件语句)以及函数的使用。此外,了解Matlab的图形界面和命令行界面对于数据可视化和调试代码也至关重要。 Chapter_2_Matlab则深入...

    有限元经典著作ciarlet-the_finite_element_method_for_elliptic_problems

    the_finite_element_method_for_elliptic_problems 有限元经典著作ciarlet

Global site tag (gtag.js) - Google Analytics