如何使用Ruby创建类似".find_by_something_and_something_else"的方法?

mar*_*cgg 2 ruby metaprogramming

使用Ruby我知道你可以通过命名方法获得相当的创意.例如,你有铁轨.find_by_this_and_that.

我怎样才能做到这一点?

例:

def get_persons_with_5_things
  res = []
  persons.each do |person|
    if person.number_of_things == %MAGICALLY GET THE NUMBER 5 FROM FUNCTION NAME%
      res << person
    end
  end
  return res
end
Run Code Online (Sandbox Code Playgroud)

我甚至不确定你怎么称呼这类东西所以任何指针都会受到赞赏.

ott*_*bar 10

你的例子我有点困惑.如果在方法名称中使用硬编码5定义方法,则不需要在方法体内进行神奇地计算.如果你想用方法缺失做一些动态的东西,它将是这样的:

def method_missing(name, *args)
  if name.to_s =~ /get_persons_with_(\d+)_things/
    number_of_things = $1.to_i
    res = []
    persons.each do |person|
      if person.number_of_things == number_of_things
        res << person
      end
    end
    return res
  else
    return super(name, *args)
  end
end
Run Code Online (Sandbox Code Playgroud)

[编辑(JörgWMittag)]:这是一种更为Rubyish的方法来实现相同的方法:

def method_missing(name, *args)
  return super unless name.to_s =~ /get_persons_with_(\d+)_things/
  number_of_things = $1.to_i
  return persons.select {|person| person.number_of_things == number_of_things }
end
Run Code Online (Sandbox Code Playgroud)
  • super 没有任何参数只传递原始参数,不需要显式传递它们
  • return由尾随ifunless表达保护的早期大大清除了控制流程
  • 所有each迭代器都是根据谓词选择项目; 但是,已经有一个用于选择项目的迭代器:select

  • +1为超级.我讨厌调试只吞下方法的代码. (2认同)

joh*_*nes 5

Ruby有不同的元编程技术来做这种事情.

首先,我们需要变量方法

class DB
  def get_persons_with_x_things(x)
    res = []
    persons.each do |person|
      if person.number_of_things == x
        res << person
      end
    end
    return res
  end
end
Run Code Online (Sandbox Code Playgroud)

define_method

如果有一定数量的x.我们可以define_method用来创建所有这些方法.define_method创建一个方法.第一个参数是方法的名称,seccond参数或给定的块是东西,它在调用方法时执行.

这样,你不会真正创建这样的方法,但如果用户调用它,它会查找用户,就像它存在一样.但是如果用户依赖Object#methods等等,他将永远不会看到你无数的假方法.

class DB
  99.times do |i|
    define_method("get_persons_with_#{i}_things") do
      get_persons_with_x_things(i)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

method_missing的

如果有一个无限的x's numbor method_missing将更适合这个任务.如果有人试图调用不存在的方法,method_missing则执行.第一个参数method_missing是方法名称作为符号,以下参数是原始参数.

class DB
  def method_missing(name, *args)
    case name.to_s
    when /^get_persons_with_(\d+)_things$/
      get_persons_with_x_things($1.to_i)
    else
      super(name, *args)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

method_missing和发送

不使用静态regexe会更酷.但这可能会带来一些安全隐患.该方法send我在这里使用,调用由它的名称的方法.

class DB
  def method_missing(name, *args)
    name.to_s=~ /\d+/
# always be carefull with $ variables, they are global for this thread, so save everything as fast as you can
    new_name= "#{$`}x#{$'}"
    number= $1.to_i
    if method_defined?(new_name)
      send(new_name, number)
    else
      super(name, *args)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)