开发者

How to create a method like ".find_by_something_and_something_else" using Ruby?

Using Ruby I know you can get pretty creative with how you name your methods. For instance in rails you have .find_by_this_and_that.

How can I do this?

Example:

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 << pers开发者_如何学Pythonon
    end
  end
  return res
end

I'm not even sure how you call this kind of things so any pointers would be appreciated.


I'm a little confused by your example. If you define the method with the hardcoded 5 in the method name, then you don't need to magically figure it out inside the body of the method. If you want to do something dynamic with method missing, it would be something like this:

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

[EDIT (Jörg W Mittag)]: This is a more Rubyish way of implementing that same method:

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
  • super without any arguments just passes the original arguments along, no need to pass them explicitly
  • an early return guarded by a trailing if or unless expression greatly clears up control flow
  • all the each iterator does, is select items according to a predicate; however, there already is an iterator for selecting items: select


Ruby has different meta programming techniches to do this kind of stuff.

First we need our variable method

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

define_method

If there is a finite number of x's. We could use define_method to create all this methods. define_method creates a method. The first argument is the name of the method, the seccond argument or the given block is the stuff, which get's executed when the method is called.

This way, you don't realy create such method's, but It will look for the user if he calls it, as if it existed. But if the user relies on Object#methods and such, he will never see your inifinite number of fake methods.

class DB
  99.times do |i|
    define_method("get_persons_with_#{i}_things") do
      get_persons_with_x_things(i)
    end
  end
end

method_missing

If there is an infinite numbor of x's method_missing would be better suited for this Task. If someone tries to call a method which does not exist, method_missing is executed instead. The first argument for method_missing is the method name as symbol, the following arguments are the original arguments.

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

method_missing and send

To not use static regexe would be even cooler. But this could have some security implications. The method send I use here, calls a method by it's name.

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


you can do a lot of things like this with method missing:

Ruby Docs

StackOveflow method_missing


Have a look at Ruby's callbacks specially method_missing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜