Presenter in Rails
http://nithinbekal.com/posts/rails-presenters/ Khi model của bạn bị phình to với rất nhiều methods mà nó chỉ được sử dụng duy nhất trong views, đây có lẽ là khoảng thời gian tốt nhất để refactor chúng. Di chuyển logic đó vào trong helper modules có thể là OK trong một số trường hợp, nhưng sự ...
http://nithinbekal.com/posts/rails-presenters/
Khi model của bạn bị phình to với rất nhiều methods mà nó chỉ được sử dụng duy nhất trong views, đây có lẽ là khoảng thời gian tốt nhất để refactor chúng. Di chuyển logic đó vào trong helper modules có thể là OK trong một số trường hợp, nhưng sự phát triển trong views của bạn khá phức tạp, có lẽ bạn cần xem xét đến presenter.
Presenter cung cấp cho bạn một cách hướng đối tượng (object oriented) để tiếp cận với views helpers. Trong bài viết này tôi sẽ theo hướng làm sao để refactored views của chúng ta bằng cách sử dụng presenter thay vì các phương thức trong helper hoặc thậm chí là các methods trong model.
Ryan Bates' một chuyên gia tuyệt vời Railscast có một bài viết về presenters from scratch nó là bài hướng dẫn đã dẫn dắt tôi đến refactor này. Nó đi sâu hơn về những gì tôi tôi sẽ viết ở đây, vì thế hãy xem nó sau khi bạn đọc bài viết này.
Refactoring the views
Chúng ta có một HAML view nơi chúng ta sẽ hiện thị title, và trạng thái public của bài viết (publication status). Title là một thuộc tính ActiveRecord và trạng thái của bài viết là một method trong Post model.
%h1= @post.title %p= @post.publication_status
publication_status sẽ hiện thị ngày public bài viết nếu nó đã được published. Nếu không sẽ được hiện thị ra một string là Draft
class Post < ActiveRecord::Base def publication_status published_at || 'Draft' end end
Nó không thực sự là quá tệ. Nhưng điều gì nếu chúng ta muốn show publication date theo dạng X hours agothay thế format bình thường? Chúng ta nên sử dụng time_ago_in_words một helper method của Rails, nhưng nó không available trong model. Một cách đơn giản nhất để tiếp cận đến nó là chúng ta sẽ chuyển method đó sang helper module.
module PostHelper def publication_status post if post.published_at time_ago_in_words post.published_at else 'Draft' end end end
Nó đã giải quyết được vấn đề của chúng ta, nhưng view helpers có những bất lợi của việc đưa tất cả các methods helper vào bên trong một single namespace. Nó cần có một class để chứa tất cả helper methods liên quan.
Creating our first presenter
Chúng ta có thể tạo một class với tên PostPresenter để implement logic mà chúng ta đã viết trong helper.
class PostPresenter < Struct.new(:post, :view) def publication_status if post.published_at? view.time_ago_in_words(post.published_at) else 'Draft' end end end presenter = PostPresenter.new(@post, view_context) presenter.publications_status #=> 'Draft'
view_context ở đây chính là một đại diện cho một instance của ActionView, đó cũng là nơi mà chúng ta có thể dùng được view helpers như hàm time_ago_in_words.Chính vì PostPresenter không chứa các helper methods, nên chúng ta cần pass view_context vào class như một tham số.
ví dụ về time_ago_in_words
ActionView::Base.new.time_ago_in_words Time.now => "less than a minute"
Bây giờ chúng ta sẽ giải quyết vấn đề với #title bởi vì nó cũng cần thiết đối với view. Chúng ta có thể tránh điều này bằng cách gọi trực tiếp method trong post object nếu chúng ta không defined nó trong presenter. Bây giờ chúng ta hãy tạo một base class BasePresenter nó sẽ được kế thừa bởi tất cả các class presenter của chúng ta.
class BasePresenter < SimpleDelegator attr_reader :model, :view def initialize model, view @model, @view = model, view super @model end end
Do kế thừa từ một class dựng sẵn của Ruby là SimpleDelegator và đã gọi super trong phương thức khởi tạo, nên chúng ta đảm bảo rằng nếu chúng ta gọi bất kì method nào mà nó không được định nghĩa trong presenter thì nó sẽ được gọi ra trong post object.
Sau khi được kế thừa từ BasePresenter thì presenter của chúng ta sẽ như sau:
class PostPresenter < BasePresenter def publication_status if model.published_at? view.time_ago_in_words model.published_at else 'Draft' end end end
Chúng ta có thể khởi tạo presenter ngay trong controller của chúng ta.
class PostsController < ApplicationController def show post = Post.find(params[:id]) @post = PostPresenter.new(post, view_context) end end
Di chuyển presenter ra khỏi controllers
Để đơn giản hóa hơn nữa, chúng ta nên tránh sử dụng presenter object trong controller và thay vào đó chúng ta sẽ thêm vào một helper method, nó sẽ cho phép bạn wrap các đối tượng ActiveRecord vào trong presenter và đích đến là view.
module ApplicationHelper def present(model) klass = "#{model.class}Presenter".constantize presenter = klass.new(model, self) yield(presenter) if block_given? end end
Ở đây chúng ta đã pass presenter object vào một block, block đó chúng ta cũng sẽ pass vào view, do đó chúng ta có thể viết code như sau:
- present(@post) do |post| %h2= post.title %p= post.author
Custom presenters
Những mã code trên cho thấy tất cả các class presenter của chúng ta đều follow theo một convention là thêm Presenter vào sau tên của model name. Đôi khi tôi muốn chia nhỏ presenter ra thành các class nhỏ hơn để sử dụng được những nơi khác nhau. Ví dụ: có một AdminPostPresenter nó chứa các method để hiện thị post trên màn hình admin.
Trong trường hợp này, chúng ta sẽ làm cho presenter chấp nhận một tham số thứ 2 cho phép truyền vào class name:
module ApplicationHelper def present model, presenter_class=nil klass = presenter_class || "#{model.class}Presenter".constantize presenter = klass.new(model, self) yield(presenter) if block_given? end end
Nó cho phép chúng ta gọi function present(@post, AdminPostPresenter)ở trong view nơi mà tôi sử dụng một admin presenter đặc biệt, trong khi đó chúng ta vẫn có thể tiếp tục sử dụng presenter(@post) ở những nơi khác.
Kết luận
Sử dụng presenter là một cách tốt cho chúng ta maintaining views. Họ cũng có thêm nhiều tiện ích dễ dàng cho việc test. Nếu bạn maitaining Rails views của bạn và gặp vấn đề, thì presenter là một cách tốt để clean mọi thứ.
Tài liệu
- Ryan Bates’ Presenters from Scratch Railscast
- Draper gem
- Jay Fields’ article about presenters
- Pull-req của Justin Gordon có nhiều thảo luận hữu ích về presenter.