5 method trong Ruby mà bạn nên dùng
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 . ...
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!