Blog
Ruby && and tip
The following code comes from Puppet source (lib/puppet/type/cron.rb)
1 # Verify that a number is within the specified limits. Return the
2 # number if it is, or false if it is not.
3 def limitcheck(num, lower, upper)
4 (num >= lower and num <= upper) && num
5 end
Take a look at the comments, and the last statement (&&num). The operator && is not used as a condition check. It is to return the value if the previous checks are passed: If the num is out-of-range, the whole statement returns false. Otherwise, the last value num is returned. This is also how the operator && works :)
The code can be rewritten as
1 def limitcheck(num, lower, upper)
2 if (num >= lower and num <= upper)
3 return num
4 else
5 return false
6 end
7 end
But it is too long, isn't it?
STDOUT: what's wrong with these lines? (1 comment)
The purpose of this portion of code is to print a star every one second
1 while true do
2 STDOUT.write("*")
3 sleep(1)
4 done
Do you think that it really works? and why?
Ruby plus
Let see the differences
1 irb> 65. + (20).chr # some spaces before and after the operator
2 TypeError: String can't be coerced into Fixnum
3 from (irb):21:in `+'
4 from (irb):21
5 from /usr/bin/irb:12:in '<main>'
6
7 irb> 65.+(20).chr # no space before/after the operator
8 => "U"
Ruby is fun, isn't it ?
Array in Ruby is ordered
1 >> %w{abc xyz} == %w{xyz abc}
2 => false
true and foobar (1 comment)
and will return false, or the last object in the expression:
1 true and :foobar # => :foobar
2 :foobar and true # => true
3 :foo and :bar and :other # => :other
So, be careful when you're using and in the return value :)
are you.defined? (1 comment)
I was trying to use defined? as a function argument, and I found that I couldn't do that. That's weird. Let's see the first example
1 >> def foobar?; puts "this is a test"; end
2 nil
3 >> define?(foobar?)
4 "method"
5 >> s = method("foobar?")
6 #<Method: Object#foobar?>
7 >> s.clall
8 this is a test
That's clear. But I can't do a same thing with defined?
1 >> define?(defined?)
2 SyntaxError: compile error
3 (irb):23: syntax error, unexpected ')'
4 >> s = method("defined?")
5 NameError: undefined method `defined?' for class `Object'
6
Why? Oh, the answer is very simple: defined? isn't a method, it is an operator. I suddendly found the reason after reading this post http://kconrails.com/2010/12/20/rubys-defined-operator/. As an operator, it works like "+", "!", etc. The post also shows that we can't overwrite / redefine it :)
Strange! And fun!
ruby-1.8.7-head: chomp called for nil:NilClass
I am using ruby-1.8.7-head with helps of RVM . This is the first time I've used a head version of ruby. And the first try is often hard :P
$ ruby -e "require 'rubygems'; require 'net/ssh'"
/home/pi/.rvm/rubies/ruby-1.8.7-head/lib/ruby/1.8/logger.rb:174: private method `chomp' called for nil:NilClass (NoMethodError)
from /home/pi/.rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require'
from /home/pi/.rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
from /home/pi/.rvm/gems/ruby-1.8.7-head/gems/net-ssh-2.1.4/lib/net/ssh.rb:5
from /home/pi/.rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:58:in `gem_original_require'
from /home/pi/.rvm/rubies/ruby-1.8.7-head/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:58:in `require'
from -e:1
There problem comes from logger.rb. Let's take a look at the 174th line of the file:
1 class Logger
2 VERSION = "1.2.6"
3 id, name, rev = %w$Id$
4 ProgName = "#{name.chomp(",v")}/#{rev}"
In the official version (logger.rb from ruby-1.8.7 stable version):
1 class Logger
2 VERSION = "1.2.6"
3 id, name, rev = %w$Id: logger.rb 22285 2009-02-13 10:19:04Z shyouhei $
4 ProgName = "#{name.chomp(",v")}/#{rev}"
So the author used SVN ID string to set some properties of the class. In HEAD version, such information wasn't substituted, as RVM forked the source from a git repository, not a subversion repository. As git simply ignores $Id$, it makes the head version stupid :))
As a work-around, I've just forced a phantom value for id,name,rev :)
bundler: no such file to load -- set.rb
This is a story.
I am using Bundler 1.0.10
1 $ gem list |grep bundler
2 bundler (1.0.10)
Bundler is required to set up boxrom. Unfortunately, I couldn't get "Rakefile" to work. When loading the boxrom scripts, I got the error "no such file to load -- set.rb"
1 $ rake db:migrate --trace
2 (in /home/pi/projects/boxroom)
3 Cannot load psych in /home/ruby/gems/1.8/gems/rake-0.8.7/bin, /home/ruby/gems/1.8/gems/rake-0.8.7/lib,... .
4 Cannot load set.rb in /home/ruby/gems/1.8/gems/bundler-1.0.10/lib
5 Cannot load /home/pi/projects/boxroom/config/boot in /home/ruby/gems/1.8/gems/bundler-1.0.10/lib
6 Cannot load /home/pi/projects/boxroom/config/application in /home/ruby/gems/1.8/gems/bundler-1.0.10/lib
7 rake aborted!
8 no such file to load -- set.rb
The file set.rb is located in /opt/ruby1.8/lib/ruby/1.8/set.rb; it defines the Set class, and it can be loaded from irb or ruby:
1 $ ruby -e "require 'set'; puts Set.class"
2 Class
So what's problem that made the file unloadable by bundler? As you can see the logs, when bundler was trying to load 'set', the $LOAD_PATH was modified. This was really weird.
Cannot load psych in /home/ruby/gems/1.8/gems/rake-0.8.7/bin, /home/ruby/gems/1.8/gems/rake-0.8.7/lib,... . # full path Cannot load set.rb in /home/ruby/gems/1.8/gems/bundler-1.0.10/lib # the path was modified
As the $LOAD_PATH was modified, I thought that bundler would do something strange. In the file bundler-1.0.10/lib/bundler.rb I saw that
1 private
2
3 def configure_gem_home_and_path
4 if settings[:disable_shared_gems]
5 ENV['GEM_PATH'] = ''
6 ENV['GEM_HOME'] = File.expand_path(bundle_path, root)
7 elsif Gem.dir != bundle_path.to_s
8 paths = [Gem.dir, Gem.path].flatten.compact.uniq.reject{|p| p.empty? }
9 ENV["GEM_PATH"] = paths.join(File::PATH_SEPARATOR)
10 ENV["GEM_HOME"] = bundle_path.to_s
11 end
12
13 FileUtils.mkdir_p bundle_path.to_s
14 Gem.clear_paths
15 end
Uhm, it seems that I forgot to use the option disable_shared_gems. So I tried:
1 $ bundle install --disable-shared-gems
2 Cannot load psych in /home/ruby/gems/1.8/gems/bundler-1.0.10/bin, ....
3 Cannot load Win32API in /home/ruby/gems/1.8/gems/bundler-1.0.10/lib/bundler/vendor,... .
4 The disable-shared-gem option is no longer available.
Errr... So... the code lies. I updated the script
1 def configure_gem_home_and_path
2 #if settings[:disable_shared_gems]
3 # ENV['GEM_PATH'] = ''
4 # ENV['GEM_HOME'] = File.expand_path(bundle_path, root)
5 #if Gem.dir != bundle_path.to_s
6 paths = [Gem.dir, Gem.path].flatten.compact.uniq.reject{|p| p.empty? }
7 ENV["GEM_PATH"] = paths.join(File::PATH_SEPARATOR)
8 ENV["GEM_HOME"] = bundle_path.to_s
9 #end
10
11 FileUtils.mkdir_p bundle_path.to_s
12 Gem.clear_paths
13 end
and things are working very well now :)
PS: I modified the file "custom_required.rb" to get the messages "Cannot load..."
1 # File /opt/ruby1.8/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb
2
3 def require(path) # :doc:
4 gem_original_require path
5 rescue LoadError => load_error
6 if load_error.message =~ /#{Regexp.escape path}\z/ and
7 spec = Gem.searcher.find(path) then
8 Gem.activate(spec.name, "= #{spec.version}")
9 gem_original_require path
10 else
11 STDERR.write("Cannot load #{path} in #{$LOAD_PATH.join(", ")}\n")
12 raise load_error
13 end
14 end
so sánh hai phiên bản
Không biết có thư viện nào cho việc này không. Thôi đành viết cái hàm đơn giản sau. Hàm sẽ trả về true nếu phiên bản thứ nhất bằng hoặc mới hơn phiên bản thứ hai. Ví dụ,
1 __v1_ge_v2 1.2.0-1 1.1.9-9
sẽ trả về true. Ta bỏ qua việc kiểm tra sự hợp lệ của phiên bản, mà giả định rằng hai phiên bản đều có dạng a.b.c.x hoặc a.b.c-x. Về cách kiểm tra thì hai dòng cuối cùng của định nghĩa hàm gần như là một mẹo :)
1 # Return true if v1 >= v2
2 def __v1_ge_v2(v1,v2)
3 av1 = v1.split(/[\.\-]/).map(&:to_i)
4 av2 = v2.split(/[\.\-]/).map(&:to_i)
5 ret = av1.size.times.map {|i| av1[i] > av2[i] ? 2 : (av1[i] == av2[i] ? 1 : 0)}.join()
6 ii = ret.index("2")
7 zero = ret.index("0")
8 zero.nil? or (ii && ii < zero)
9 end
Mảng: tính tổng và giá trị trung bình
Cho trước một mảng các số, Ruby không có sẵn hàm để tính tổng, giá trị trung bình của tất cả các phần tử của mảng. Trong nhiều trường hợp, ta sẽ cần viết bổ sung nhanh các hàm sum, average cho lớp mảng. Có thể dùng vòng lặp (for, foreach, while,...) để duyệt qua các phần tử của mảng rồi cộng chúng lại với nhau (theo cách quen thuộc khi học nhập môn các ngôn ngữ lập trình.) Tuy nhiên, Ruby cung cấp hàm inspect rất đơn giản
1 class Array
2 def sum
3 inject(0) {|s,i| s + i}
4 end
5 end
Trong biểu thức |s,i| của ví dụ trên, biến i đại diện cho phần tử của mảng, còn biến s sẽ thay đổi giá trị và sự thay đó sẽ lưu trữ lại cho lần lặp kế tiếp. Giá trị khởi đầu của s có thể cho bằng tham số bổ sung của hàm inject, như ở trên là 0. Kết quả trả về của toàn bộ biểu thứ inject là giá trị s được tính toán sau cùng. Lưu ý rằng, ta đã giả định mảng chỉ gồm các số nguyên. Việc xác nhận tính chất này có thể thay đổi tùy trường hợp, nên không được nêu ra ở đây.
Khi đã có hàm sum thì hàm average thật đơn giản
1 def average
2 size > 0 ? sum / size : 0
3 end
Một ví dụ khác: tính tổng tất cả các số dương trong một mảng số
1 class Array
2 def psum
3 inject(0) {|s,i| i > 0 ? s + i : s}
4 end
5 end
Also available in: Atom