用红宝石链接的方法

时间:2014-04-30 11:25:35

标签: ruby lazy-evaluation method-chaining

我想构建一个API客户端,其界面类似于rails active record。我希望消费者能够链接方法,并且在最后一个方法被链接之后,客户端基于所调用的方法请求URL。所以它的方法是链接一些懒惰的评估。我查看了Active Record,但这非常复杂(产生过程等)。

以下是我所谈论的那种玩具示例。你可以在调用'get'之前将任意数量的'bar'方法链接在一起,如下所示:

puts Foo.bar.bar.get # => 'bar,bar'
puts Foo.bar.bar.bar.get # => 'bar,bar,bar'

我已经成功实现了这个,但我宁愿不需要调用'get'方法。所以我想要的是:

puts Foo.bar.bar # => 'bar,bar' 

但是我目前的实现是这样做的:

puts Foo.bar.bar #=> [:bar, :bar]

我想过覆盖eachto_s之类的数组方法,但我相信有更好的解决方案。

我如何链接方法并知道哪个是最后一个,所以我可以返回类似get方法中返回的字符串的内容?

这是我目前的实施:

#!/usr/bin/env ruby

class Bar
  def get(args)
    # does a request to an API and returns things but this will do for now.
    args.join(',') 
  end
end

class Foo < Array
  def self.bar
    @q = new
    @q << :bar
    @q
  end

  def bar
    self << :bar
    self
  end

  def get
    Bar.new.get(self)
  end
end

另见:Ruby Challenge - Method chaining and Lazy Evaluation

2 个答案:

答案 0 :(得分:8)

它与activerecord的工作原理是该关系是数组的包装器,将任何未定义的方法委托给此内部数组(称为target)。所以你需要的是从BasicObject而不是Object开始:

class Foo < BasicObject

然后你需要创建内部变量,你将委托所有方法:

  def method_missing(*args, &block)
    reload! unless loaded?
    @target.send(*args, &block)
  end

  def reload!
    # your logic to populate target, e.g:
    @target = @counter
    @loaded = true
  end

  def loaded?
    !!@loaded
  end

要链接方法,您的方法需要返回类的新实例,例如:

def initialize(counter=0)
  @counter = counter
end

def bar
  _class.new(@counter + 1)
end

private

# BasicObject does not define class method. If you want to wrap your target 
# completely (like ActiveRecord does before rails 4), you want to delegate it 
# to @target as well. Still you need to access the instance class to create 
# new instances. That's the way (if there are any suggestion how to improve it,
# please comment!)
def _class
  (class << self; self end).superclass
end

现在您可以检查它:

p Foo.new.bar.bar.bar      #=> 3
(f = Foo.new) && nil       # '&& nil' added to prevent execution of inspect             
                           # object in the console , as it will force @target 
                           # to be loaded

f.loaded?                  #=> false
puts f                     #=> 0
f.loaded?                  #=> true

答案 1 :(得分:0)

(非常简单,可能是简单化)选项是实现to_s方法 - 因为它用于强制&#34;字符串(例如在看跌期权中),您可以拥有特定的&#34;这是链的末端&#34;代码那里。