ruby Object#tap and Object#returning

Posted by wxianfeng Wed, 19 Jan 2011 03:29:00 GMT

今天在rails中使用 returning 的时候 log 打出warning:

Object#returning has been deprecated in favor of Object#tap

环境是 ruby 1.9.2 + rails 3.0.3

从warn上看是returning不建议使用,建议使用tap方法,那么tap方法和returning方法有什么不同

Object#tap 方法是 ruby1.8.7 以后加入的,Object#returning 方法是 rails添加的

rails 3.0.3 returning 源码:here

rails 2.3.5 returning源码:

class Object
  # Returns +value+ after yielding +value+ to the block. This simplifies the
  # process of constructing an object, performing work on the object, and then
  # returning the object from a method. It is a Ruby-ized realization of the K
  # combinator, courtesy of Mikael Brockman.
  #
  # ==== Examples
  #
  #  # Without returning
  #  def foo
  #    values = []
  #    values << "bar"
  #    values << "baz"
  #    return values
  #  end
  #
  #  foo # => ['bar', 'baz']
  #
  #  # returning with a local variable
  #  def foo
  #    returning values = [] do
  #      values << 'bar'
  #      values << 'baz'
  #    end
  #  end
  #
  #  foo # => ['bar', 'baz']
  #  
  #  # returning with a block argument
  #  def foo
  #    returning [] do |values|
  #      values << 'bar'
  #      values << 'baz'
  #    end
  #  end
  #  
  #  foo # => ['bar', 'baz']
  def returning(value)
    yield(value)
    value
  end

  # Yields <code>x</code> to the block, and then returns <code>x</code>.
  # The primary purpose of this method is to "tap into" a method chain,
  # in order to perform operations on intermediate results within the chain.
  #
  #   (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}" }
  def tap
    yield self
    self
  end unless Object.respond_to?(:tap)

  # An elegant way to factor duplication out of options passed to a series of
  # method calls. Each method called in the block, with the block variable as
  # the receiver, will have its options merged with the default +options+ hash
  # provided. Each method called on the block variable must take an options
  # hash as its final argument.
  # 
  #   with_options :order => 'created_at', :class_name => 'Comment' do |post|
  #     post.has_many :comments, :conditions => ['approved = ?', true], :dependent => :delete_all
  #     post.has_many :unapproved_comments, :conditions => ['approved = ?', false]
  #     post.has_many :all_comments
  #   end
  #
  # Can also be used with an explicit receiver:
  #
  #   map.with_options :controller => "people" do |people|
  #     people.connect "/people",     :action => "index"
  #     people.connect "/people/:id", :action => "show"
  #   end
  #
  def with_options(options)
    yield ActiveSupport::OptionMerger.new(self, options)
  end
  
  # A duck-type assistant method. For example, Active Support extends Date
  # to define an acts_like_date? method, and extends Time to define
  # acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
  # "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
  # we want to act like Time simply need to define an acts_like_time? method.
  def acts_like?(duck)
    respond_to? "acts_like_#{duck}?"
  end

end

可以看到 tap 方法也封装了,为了防止 ruby版本过低 没有tap方法就 添加Object#tap 方法,tap 和 returning本质是一样的,函数体都是调用block闭包,只不过returning需要传递一个参数给闭包,最后返回的就是这个参数,而 tap直接操作self指针,最后返回的也就是self

另外最新rails源码(>rails3.0.3)已经没有 returning方法了,所以以后最好都用tap方法

DEMO1:

require "rubygems"
require "active_support"
# Object#tap 是>ruby1.8.7 有的
# Object#returning 是Rails 封装的方法, rails3.X 已经不建议使用

# Object#tap 可以支持链式(chain)操作
(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}"
}

def object_tap
  {}.tap do |h| # => Hash.new.tap
    h[:a] = 1
    h[:b] =2
  end
end

p object_tap # {:b=>2, :a=>1}

def object_returning
  returning Hash.new do |h| # 注意这里不能用 {}  , 放在 returning 方法后面 当作成 block闭包了
    h[:a] = 1
    h[:b] = 2
  end  
end

p object_returning # {:b=>2, :a=>1}

DEMO2:

require "rubygems"
require "active_support"

class Hash  
  def shift_value_tap_self
    self.tap do |h|
      h.each { |k,v| v.shift if v.is_a?(Array) }
    end
  end
  def shift_value_tap
    {}.tap do |h|
      self.each { |k,v|  v.is_a?(Array) ? h[k] = v.shift : h[k] = v } 
    end
  end

  def shift_value_returning
    returning Hash.new do |h|
      self.each { |k,v|  v.is_a?(Array) ? h[k] = v.shift : h[k] = v } 
    end
  end
  def shift_value_returning_self
    returning self do |h|
      h.each { |k,v| v.shift if v.is_a?(Array) }
    end
  end
end

hsh = {"a"=>[1,2,3],"b"=>["g","f","w"],"c"=>"fuck_china"}
hsh1 = {"a"=>[1,2,3],"b"=>["g","f","w"],"c"=>"fuck_china"}
hsh2 = {"a"=>[1,2,3],"b"=>["g","f","w"],"c"=>"fuck_china"}
hsh3 = {"a"=>[1,2,3],"b"=>["g","f","w"],"c"=>"fuck_china"}
p hsh.shift_value_tap # {"a"=>1, "b"=>"g", "c"=>"fuck_china"}
p hsh1.shift_value_returning # {"a"=>1, "b"=>"g", "c"=>"fuck_china"}
p hsh2.shift_value_tap_self # {"a"=>[2, 3], "b"=>["f", "w"], "c"=>"fuck_china"}
p hsh3.shift_value_returning_self # {"a"=>[2, 3], "b"=>["f", "w"], "c"=>"fuck_china"}

SEE:
http://blog.rubybestpractices.com/posts/gregory/011-tap-that-hash.html
http://www.simonecarletti.com/blog/2010/09/rails-3-beware-the-tap-pattern/
http://fuliang.javaeye.com/blog/857163


ruby1.9 char to ascii(int value)

Posted by wxianfeng Wed, 26 Aug 2009 20:20:00 GMT

环境 : ruby 1.9.2 + ruby 1.8.7

ruby 1.8 时代 char to ascill 一般这么做:

"a"[0]  # 97
?a # 97

但是 1.9 时代 该方法 不可以了,输出如下:

"a"[0] # a
?a # a

后来 我用了 一个 变态的 方法:

"a".bytes.to_a.first # 97

1.9 时代 应该使用ord 函数

"a".ord  # 97
"a"[0].ord # 97
?a.ord # 97

ascill to char 通用:

97.chr # "a"

但是 ord 函数 1.8 环境下 同样可以使用;

看下 ord 函数的源码(built in 方法,内部看不到ruby代码):

  #     int.ord    => int
  #
  #
  # Returns the int itself.
  #
  #    ?a.ord    #=> 97
  #
  # This method is intended for compatibility to
  # character constant in Ruby 1.9.
  # For example, ?a.ord returns 97 both in 1.8 and 1.9.
  #
  #
  def ord
    # This is just a stub for a builtin Ruby method.
    # See the top of this file for more info.
  end

see:
http://stackoverflow.com/questions/1270209/getting-an-ascii-character-code-in-ruby-fails


rvm ruby 多版本控制

Posted by wxianfeng Mon, 22 Nov 2010 21:07:00 GMT

最终环境:ubuntu10.10 + ruby 1.8.7 + ruby 1.9.2 + rails 2.3.5 + rails 3.0.3

新的项目,使用最新的ruby1.9.2 + rails3.X开发,所以 环境必须重新搭建 ,但是又要能使之前的rails2.X项目跑起来,所以,就用到了rvm来控制,rvm确实不错,一直用部署的capistrano差不多,很方便,rvm的原理是动态的改变系统的path,即ruby的bin目录,来调用不同的ruby版本的,都有自己独立的文件夹,这样管理rails的那些gem也就独立开来了,核心是一个script,有兴趣的可以看下,$HOME/.rvm/scripts/rvm , 这个shell脚本,下面介绍安装过程:

需要用到 git 和 curl

sudo apt-get install git
sudo apt-get install curl

注意首先把原来的ruby bin目录从PATH中去掉

安装 rvm

cd ~
bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )
cd .rvm/src/rvm
./install

查看 rvm 安装目录

wxianfeng@ubuntu:~/.rvm/gemsets/ruby$ echo $rvm_path
/home/wxianfeng/.rvm

~/.bashrc中添加下面代码(PATH之用) 很重要

if [[ -s "$HOME/.rvm/scripts/rvm" ]]  ; then source "$HOME/.rvm/scripts/rvm" ; fi  

安装 ruby 1.9.2

>rvm notes
sudo apt-get install build-essential bison openssl libreadline5 libreadline5-dev curl git zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev autoconf
rvm install 1.9.2

查看:

wxianfeng@ubuntu:~$ rvm list
rvm rubies
   ruby-1.9.2-p0 [ i386 ]

设置默认的 ruby 版本(必须设置)

>rvm --default ruby-1.9.2-p0

可以看到 相关信息

wxianfeng@ubuntu:~$ rvm info

ruby-1.9.2-p0:

  system:
    uname:       "Linux ubuntu 2.6.35-22-generic #33-Ubuntu SMP Sun Sep 19 20:34:50 UTC 2010 i686 GNU/Linux"
    bash:        "/bin/bash => GNU bash, version 4.1.5(1)-release (i686-pc-linux-gnu)"
    zsh:         " => not installed"

  rvm:
    version:      "rvm 1.1.0 by Wayne E. Seguin (wayneeseguin@gmail.com) [http://rvm.beginrescueend.com/]"

  ruby:
    interpreter:  "ruby"
    version:      "1.9.2p0"
    date:         "2010-08-18"
    platform:     "i686-linux"
    patchlevel:   "2010-08-18 revision 29036"
    full_version: "ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]"

  homes:
    gem:          "/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0"
    ruby:         "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0"

  binaries:
    ruby:         "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/ruby"
    irb:          "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/irb"
    gem:          "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/gem"
    rake:         "/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0/bin/rake"

  environment:
    PATH:         "/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0/bin:/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0@global/bin:/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin:/home/wxianfeng/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/system/mysql/bin:/usr/local/system/jdk1.6.0_22/bin"
    GEM_HOME:     "/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0"
    GEM_PATH:     "/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0:/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0@global"
    MY_RUBY_HOME: "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0"
    IRBRC:        "/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/.irbrc"
    RUBYOPT:      ""
    gemset:       ""

这时发现你的PATH已经变了

wxianfeng@ubuntu:~$ echo $PATH
/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0/bin:/home/wxianfeng/.rvm/gems/ruby-1.9.2-p0@global/bin:/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin:/home/wxianfeng/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/system/mysql/bin:/usr/local/system/jdk1.6.0_22/bin
wxianfeng@ubuntu:~$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]

ruby 1.9.2 安装成功

安装rails

wxianfeng@ubuntu:~$ gem install rails --pre --no-ri --no-rdoc
/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/gem:4: warning: Insecure world writable dir /usr/local/system in PATH, mode 040777
Successfully installed activesupport-3.0.0.rc2
Successfully installed builder-2.1.2
Successfully installed i18n-0.4.2
Successfully installed activemodel-3.0.0.rc2
Successfully installed rack-1.2.1
Successfully installed rack-test-0.5.6
Successfully installed rack-mount-0.6.13
Successfully installed tzinfo-0.3.23
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.6
Successfully installed actionpack-3.0.0.rc2
Successfully installed arel-1.0.0.rc1
Successfully installed activerecord-3.0.0.rc2
Successfully installed activeresource-3.0.0.rc2
Successfully installed mime-types-1.16
Successfully installed polyglot-0.3.1
Successfully installed treetop-1.4.9
Successfully installed mail-2.2.10
Successfully installed actionmailer-3.0.0.rc2
Successfully installed thor-0.14.6
Successfully installed railties-3.0.0.rc2
Successfully installed bundler-1.0.0.rc.6
Successfully installed rails-3.0.0.rc2
23 gems installed

有提示我path中有777权限目录,看着不顺眼,解决掉:

sudo chmod go-w /usr/local/system

发现安装上的是 3.0.0.rc2 不是我想要的,目前最新版是 3.0.3

咋办,用shell脚本全部remove掉

shell:

#!/bin/bash 

for i in `gem list`
do
        gem uninstall $i
done

最后发现rake无法uninstall掉,好那就进入目录把rake delete掉

wxianfeng@ubuntu:~$ gem list -d rake
/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/gem:4: warning: Insecure world writable dir /usr/local/system in PATH, mode 040777

*** LOCAL GEMS ***

rake (0.8.7)
    Author: Jim Weirich
    Rubyforge: http://rubyforge.org/projects/rake
    Homepage: http://rake.rubyforge.org
    Installed at: /home/wxianfeng/.rvm/gems/ruby-1.9.2-p0@global

    Ruby based make-like utility.


>cd /home/wxianfeng/.rvm/gems/ruby-1.9.2-p0@global

wxianfeng@ubuntu:~/.rvm/gems/ruby-1.9.2-p0@global$ ll
total 28
drwxr-xr-x 7 wxianfeng wxianfeng 4096 2010-11-23 14:14 ./
drwxr-xr-x 5 wxianfeng wxianfeng 4096 2010-11-23 14:14 ../
drwxr-xr-x 2 wxianfeng wxianfeng 4096 2010-11-23 15:21 bin/
drwxr-xr-x 2 wxianfeng wxianfeng 4096 2010-11-23 15:22 cache/
drwxr-xr-x 2 wxianfeng wxianfeng 4096 2010-11-23 15:22 doc/
drwxr-xr-x 2 wxianfeng wxianfeng 4096 2010-11-23 15:20 gems/
drwxr-xr-x 2 wxianfeng wxianfeng 4096 2010-11-23 15:21 specifications/

把bin cache doc gems specifications 下和rake相关的信息全部remove掉

重新安装成功 rails 3.0.3

wxianfeng@ubuntu:~$ gem install rails -v=3.0.3 --no-ri --no-rdoc
/home/wxianfeng/.rvm/rubies/ruby-1.9.2-p0/bin/gem:4: warning: Insecure world writable dir /usr/local/system in PATH, mode 040777
Successfully installed activesupport-3.0.3
Successfully installed builder-2.1.2
Successfully installed i18n-0.4.2
Successfully installed activemodel-3.0.3
Successfully installed rack-1.2.1
Successfully installed rack-test-0.5.6
Successfully installed rack-mount-0.6.13
Successfully installed tzinfo-0.3.23
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.6
Successfully installed actionpack-3.0.3
Successfully installed arel-2.0.4
Successfully installed activerecord-3.0.3
Successfully installed activeresource-3.0.3
Successfully installed mime-types-1.16
Successfully installed polyglot-0.3.1
Successfully installed treetop-1.4.9
Successfully installed mail-2.2.10
Successfully installed actionmailer-3.0.3
Successfully installed rake-0.8.7
Successfully installed thor-0.14.6
Successfully installed railties-3.0.3
Successfully installed bundler-1.0.7
Successfully installed rails-3.0.3
24 gems installed
wxianfeng@ubuntu:~$ rails -v
Rails 3.0.3

安装 ruby 1.8.7

>rvm install 1.8.7

切换到 1.8.7

wxianfeng@ubuntu:~/.rvm/gemsets/ruby$ rvm 1.8.7
wxianfeng@ubuntu:~/.rvm/gemsets/ruby$ gem list
/home/wxianfeng/.rvm/rubies/ruby-1.8.7-p302/bin/gem:4: warning: Insecure world writable dir /usr/local/system in PATH, mode 040777

*** LOCAL GEMS ***

rake (0.8.7)

悲剧的发现 其实 默认rake是安装上的,之前我还把它 给干掉 重新安装了

安装 rails 2.3.5

>gem install rails -v=2.3.5 --no-ri --no-rdoc

以后切换的话 就用 rvm —default 1.8.7 或者 rvm —default 1.9.2 切换默认的ruby版本,可以使用 rvm 1.8.7 切换当前终端版本到1.8.7 , 也就是可以同时开ruby 1.8.7 , ruby 1.9.2 的项目了

安装之前ruby 1.8.7 + rails 2.3.5 项目的 所有gems (注意把ruby版本切到1.8.7), 如果已经安装了,就不安装:

shell:

#!/bin/bash 
# 安装所有的gems,如果已经安装了就不安装

cd /usr/local/system/ruby/lib/ruby/gems/1.8/cache 


for i in `ls`;do
        gem=`echo $i | awk -F'-' '{print $1}'`
        version=`echo $i | grep -o "\-[0-9].*" | sed 's/^-//;s/.gem//'`
        is_gem_exist=`gem list $gem -v=$version`
        if [ -z "$is_gem_exist" ]; then # 注意[] 内部两边留空格
                `gem install $i`
        else
                echo "$i have installed"
        fi
done

这样,之前ruby 1.8.7+rails2.3.4的项目 和现在的 ruby 1.9.2 + rails 3.0.3 都兼容了…oh…YEAR

问题: netbeans 配置使用 ruby 1.8.7 + rails 2.3.5 OR ruby 1.9.2 + rails 3.0.3 ?
更改netbeans 的ruby path ,选择 autodetect 会把ruby的bin全部找出来,但是注意gem path 有可能错误,通过 rvm info 看下 gem 的位置,然后手动更改即可~!

SEE:

http://thoughtrails.com/episodes/1-installing-rails-3-on-ubuntu-10-04-lucid-lynx-with-rvm
http://rvm.beginrescueend.com/rvm/install/