+2

Một kỹ thuật sử dụng yield trong rails

Những ai biết về rails chắc hẳn đã từng gặp qua từ khóa "yield", những ví dụ về yield đa phần đều liên quan đến view và nó có vẻ khá giống với "render":

<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

Tuy nhiên bài viết này sẽ nói về 1 cách dùng khác của yield, kỹ thuật sử dụng yield trong method.

Đặt vấn đề

Giả sử ta có một đoạn code như sau:

def start_till_cash_register_session
  cmd = Till::StartNewSession.new(
    uuid: SecureRandom.uuid,
    terminal_name: "2nd floor, nr 2103",
    employee_name: "Jurgen Klinsman",
    organizer_id: user_id,
    currency: currency,
    starting_cash_balance: "0.00",
  )
  command_bus.call(cmd)
end

Chúng ta không cần quan tâm nó nằm ở đâu, để làm gì hay nó đang ở trong bối cảnh như thế nào, chúng ta sẽ chỉ xét đến góc độ nó là một ví dụ. Ở ví dụ trên ta thấy có các giá trị mặc định, và chúng ta có thể làm những cách nào để ghi đè lên chúng?

Đối số được gán giá trị mặc định

Chúng ta có thể dùng đối số và gán giá trị mặc định cho nó:

def start_till_cash_register_session(
    uuid: SecureRandom.uuid,
    terminal_name: "2nd floor, nr 2103",
    employee_name: "Jurgen Klinsman",
    starting_cash_balance: "0.00",
)

  cmd = Till::StartNewSession.new(
    uuid: uuid,
    terminal_name: terminal_name,
    employee_name: employee_name,
    organizer_id: user_id,
    currency: currency,
    starting_cash_balance: starting_cash_balance,
  )
  command_bus.call(cmd)
end

Bây giờ muốn thay đổi giá trị default ta có thể truyền thêm đối số vào method như sau:

start_till_cash_register_session(employee_name: "Batman")

Tuy nhiên, chúng ta có thể thấy rằng cách trên khá dài dòng. Không chỉ như vậy, giả sử bây giờ chúng ta muốn đổi tên một đối số, chúng ta cần phải tìm tất cả những chỗ mà đối số được sử dụng và đổi lại. Ngoài ra kết quả việc tìm kiếm từ khóa với cái tên đó cũng tăng lên, gây khó khăn cho việc maintain hơn. Vậy có cách nào tốt hơn không? Tất nhiên là có. Chúng ta hãy cũng tìm hiểu hai cách sau nhé.

Merge attributes

Chúng ta sử dụng method merge của Hash để có thể ghi đè lên những giá trị mặc định. Cùng xét đoạn mã sau:

def start_till_cash_register_session(**attributes)
  defaults = {
    uuid: SecureRandom.uuid,
    terminal_name: "2nd floor, nr 2103",
    employee_name: "Jurgen Klinsman",
    organizer_id: user_id,
    currency: currency,
    starting_cash_balance: "0.00",
  }
  cmd = Till::StartNewSession.new(defaults.merge(attributes))
  command_bus.call(cmd)
end

Để thay đổi một giá trị mặc định, ta có thể gọi như sau:

start_till_cash_register_session(employee_name: "Batman")

Như vậy đoạn code đã ngắn lại và nó cũng giải quyết được những vấn đề maintain ở trên. Tuy nhiên nó sẽ không có thông báo gì trong trường hợp đầu vào của chúng ta không ghi đè lên attributes trong Hash mà sẽ tạo ra attributes mới. Điều này sẽ được class StartNewSession thực hiện.

Dùng yield object

Đây là một cách tiếp cận khác và cũng là điều mà bài viết này muốn nói đến:

def start_till_cash_register_session
  cmd = Till::StartNewSession.new(
    uuid: SecureRandom.uuid,
    terminal_name: "2nd floor, nr 2103",
    employee_name: "Jurgen Klinsman",
    organizer_id: user_id,
    currency: currency,
    starting_cash_balance: "0.00",
  )
  yield cmd if block_given?
  command_bus.call(cmd)
end

Thay vì việc thông qua các đổi số, chúng ta thông qua trực tiếp đối tượng chính bằng cách dùng yield để gọi. Sau đó chúng ta có thể xử lý đổi tượng đó kiểu như ví dụ sau:

start_till_cash_register_session{|cmd| cmd.employee_name = "Batman" }

Hoặc có thể là như sau (điều này phụ thuộc vào interface của StartNewSession mà chúng ta xây dựng):

start_till_cash_register_session do |cmd| 
  cmd.employee_name("Batman").starting_cash_balance("200.00")
end

Kết luận

Một vấn đề trên nhưng số cách giải quyết nó là vô cùng. Hoặc chúng ta cũng có thể kết hợp nhiều cách giải quyết với nhau tùy vào trường hợp gặp phải. Mong rằng bài viết sẽ hữu ích với bạn.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.