5 method trong Ruby mà bạn nên dùng
Bài đăng này đã không được cập nhật trong 7 năm
Object#tap
Vào một ngày đẹp trời, bạn implement code cho function login bằng Omniauth
, class Use
cần method như sau:
def self.from_omniauth auth
user = find_or_initialize_by email: auth.info.email
user.name = auth.info.name
user.provider = auth.provider
user.password = User.generate_unique_secure_token if user.new_record?
user.save
user
end
Bạn tưởng chừng đến đây là xong, nhưng trước lúc gửi pull request, bạn chạy reek và nhận ngay trái đắng:
User#from_omniauth has approx 6 statements [https://github.com/troessner/reek/blob/master/docs/Too-Many-Statements.md]
Quá nhiều statements
cho một method
. Và vấn đề của bạn là làm sao có thể giảm bớt số statements
lại.
Đây là nơi cho tap
thể hiện sức mạnh.
Refactor lại code bằng tap
:
def from_omniauth auth
User.find_or_initialize_by(email: auth.info.email).tap do |user|
user.name = auth.info.name
user.provider = auth.provider
user.password = User.generate_unique_secure_token if user.new_record?
user.save
end
end
Sẽ chằng còn một lỗi reek
nào nữa.
Để biết nơi nào cần sử dụng tap
, chúng ta chỉ cần để ý:
Method nào thay đổi các attributes của object nhưng lại không trả về object đó.
Như ví dụ trên, method user.save
không trả về user
mà chúng ta muốn, nó chỉ trả về true
hoặc false
. Vậy nên chúng ta cần sử dụng tap
Array#bsearch
Có rất nhiều hàm tìm kiếm trong Ruby mà chúng ta có thể sử dụng trên Array
như: select
, 'reject', 'find', nhưng khi mà Array quá lớn, chúng ta phải bắt đầu chú ý đến thời gian thực thi của các hàm trên.
Ví dụ sử dụng find
, nó sẽ tìm trên toàn bộ array
, đến lúc nào tìm thấy thì thôi. Độ phức tạp O(n)
.
Tuy nhiên có một cách nhanh hơn, độ phức tạp O(log n)
:
require "benchmark"
data = (0..50_000_000)
Benchmark.bm do |x|
x.report(:find) { data.find {|number| number > 40_000_000 } }
x.report(:bsearch) { data.bsearch {|number| number > 40_000_000 } }
end
user system total real
find 3.020000 0.010000 3.030000 (3.028417)
bsearch 0.000000 0.000000 0.000000 (0.000006)
Như bạn có thể thấy, bsearch
nhanh hơn rất nhiều.
Tuy nhiên bsearch
có một nhược điểm đó là: array
cần phải được sort
trước khi tìm kiếm. Tuy nhiên cũng có khá nhiều trường hợp phải dùng đến nó. Ví dụ như tìm kiếm created_at
chẳng bạn.
Enumerable#flat_map
Tưởng tượng, bạn phải implement một method. Bạn có danh sách post
được viết vào tháng trước bởi các users
, tìm những người đã comment trên các post
đó.
Chúng ta implement method đó như sau.
module CommentFinder
def self.find_for_users(user_ids)
users = User.where(id: user_ids)
users.map do |user|
user.posts.map do |post|
post.comments.map |comment|
comment.author.username
end
end
end
end
end
Kết quả trả về như sau:
[[['Ben', 'Sam', 'David'], ['Keith']], [[], [nil]], [['Chris'], []]]
Nhưng kết quả mà bạn mong muốn chỉ là tên các user
thôi. Đây là nơi bạn cần sử dụng flatten
module CommentFinder
def self.find_for_users(user_ids)
users = User.where(id: user_ids)
users.map { |user|
user.posts.map { |post|
post.comments.map { |comment|
comment.author.username
}.flatten
}.flatten
}.flatten
end
end
Hoặc bạn cũng có thể sử dụng flat_map
:
module CommentFinder
def self.find_for_users(user_ids)
users = User.where(id: user_ids)
users.flat_map { |user|
user.posts.flat_map { |post|
post.comments.flat_map { |comment|
comment.author.username
}
}
}
end
end
Array#new with a Block
Tưởng tượng, bạn cần một array
có 1000 phần từ nil
, có nhiều cách để làm nhưng cách làm đơn giản nhất là:
Array.new(1000) {nil}
<=>
Đây là method có thể chúng ta ít sử dụng trong Ruby nhưng trong các class được Ruby 'built-in' sẵn thì lại được sử dụng rất nhiều. Đây là cách nó làm việc:
4 <=> 4 # 0
5 <=> 4 # 1
4 <=> 5 # -1
Các class khi muốn include Comparable
module, bạn cần implement method này.
Tưởng tượng, bạn cần implement một method cho phép cộng trừ thời gian bằng phút và giờ. Việc này sẽ trở nên rất phức tạp nếu con số bạn muốn cộng trừ lớn hơn 60 minutes
. Khi thời gian muốn trừ lớn hơn 60 minutes
, bạn cần trừ đi 1 hour
và trừ đi 60 minutes
def fix_minutes minutes
until (0...60).member? minutes
@hours -= 60 <=> minutes
@minutes += 60 * (60 <=> minutes)
end
@hours %= 24
self
end
Sử dụng <=>
chúng ta có thể implement một function phức tạp bằng đoạn code rất ngắn. Less code less bugs
Happy Coding!
All rights reserved