18 November 2014

前言

Maintainable Rails View追溯到的资料,怎么说呢,原本是PPT的内容。收获是,原来存在可直接从Markdown转换为Slides的gem包。

正文

Rails应用中view的基础是布局。模板通常会被忽略。

不可维护的模板包含:

  • 重复标签
  • 模板中包含逻辑(高度重复,难于测试)

好的设计师重复自己,好的程序员从不重复。

避免标签重复的方法:

  • 抽象接口组件
  • 使用部分视图

程序中逻辑

重复逻辑实例:

<h3>Your Saved Credit Card</h3>

<dl>
  <dt>Number</dt>
  <dd>XXXX-XXXX-XXXX-<%= @credit_card.number[-4..-1] %></dd>
  <dt>Exp. Date</dt>
  <dd>
    <%= @credit_card.expiration_month %> / <%= @credit_card.expiration_year %>
  </dd>
</dl>
<p>
  Thanks for ordering! Your purchase has been billed to your credit card:
  <strong>XXXX-XXXX-XXXX-<%= @order.credit_card.number[-4..-1] %></strong>
</p>

解决方法: 使用 Helpers

Helper

View Helpers位于app/helpers,并提供views的代码片段的重用。

[Rails Guides][http://guides.rubyonrails.org/getting_started.html#view-helpers]

helpers的定义:

module CreditCardHelper
  def masked_credit_card_number(number)
    "XXXX-XXXX-XXXX-" + number[-4..-1]
  end
end

helpers的使用:

<p>
  Thanks for ordering! Your purchase has been billed to your credit card:
  <strong><%= masked_credit_card_number(@credit_card.number) %></strong>
</p>

helpers可能出现问题

  • 大项目中,会出现成的helper
  • 难于组织
  • 不适合复杂的逻辑
  • 感觉怪怪的

此时,需要介绍另一种清理视图的方法,装饰者模式。

Decorator Pattern

[Decorators] 动态的给对象附加责任,其提供了比子类化更灵活的扩展功能的方式。

Design Patterns: Elements of Reusable Object-Oriented Software

装饰者模式的特性:

  • 包装一个对象
  • 透明接口
  • 提取原始对象的方法

使用时机: 表象逻辑与模型的单个实例相关。

这里,可以提供装饰者模式,添加表象的逻辑而不必影响model本身。以下,是其在Ruby中的实现。

Decorator类的实现如下:

class Decorator
  def initialize(component)
    @component = component
  end

  # 使用method_missing方法,减低运行速度,用作父类的实现,不错的实践
  def method_missing(method, *arguments, &block)
    if @component.respond_to?(method)
      @component.send(method, *arguments, &block)
    else
      super
    end
  end

  def respond_to_missing?(method, *)
    @component.respond_to?(method) || super
  end
end

信用卡类的装饰者类:

class CreditCardDecorator < Decorator
  def masked_number
    "XXXX-XXXX-XXXX-" + number[-4..-1]
  end

  # ... other presentational methods
end

实例化装饰者,使用装饰者的关键在于,使用装饰者,而不是原始的对象。

class CreditCardsController < ApplicationController
  def show
    @credit_card = CreditCardDecorator.new(
      current_user.credit_cards.find(params[:id])
    )
  end
end

在视图中,使用装饰者提供的方法:

<p>
  Thanks for ordering! Your purchase has been billed to your credit card:
  <strong><%= @credit_card.masked_number %></strong>
</p>

Draper

Implementing basic decorators is easy, but Draper adds a few helpful features:

实现一个基本的装饰者相当的容易,但是,使用Draper可以获得额外的好处:

  • 可在视图上下文访问
  • 很方便的包装集合
  • 假装成被装饰的对象(在form_for等方法中非常有用)
  • 很容易的装饰关联

复杂视图

独特的或复杂的UI行为,将使得helper迅速膨胀。

复杂视图的例子

<dl class="story-summary">
  <dt>Assigned to</dt>
  <dd>
    <% if @story.assigned_user == current_user %>
      You
    <% else %>
      <%= @story.assigned_user.name %>
    <% end %>
  </dd>
  <dt>Participants</dt>
  <dd><%= @story.participants.reject { |p| p == current_user }.map(&:name).join(", ") %></dd>
</dl>

演示模型(Presentation Model)

演示模型(Presentation Model)的本质是使用独立的类来表示用户界面所有数据和行为,但 不包含如何控制其在界面上的渲染。视图可以简单映射为演示模型的状态(A view then simply projects the state of the presentation model onto the glass.)。

Martin Fowler

该模式和方法的实现,从Backbone中偷学了不少。

视图对象的设计:

class StorySummaryView
  def initialize(template, story, current_user)
    @template = template
    @story = story
    @current_user = current_user
  end

  def assigned_user
    if @story.assigned_user == @current_user
      "You"
    else
      @story.assigned_user.name
    end
  end

  def participant_names
    participants.map(&:name).join(", ")
  end

  def to_s
    @template.render(partial: "story_summary", object: self)
  end

  private

  def participants
    @story.participants.reject { |p| p == @current_user }
  end
end

Story summary模板:

<dl class="story-summary">
  <dt>Assigned to</dt>
  <dd><%= story_summary.assigned_user %></dd>
  <dt>Participants</dt>
  <dd><%= story_summary.participant_names %></dd>
</dl>

设置视图对象的辅助方法:

module StoriesHelper
  def story_summary(story)
    StorySummaryView.new(self, story, current_user)
  end
end

然后,在视图中调用:

<%= story_summary(@story) %>

Form Builders

Rails自带了一些View对象,比如,form_for

注: 原来form_for是视图对象,如何理解Rails中的辅助方法?? 难道,是要去阅读源代码!

form_for

<%= form_for @user do |form| %>
  <div class="form-field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="form-field">
    <%= form.label :email %>
    <%= form.text_field :email %>
  </div>
<% end %>

定制化一个FormBuilder类:

class FancyFormBuilder < ActionView::Helpers::FormBuilder
  def fancy_text_field(attribute, options = {})
    @template.content_tag(:div, class: "form-field") do
      label(attribute) + text_field(attribute, options)
    end
  end
nd

以定制的builder渲染form:

<%= form_for @user, builder: FancyFormBuilder do |form| %>
  <%= form.fancy_text_field :name %>
  <%= form.fancy_text_field :email %>
<% end %>

其他的一些建议:

进一步阅读




傲娇的使用Disqus