Rim*_*ian 4 ruby lazy-evaluation method-chaining
我想构建一个具有类似rails活动记录的接口的API客户端.我希望消费者能够链接方法,并且在最后一个方法被链接之后,客户端基于所调用的方法请求URL.所以它的方法是链接一些懒惰的评估.我查看了Active Record,但这非常复杂(产生过程等).
这是我正在谈论的那种事情的玩具示例.你可以在调用'get'之前将任意数量的'bar'方法链接在一起,如下所示:
puts Foo.bar.bar.get # => 'bar,bar'
puts Foo.bar.bar.bar.get # => 'bar,bar,bar'
Run Code Online (Sandbox Code Playgroud)
我已经成功实现了这个,但我宁愿不需要调用'get'方法.所以我想要的是:
puts Foo.bar.bar # => 'bar,bar'
Run Code Online (Sandbox Code Playgroud)
但我目前的实现是这样做的:
puts Foo.bar.bar #=> [:bar, :bar]
Run Code Online (Sandbox Code Playgroud)
我曾想过重写数组方法each,to_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
Run Code Online (Sandbox Code Playgroud)
另请参阅:Ruby挑战 - 方法链和懒惰评估
它与activerecord的工作原理是,该关系是数组的包装器,将任何未定义的方法委托给这个内部数组(被调用target).所以你需要的是从BasicObject而不是Object开始:
class Foo < BasicObject
Run Code Online (Sandbox Code Playgroud)
那么你需要创建内部变量,你将委托所有方法:
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
Run Code Online (Sandbox Code Playgroud)
要链接方法,您的方法需要返回类的新实例,例如:
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
Run Code Online (Sandbox Code Playgroud)
现在您可以检查它的实际效果:
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
Run Code Online (Sandbox Code Playgroud)