Loại bỏ Nil value trong Ruby bằng Special Case Pattern
Bài đăng này đã không được cập nhật trong 7 năm
I. Mở đầu
Nil value khá là khó chịu, sự hiện diện của nó khiến chúng ta phải làm bừa bộn code của mình với những câu điều điện, cứ phải kiểm tra xem 1 biến có phải nil hay không. Hôm nay mình sẽ dùng 1 cách để loại bỏ nil value trong 1 vài tình huống thường gặp, sử dụng "Special case" pattern.
II. Ví dụ
Ví dụ dưới đây là 1 helper từ 1 web application, nó tìm current_user
bằng cách kiểm tra session
object. Nếu nó có thể tìm được thì nó trả về user
đó, còn không thì trả về nil
def current_user
User.find(session[:user_id]) if session[:user_id]
end
Hãy xem xem method này có thể được sử dụng ở đâu:
def greeting
"Hello, #{current_user ? current_user.name : 'Guest'}"
end
Method trên có thể dùng để chào user khi họ visit page, bằng cách kiểm tra có current_user
chưa, nếu có thì dùng name
của user đó, không thì dùng từ "Guest"
current_user ? render_logout_button : render_login_button
Đây là trường hợp ta dùng current_user
để kiểm tra user đã login chưa để render ra button login, logout tương ứng.
cart = current_user ? current_user.cart : SessionCart.new
cart.add_item(item, 1)
Đây là đoạn code dùng để thêm item vào cart, nếu user đã login thì item sẽ được add vào giỏ hàng của user đó, còn không thì nó sẽ được add vào giỏ hàng đặc biệt được lưu tạm thời trong session.
Tất cả những đoạn code ở trên đều có 1 điểm chung, đó là nó không chắc chắn về việc sẽ có 1 user object hay không, và nó phải kiểm tra đi kiểm tra lại. Bây giờ để loại bỏ những nil value khó chịu này, ta sẽ viết 1 class thể hiện trường hợp ấy. Ta gọi class đó là GuestUser
class GuestUser
def initialize(session)
@session = session
end
end
Ta viết lại method current_user
để trả về 1 instance của class này nếu không tìm thấy user_id
trong session
def current_user
session[:user_id] ? User.find(session[:user_id]) : GuestUser.new(session)
end
Ta thêm name
attribute vào class GuestUser
class GuestUser
# ...
def name
"Anonymous"
end
end
Bằng cách đó, ta đơn giản lại method greeting
:
def greeting
"Hello, #{current_user.name}"
end
Đối với trường hợp render login, logout button ta không thể loại bỏ hoàn toàn điều kiện được. Những gì ta có thể làm là thêm 1 method authenticated?
để kiểm tra user login chưa trong cả 2 class User
và GuestUser
class User
def authenticated?
true
end
end
class GuestUser
def authenticated?
false
end
end
Bằng cách này ta có thể viết lại điều kiện render login, logout button 1 cách ngữ nghĩa hơn:
current_user.authenticated? ? render_logout_button : render_login_button
Bây giờ để implement cart cho users chưa login, ta có thể viết 1 method trong GuestUser
để return lại 1 instance của SessionCart
class GuestUser
# ...
def cart
SessionCart.new
end
end
Giờ đoạn code để add item vào cart ta có thể viết ngắn lại thành current_user.cart.add_item(item, 1)
Những gì ta vừa làm chính là nhận dạng 1 "Special Case" - Case mà khi user chưa login. Và sau đó ta có thể diễn tả trường hợp đặc biệt đó như là 1 object bình thường. Kết quả là ta có thể đơn giản đoạn code của mình hơn, và đặc biệt là khiến đoạn code có ngữ nghĩa hơn.
All rights reserved