10 thủ thuật Ruby mà bạn chưa từng thấy

1. Deep copy

Khi bạn copy một object mà bên trong nó có chứa những object khác, ví dụ như là một Array, thì đơn thuần bạn chỉ copy tham chiếu đến các object đó.

Bạn có thể xem nó ở hành động dưới đây:

food = %w( bread milk orange )
food.map(&:object_id)
=> [69612840, 69612820, 69612800]
[122] pry(main)> food.clone.map(&:object_id)
=> [69612840, 69612820, 69612800]

Sử dụng Marshal (thường được dùng cho serialization) bạn có thể tạo ra một deep copy của một object.

def deep_copy(obj)
  Marshal.load(Marshal.dump(obj))
end

Và kết quả như sau:

ruby
ObjectSpace.each_object(String).select{|x| x == food.first}.count
=> 54
deep_copy(food).map &:object_id
=> [77564660, 77564420, 77564300]
ObjectSpace.each_object(String).select{|x| x == food.first}.count
=> 56

Bonus: Sự khác biệt giữa clonedup (bản chất thì tương đương nhau nhưng mà clone có 2 điểm mà dup không có):

  • Sao chép cả singleton class của object được sao chép
  • Duy trì trạng thái frozen của object được sao chép.

Ví dụ:

  • Singleton methods
a = Object.new
def a.foo; :foo end
p a.foo
=> :foo
b = a.dup
p b.foo
=> undefined method `foo' for #<Object:0x007f8bc395ff00> (NoMethodError)
c = a.clone
c.foo
=> :foo
  • Trạng thái Frozen
a = Object.new
a.freeze
p a.frozen?
=> true
b = a.dup
p b.frozen?
=> false
c = a.clone
p c.frozen?
=> true

2. Các cách khác nhau để call một lambda

my_lambda = -> { puts 'Hello' }
 
my_lambda.call
my_lambda[]
my_lambda.()
my_lambda.===

Cách đầu tiên là cách được hầu hết mọi người sử dụng => bạn cũng chỉ cần nhớ đến cách đó là đủ.

3. Tạo mảng các phần tử có sẵn

Array.new(10) { rand 300 }

Nó sẽ tạo ra một mảng chứa 10 phẩn tử có giá trị random từ 0 đến 299

4. Lambdas quản lý rất nghiêm các tham số truyền vào, nhưng Procs thì không quan tâm đến điều đó

my_lambda = ->(a, b)  { a + b }
my_proc   = Proc.new  { |a, b| a + b }
 
my_lambda.call(2)
=> ArgumentError: wrong number of arguments (1 for 2)
 
my_proc.call(2)
=> TypeError: nil can't be coerced into Fixnum
5. Thực thi code trực tiếp mà không cần đến irb hay files

Ruby command có rất nhiều option cho bạn lựa chọn, bạn có thể xem bằng lệnh:

ruby -h
Usage: ruby [switches] [--] [programfile] [arguments]
  -0[octal]       specify record separator (\0, if no argument)
  -a              autosplit mode with -n or -p (splits $_ into $F)
  -c              check syntax only
  -Cdirectory     cd to directory before executing your script
  -d              set debugging flags (set $DEBUG to true)
  -e 'command'    one line of script. Several -e's allowed. Omit [programfile]
  -Eex[:in]       specify the default external and internal character encodings
  -Fpattern       split() pattern for autosplit (-a)
  -i[extension]   edit ARGV files in place (make backup if extension supplied)
  -Idirectory     specify $LOAD_PATH directory (may be used more than once)
  -l              enable line ending processing
  -n              assume 'while gets(); ... end' loop around your script
  -p              assume loop like -n but print line also like sed
  -rlibrary       require the library before executing your script
  -s              enable some switch parsing for switches after script name
  -S              look for the script using PATH environment variable
  -T[level=1]     turn on tainting checks
  -v              print version number, then turn on verbose mode
  -w              turn warnings on for your script
  -W[level=2]     set warning level; 0=silence, 1=medium, 2=verbose
  -x[directory]   strip off text before #!ruby line and perhaps cd to directory
  -h              show this message, --help for more info

Với flag -e bạn có thể thực thi được một đoạn mã ngắn gửi vào trong đó:

ruby -e '5.times { puts "Fun with Ruby" }'

6. Sử dụng mini-irb trong một lệnh

Có bao giờ bạn tự hỏi rằng irb hoạt động như thế nào? Dưới đây là một version vô cùng đơn giản của nó

ruby -n -e 'p eval($_)'

Bạn sẽ không thấy được sự nhắc nhở nào, tuy nhiên hãy gõ gõ các lệnh ruby ở đây (để kết thúc gõ exit, hoặc ctrl+c)

ruby -n -e 'p eval($_)'
puts "a"   
a
nil

7. Unfreeze một object (cái này rất nguy hiểm)

Ruby không có bất kì một method nào để unfreeze một object, nhưng sử dụng Fiddle class bạn có thể vào Ruby internals để làm cho nó có thể xảy ra.

require 'fiddle'
 
str = 'water'.freeze
str.frozen?
=> true
 
memory_address = str.object_id * 2
 
Fiddle::Pointer.new(memory_address)[1] &= ~8
 
str.frozen?
=> false

8. Object với identity đặc biệt

Ruby object có một identitier hoặc số id để bạn có thể truy cập sử dụng phương thức object_id. Một số object có object_id cố định như : Fixnums, true, false & nil.

false.object_id # 0
=> 0
true.object_id
=> 20
nil.object_id
=> 8
 
1.object_id
=> 3
2.object_id
=> 5

Fixnum ids sử dụng công thức: (number * 2) + 1 Bonus: Maximum của Fixnum là 1073741823, sau đó bạn sẽ có 1 object Bignum

9. Tránh dùng output lớn trong irb hoặc pry

Nếu bạn đang làm việc với irb và muốn tránh điền vào màn hình của bạn một contents lớn như là một array hay một string lớn, bạn có thể nối thêm ; ở cuối dòng code của bạn Ví dụ

require 'rest-client'
 
RestClient.get('blackbytes.info');

Hãy thử với trường hợp không có ; và xem sự khác biệt của nó.

10. Sử dụng caller method để lấy call stack hiện tại

Xem ví dụ sau

def foo
  bar
end
 
def bar
  puts caller
end
 
foo

Và output sẽ như sau

(pry):236:in `foo'
(pry):244:in `<main>'
....

Nếu bạn cần tên phương thức hiện tại, bạn có thể dùng __method__ hoặc __callee__.