12/08/2018, 13:28

ActiveRecord refactoring (P3) - Presenters

Mở đầu Như vậy là thông qua việc dịch chuỗi bài viết ActiveRecord Refactoring của tác giả Luke Morton, mình đã cùng các bạn đã lần lượt tìm hiểu bài dịch về concerns và services. Hôm nay mình xin giới thiệu đến các bạn bài viết cuối cùng trong chuỗi bài viết về ActiveRecord Refactoring này - ...

Mở đầu

Như vậy là thông qua việc dịch chuỗi bài viết ActiveRecord Refactoring của tác giả Luke Morton, mình đã cùng các bạn đã lần lượt tìm hiểu bài dịch về concerns và services.

Hôm nay mình xin giới thiệu đến các bạn bài viết cuối cùng trong chuỗi bài viết về ActiveRecord Refactoring này - Presenters.

869d002d3823aa3ffc8b2e98a8ab5f117121734e.png

Phần 3. Presenters

Presenters cung cấp cho chúng ta một cách thức của việc vá chức năng bổ sung cho model của bạn trước khi chúng ta truyền nó đến một khung nhìn. Thường thì presenters sẽ là cho từng trang cụ thể nhưng nó cũng có thể mang tính tổng quát hơn và cũng dựa module hoặc dựa thành phần.

Nếu như không phải là Rails, presenter thường được biết đến như là mẫu View Model hơn. Khái niệm View Model giúp chúng ta giải thích rằng các mẫu là cho cái gì. Ví dụ : một khung nhìn miền model cụ thể, một nơi để đưa logic chỉnh sửa một miền model cho một khung nhìn cụ thể.

Thông thường, presenters là một đối tượng ủy thác đến miền model. Nói cách khác, một presenters sẽ chứa một model và với tất cả các phương thức mà không tồn tại trong presenters thì nó sẽ ủy thác ngược trở lại cho đối tượng ban đầu, đối tượng gốc và gọi đến phương thức ở trên đó.

Presenters cũng là một hình thức của decorator, một đối tượng thêm các chức năng vào đối tượng mà nó decorate. Trong trường hợp này, đối tượng presenter đang decorate khung nhìn một chức năng cụ thể vào một model. Hãy cũng phân tích sâu hơn với một ví dụ cho dễ hiểu nhé.

Một presenter cụ thể - UserProfilePresenter, nó có gọi đến model User cho hành động UsersController#view.

class UsersController < ApplicationController
  def view
    @user = UserProfilePresenter.new(User.find(params[:id]))
  end
end

Như đã thấy ở trên, chúng ta truyền model vào presenter trong hành động của controller. Và sau đó, view sẽ sử dụng biến @user giống như là bạn đang sử dụng trực tiếp một model vậy. Bạn có thể truyền User trong biến @user và bạn cần viết một số logic cụ thể cho view.

Giả sử chúng ta cần sử dụng username nếu như full name không tông tại trong trang tiêu đề của trang hồ sơ cá nhân.

require "delegate"

class UserProfilePresenter < SimpleDelegator
  def page_title
    I18n.t("user.profile.page_title", name: full_name || username)
  end
end

Ở đây chúng ta sử dụng một trình dịch và truyền vào full_name || username vào như là biến. Tất nhiên chúng ta có thể đặt dòng này ở trong view.

Nếu như chúng ta muốn hiển thị cho dù là người dùng đang online hay là thời gian họ online lần cuối, chúng ta có thể đặt logic này ở trong một phương thức #online_status.

require "delegate"

class UserProfilePresenter < SimpleDelegator
  def online_status
    if online?
      I18n.t("user.profile.online_status.online")
    else
      I18n.t("user.profile.online_status.last_online", last_online: last_online)
    end
  end
end

Một lần nữa chúng ta gọi đến trình dịch, nhưng có sử dụng thêm một chút logic để quyết định xem là sẽ hiển thị một thông báo trực tuyến hay là lần online cuối cùng.

Đến đây thì có lẽ bạn sẽ thắc mắc là các phương thức full_name, username, online? và last_online thì ở đâu ra? Và đó là SimpleDelegator. Lớp này có trong thư viện chuẩn của Ruby và nó cung cấp một phương thức initialize.

Như bạn đã thấy ở UsersController bên trên kia, chúng ta đã truyền vào một đối tượng User. Bất kỳ phương thức nào không tồn tại trong UserProfilePresenter thì sẽ được gọi đến trong đối tượng User mà chúng ta đã truyền vào. Điều đó có nghĩa là presenter sẽ có tất cả các phương thức mà model có, bất kỳ phương thức ghi đè hay là chúng ta tự định nghĩa mới.

Nó rất hữu ích vì chúng ta sẽ thường xuyên muốn sử dụng trực tiếp phương thức của model mà không qua sửa đổi.

Thỉnh thoảng bạn sẽ có những phương thức được sử dụng trong nhiều view có liên quan đến User. Hãy xem phương thức online_status từ ví dụ trên.

Nếu cần thiết trong nhiều view, chúng ta có vài lựa chọn :

  • Di chuyển nó vào đối tượng User. Tuy nhiên, nếu bạn muốn giữ view liên quan đến logic, đặc biệt là khi gọi đến trình dịch I18n.t, sau đó ta có thể lại muốn đặt nó ở một nơi khác.

  • Sử dụng helper, UserHelper cung có thể là một phương thức như vậy:

module UserHelper
  def user_online_status
    if @user.online?
      I18n.t("user.profile.online_status.online")
    else
      I18n.t("user.profile.online_status.last_online", last_online: @user.last_online)
    end
  end
end

Tuy nhiên tôi lại thích tạo một mixin UserPresenter hơn :

class UserPresenter
  module Mixin
    def online_status
      if online?
        I18n.t("user.profile.online_status.online")
      else
        I18n.t("user.profile.online_status.last_online", last_online: last_online)
      end
    end
  end

  include Mixin
end

Ở đây, chúng ta có UserPresenter mà có thể sử dụng độc lập; 'UserPresenter::Mixin thì có thể chứa nhiều presenter cụ thể như là UserProfilePresenter. Tuyệt vời!!!

Đối tượng presenter thực hiện kiểm tra các khía cạnh phức tạp của các trường hợp khung nhìn một cách đơn giản hơn. Nó không thay thế được test sử dụng capybara nhưng nó sẽ giúp giảm tải việc cần thiết phải test view trong unit test.

Trên đây mình đã trình bày xong chuỗi 3 bài viết này, hi vọng mọi người sẽ có được một cái nhìn tổng quát hơn về ActiveRecord refactoring.

  • ActiveRecord refactoring (P1) - Concerns
  • ActiveRecord refactoring (P2) - Services

Cảm ơn bạn đã theo dõi bài viết

tribeo

0