分享 Rails 技巧之 tap & try

diudiutang · 2012年09月04日 · 最后由 franklinyu 回复于 2019年03月06日 · 20892 次阅读

Rails 技巧之 tap & try

最近发现很多时候都要判断 nil , 用了很多三元表达式,写起来总是感觉很重复,想起来以前看过关于 tap 和 try 的相关技巧,论坛里一些高手肯定也经常使用这个技巧,无奈自己经验尚浅,到现在才开始使用,特地仔细学习了下,希望能给新手朋友一点帮助

tap

tap 和 try 是 Rails 开发过程中两个很常用的方法,在调试和写出简洁代码上有着不错的发挥

从 tap 的 Api 可以看出,tap 是 Object 的 instance_method,传递 self 给一个 block,最后返回 self.

1.9.3p194 :015 > Object.instance_methods.grep /tap/
 => [:tap]

def tap     #tap 源码实现
  yield self
  self
end

用途一:调试。当你使用链式方法发生错误时,如果需要测试这个过程中哪出了问题,一般的做法是拆断这个方法,设置中间变量,接着测试中间变量是否正确,如果正确,则把变量换个地方继续测试,方法很长时就没法测了,当然 Ruby 为你想好了,有着优雅又简便的实现

%w(x y z).push('a').shift.upcase.next # => "Y"

%w(x y z).push('a').shift.tap {|x| p x }.upcase.next

输出每步的结果看看

(1..10)                .tap {|x| puts "original: #{x.inspect}"}
  .to_a                .tap {|x| puts "array: #{x.inspect}"}
  .select {|x| x%2==0} .tap {|x| puts "evens: #{x.inspect}"}
  .map { |x| x*x }     .tap {|x| puts "squares: #{x.inspect}"}

用途二:简化代码。我们构建一个方法想返回一个 String / Array / Hash 之类结果,一般的做法是先定义一个变量,结果把运算结果赋值给这个变量,接着返回变量,用 tap 一步搞定,其实就是源码意思的实现

[].tap {|i| i << "abc"}
''.tap {|i| i << do_some_thing }
try

try 可以让你调用对象方法时不用担心对象是 nil 并抛出异常,依旧先看源码 (rails 3.2.8)

def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

def try(*args)
  nil
end

上面一个 try 是对 Object 的扩展,下面的是对 NilClass 的扩展,这就是为什么会 nil 不会抛出异常的原因,源码 .
try 如果只接受 block 则传递 self 给 block,返回 block 执行后的结果,否则就执行 public_send,public_send 与 send 的不同之处,public_send 只会 call public_method,看例子

@person && @persion.name
@person ? @person.name : nil

用 try 改写

@person.try(:name)
@person.try { |p| "#{p.first_name} #{p.last_name}" }

try 同样能接受参数、block,

Person.try(:find, 1)
@people.try(:collect) {|p| p.name}

通过源码可以看出 tap 和 try 两个小工具意思都非常明白,用起来也很简单方便,实乃居家简化好帮手

匿名 #2 2012年09月04日

经常要判断 对象是否是 nil 用 try 就简单多了

依旧先看源码(rails 3.2.8)

你引用的是 Rails4.0 的源码。。3.8 的在这:https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/object/try.rb#L32

受教了。

#3 楼 @hooopo o,没有注意版本 #4 楼 @lgn21st 我是看着你的视频长大的。。这点小技巧你们不早就驾轻就熟啦

#5 楼 @diudiutang 这还真不一定,ruby 社区有数量繁多的漂亮的 api,即使使用多年也未必全都熟悉,大家都要不断学习,所以如果看到什么好的,别管是否 old,拿出来 show 就是

Rails 3.2 开始的 try 是不会判断 respond_to? 的,对于非 nil 对象是可能抛出NoMethodError

这个方法好,之前我都是用 alias_method 实现的让 list 字段不为 nil

def do_validate(name) ret = send("#{name}_without_validate".to_sym)

if ret.nil? logger.debug(“#{name} is nil”) ret = [] end

ret end

def self.validate(method) method_without = "#{method}_without_validate".to_sym method_with = "#{method}_with_validate".to_sym

define_method(method_with) do |*args, &block| do_validate(method) end

alias_method method_without, method alias_method method, method_with end

受教了。确实对于写出简洁的代码有很大的好处 :)

学习了,越来越欣赏 Ruby 的艺术性了

嗯,好东西!

学习了

tar = [].tap {|i| (1..3).to_a.each{|e| i << e}}

感谢楼主分享

谢谢分享

diudiutang 回复

哈哈,想不到大神都是从小鸟做起的,这很励志,2012-2017

感谢分享

输出每步的结果看看

(1..10)                .tap {|x| puts "original: #{x.inspect}"}
  .to_a                .tap {|x| puts "array: #{x.inspect}"}
  .select {|x| x%2==0} .tap {|x| puts "evens: #{x.inspect}"}
  .map { |x| x*x }     .tap {|x| puts "squares: #{x.inspect}"}

↑↑↑↑↑↑ 这里似乎每行得加个\才行吧:

输出每步的结果看看

(1..10)                .tap {|x| puts "original: #{x.inspect}"}\
  .to_a                .tap {|x| puts "array: #{x.inspect}"}\
  .select {|x| x%2==0} .tap {|x| puts "evens: #{x.inspect}"}\
  .map { |x| x*x }     .tap {|x| puts "squares: #{x.inspect}"}
hexawing 回复

不需要。第一個字符是 . 的,自動接續上一行。我見過的腳本語言裏面只有 Ruby 可以這樣。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号