Ruby không chỉ nổi tiếng bởi cú pháp “thơ văn” mà còn vì khả năng xử lý code dưới dạng khối (block of code) cực kỳ linh hoạt. Trong thế giới Ruby on Rails, nếu bạn từng gặp yield, proc, hoặc lambda mà chưa thực sự hiểu sự khác biệt giữa chúng – thì bài viết này dành cho bạn.
I. Block, Proc và Lambda là gì?
1. Block
Block là một đoạn code có thể được truyền vào một method nhưng không phải là đối tượng (object). Ruby hỗ trợ hai dạng block:
# Dạng do...end
[1, 2, 3].each do |num| puts num
end # Dạng ngoặc {}
[1, 2, 3].each { |num| puts num }
Bạn thường thấy block đi kèm với yield trong method:
def say_hello puts "Hello" yield if block_given? puts "Bye"
end say_hello { puts "I'm a block" }
2. Proc
Proc là đối tượng của class Proc, là một object đại diện cho một đoạn mã (block) có thể được lưu trữ vào biến, truyền như tham số, và gọi thực thi nhiều lần.
my_proc = Proc.new { |name| puts "Hello #{name}" }
my_proc.call("Ruby") # => Hello Ruby
Đặc điểm:
- Không kiểm tra số lượng tham số (argument).
- Có thể được gọi bằng .call, .[], hoặc .()
Proc.new { |x, y| puts "#{x}, #{y}" }.call(1) # => "1, " (Không lỗi, thiếu biến y => nil)
return trong Proc sẽ thoát khỏi method bên ngoài, điều này có thể gây bất ngờ:
def test_proc my_proc = Proc.new { return "From Proc" } my_proc.call return "After Proc"
end puts test_proc
# => "From Proc"
Sử dụng:
Gọi proc với 1 giá trị
proc.call(2)
Gọi proc với 1 array
proc = Proc.new { |x| puts "Giá trị là #{x}" } [1, 2, 3].each(&proc)
# => Giá trị là 1
# => Giá trị là 2
# => Giá trị là 3
Dùng symbol to proc: &:method_name
Ký hiệu &
chuyển một symbol thành một proc object và truyền nó vào như một block
[1, 2, 3].map(&:to_s)
# tương đương với:
[1, 2, 3].map { |i| i.to_s }
Truyền proc như callback hay logic tái sử dụng
upcase_proc = Proc.new { |str| str.upcase } def process_strings(arr, proc) arr.map(&proc)
end puts process_strings(["a", "b", "c"], upcase_proc)
# => ["A", "B", "C"]
3. Lambda
- Lambda là một function và không có tên cụ thể
- Nó có thể được sử dụng để gán 1 đoạn code
- là một object
# Cách 1: Dùng từ khóa lambda
my_lambda = lambda { |x| puts "Hello #{x}" }
my_lambda.call("Rails") # Hello Rails # Cách 2: Dùng dấu chấm -> (ngắn gọn hơn)
my_lambda = ->(x) { puts "Hello #{x}" } # Cách 3: Với nhiều dòng
my_lambda = ->(x, y) do puts "x + y = #{x + y}"
end
Lambda giống Proc, nhưng nghiêm khắc hơn về số lượng tham số. Nó hoạt động như một method thực thụ.
my_lambda = -> (x, y) { puts x + y }
my_lambda.call(2, 3) # => 5 my_lambda.call(1) # ArgumentError
Một điểm khác biệt nữa: return trong lambda chỉ thoát khỏi lambda, trong khi return trong Proc thoát cả method chứa nó:
def test_lambda lam = lambda { return "Thoát khỏi lambda" } lam.call return "Thoát khỏi method"
end puts test_lambda # 👉 Output: "Thoát khỏi method"
Một vài ứng dụng thực tế vào trong rails:
# Trong callback hoặc validation
validate -> { errors.add(:base, "Custom error") if some_condition } # Trong scope của ActiveRecord
class Post < ApplicationRecord scope :published, -> { where(published: true) } scope :by_author, ->(author_id) { where(author_id: author_id) }
end Post.published
Post.by_author(1)
II. So Sánh Nhanh: Block vs Proc vs Lambda
III . Kết Luận
- Dùng block khi bạn chỉ cần truyền một đoạn code đơn giản vào method.
- Dùng proc khi bạn muốn lưu trữ khối code linh hoạt, ít ràng buộc.
- Dùng lambda khi bạn muốn kiểm soát chặt chẽ số lượng tham số và hành vi return.
Hiểu rõ ba khái niệm này sẽ giúp bạn viết code Ruby/Rails sạch hơn, rõ ràng hơn – và đặc biệt là tránh được những lỗi "khó nhằn" khi làm việc với callback, scope, hay bất kỳ DSL nào của Rails
Tài liệu tham khảo:
https://ruby-doc.org/core-2.6.1/Proc.html
https://viblo.asia/p/block-proc-va-lambda-trong-ruby-jaqG0lQQGEKw#_2-proc-2