Ruby Rails 具有复杂逻辑的 haml 视图装饰器

Ruby on Rails decorator for haml view with complex logic

这是我想使用装饰器重构的部分 haml 视图。

- if comments.empty?
  = t('.none')

- comments.order(:created_at).each do |comment|
  .comment{ class: choose_class_for(comment) }
    =markdown(comment.body)

    - if comment.person.present?
      - if comment.type == 'InternalComment'
        - rating = comment.person.rating_for(proposal)
        .rating{class:disable_click(comment)}
          = rating_label_build(5, rating)
      - if edited?(comment)
        .meta
          %small{ title: comment.updated_at.to_s }
            =t( '.edited_at', date: comment.updated_at.strftime('%-d %b @ %H:%M') )

      .meta
        %small{ title: comment.created_at.to_s }
          =t( '.created_by', name: comment.person.name, date: comment.created_at.strftime('%-d %b @ %H:%M') )
        - if current_user == comment.person || current_user.admin?
          = link_to proposal_comment_path(slug: event.slug, proposal_uuid: proposal, id: comment.id, type: comment.type, proposal_id: proposal.id, person: comment.person),
        method: :delete, data: {confirm: t('.confirm')}, class: 'btn btn-xs btn-danger', id: 'delete' do
        %span.fa.fa-trash-o{title: t('.delete')}
        - if current_user == comment.person
          = link_to edit_proposal_comment_path(slug: event.slug, proposal_uuid: proposal, proposal_id: proposal.id,
        id: comment.id, type: comment.type), class: 'btn btn-xs btn-primary' do
        %span.fa.fa-edit{title: t('.edit')}

- unless comments.name == 'InternalComment'
  = form_for comments.new do |form|
    = form.hidden_field :proposal_id
    .form-group
      = form.text_area :body, class: 'form-control', placeholder: t('.placeholder'), rows: 5, maxlength: Comment::BODY_LENGTH
      %p.help-block= t('.comments_are_limited', body_length: Comment::BODY_LENGTH)
    %button.btn.btn-success.save-comment{type: "submit"}
      %span.glyphicon.glyphicon-ok
      =t('.comment')

这是使用这条线实现的

= render partial: 'proposals/comments', locals: { proposal: proposal, comments: proposal.public_comments }

我看过所有涉及装饰器的教程,但它们只涉及简单的示例,例如简化单个 if 语句或字符串操作。我该如何着手重构它?

这是一个相当复杂的问题。这是一个带有不完整代码的部分答案,肯定会有错误,但可以让您了解如何开始。我们一直在做这样的事情——尽管我试着做了一些简化。

作为介绍方式,此答案使用 PresenterDecorator。关于这些词的含义和不含义,存在大量的讨论和争论。出于我们的目的,我们在需要进行渲染时使用 Presenters。我们使用 Decorators 来包装模型。大致。肯定会有人骂我的。

要在您的 controller 中使用 Presenter(此处称为 ProposalPresenter),您需要执行如下操作:

@page_content = ProposalPresenter.present(self, proposal)

请注意,这会传入 controllerproposal。传入 controller 允许在 presenter.

内渲染

我们在做这一切时试图实现的一件事是我们的视图(模板或部分)从不有任何逻辑,它们有除了他们的主持人之外,对任何事情一无所知。因此,我们大大简化了我们的观点。

proposal_presenter.rb

class ProposalPresenter

  attr_accessor :comment,
                :controller,
                :proposal

  delegate  :comments,
            to: :proposal

  delegate  :edited?,
            :person,
            :internal_comment?,
            :pretty_updated_at,
            to: :comment

  class < self 

    def present(controller, proposal)
      new(controller, proposal).present
    end

  end

    def initialize(controller, proposal)
      @controller, @proposal = controller, proposal
      # setting a @presenter variable on the controller
      # allows for the partials to access @presenter
      @controller.instance_variable_set('@presenter', self)
    end

    def present
      comments.order(:created_at).each_with_object("") do |comment, to_return|
        # wrap the comment in the `CommentDecorator`
        # so that you can add logic in the decorator
        @comment = CommentDecorator.new(comment)
        to_return << render_partial(:comment)
      end.html_safe
    end

    # any method called in a partial needs to be a public method

    def comment_class
      #logic, then 
    end

    def comment_markdown
      markdown(comment.body)
    end

    def comment_person
      render_partial if person
    end

    def rating
      render_partial if internal_comment?
    end

    def rating_score
      person.rating_for(proposal)
    end

    def edited
      render_partial if edited?
    end

    def updated_at
      comment.updated_at.to_s
    end

  private

    def view_context
      controller.view_context
    end

    def method_missing(meth, *params, &block)
      if view_context.respond_to?(meth)
        view_context.send(meth, *params, &block)
      else
        super
      end
    end

    def render_partial(partial_name=nil)
      partial_name = partial_name.to_s if partial_name
      partial_name ||= caller[0][/`.*'/][1..-2]
      render(partial: partial_name)
    end          

end

comment_decorator.rb

class CommentDecorator < SimpleDelegator 

    def edited?
      #some logic
    end

    def internal_comment?
      type == 'InternalComment'
    end

    def pretty_updated_at
      updated_at.strftime('%-d %b @ %H:%M')
    end

end

_comment.html.haml

.comment{class @presenter.comment_class}
  = @presenter.comment_markdown
  = @presenter.comment_person
  = @presenter.comment_form

_person.html.haml

= @presenter.rating
= @presenter.edited

_rating.html.haml

.rating{class: @presenter.rating_class}
  = rating_label_build(5, @presenter.rating_score)

_edited.html.haml

.meta 
  %small{title: @presenter.updated_at}
    = t('.edited_at', date: @presenter.pretty_updated_at)

在我们的模板中,我们会做类似的事情:

some_template.html.haml

@page_content