我想构建一个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]
我想过覆盖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
答案 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;代码那里。