【问题标题】:Rails: Refactoring, views, helpers: how does it all go together?Rails:重构、视图、助手:它们是如何结合在一起的?
【发布时间】:2011-01-12 18:40:07
【问题描述】:

警告:这里是菜鸟。

我知道这是一个微不足道的主题,但我很难弄清楚如何通过将部分视图移入助手来简化我的视图。例如,我一直读到您观点中的条件是提取到帮助器的主要候选者,但我真的找不到这方面的例子,而且我试图实现这一点的尝试失败了。

例如,假设我有:

#index.html.erb

<% for beast in @beasts do -%>
  <% if beast.dead? -%>
    <%= beast.body %>
    <%= link_to "bury", bury_beast_path( :id => beast.id ) %>
  <% else -%>
    <%= beast.body %>
    <%= link_to "kill!", kill_beast_path( :id => beast.id ) %>
  <% end -%>
<% end -%>

在我看来,这让我有点恼火,但我究竟怎么能把它移到一个助手身上呢?如果可能的话,进一步简化它。 (我在某处读到条件语句很糟糕,但我无法在没有它们的情况下编写任何程序。)

另一个例子:我需要id我的body标签,格式为controller_action。到目前为止,我得到的最好的是:

#index.html.erb

<body id="<%= controller_action %>">

……和……

#application_helper.rb

def controller_action
  @id = @controller.controller_name + "_" + @controller.action_name
end

我不是专家,但这对我来说仍然很难看。

为了让事情变得更复杂,Ryan Singer said something I liked:将 ERB 视为图像标签,使用助手“揭示意图”。然后在接下来的呼吸中说你不应该在帮助程序中使用 HTML,因为那是通往地狱的道路。怎么回事?两者如何兼容?如果到了可以在视图中声明行为的地步,那么肯定应该有很多 HTML 需要在幕后渲染吗?看不懂。

所以,基本上就是这样。如果有人能就此分享一些想法,或者指出我对这个主题的一些深入阅读,我将不胜感激——我发现它在网络上的报道非常薄弱。我已经用谷歌搜索了它,但谁知道呢。

【问题讨论】:

  • 小注。使用 for 很好,但使用迭代器更符合 Ruby 约定,例如,&lt;% @beasts.each do |beast| %&gt;
  • @Sarah:谢谢。只是我真的很喜欢使用for 的自然语言方面。

标签: ruby-on-rails refactoring views helper


【解决方案1】:

第三种选择是使用来自Cells gem 的视图模型。这是一个非常流行的框架,它为 Rails 中的视图层带来了面向对象。

# app/cells/beast/cell.rb

class Beast::Cell < Cell::Concept
  def show
    return dead if model.dead?
    kill
  end

private
  def dead
    link_to "bury", bury_beast_path( :id => model.id ) 
    # you could render a view here, too!
  end

  def kill
    link_to "kill!", kill_beast_path( :id => model.id )
  end
end

然后您使用帮助器(在视图或控制器中)呈现视图模型。

# app/views/beasts/index.erb

<%= concept(:beast, @beast).call %>
<%-# this returns the link content %>

就是这样!您可以在单独的测试中测试此隔离单元。单元格还为您提供视图渲染、视图继承和更多功能。

例如,您可以为kill 链接使用视图。

# app/cells/beast/cell.rb

class Beast::Cell < Cell::Concept

  # ..

  def kill
    render :kill
  end
end

这会呈现单元格的杀手级视图。

# app/cells/beast/views/index.erb

<%= link_to "kill!", kill_beast_path( :id => model.id ) %>

注意视图的位置,它很好地打包到单元目录中。

而且,是的,单元格可以执行 HAML 和 AbstractController 支持的任何其他模板引擎。

【讨论】:

    【解决方案2】:

    另一个策略是根本不使用模板和帮助程序。 对于渲染,您可以:

    1. 使用 render(:inline => ) 直接从控制器渲染视图。如果您仍想保持视图和控制器正式分离,您可以创建模块/mixin,将其包含在控制器中。
    2. 或创建您自己的视图类并使用它们来呈现您的响应。

    这背后的想法是 helpers 和 rails erb 模板系统不利用 OOP,因此最终您无法定义您将根据每个控制器/请求的需要专门化的一般行为;通常情况下,最终会重写看起来非常相似的代码块,从维护的角度来看,这不是很好。

    如果您仍然需要一些辅助方法(例如 form_tag、h、raw...),您只需将它们包含在您的控制器/专用视图类中。

    请参阅:rails-misapprehensions-helpers-are-shit 以获得有趣但有用的文章。

    编辑:听起来不像是一个完整的冲洗器,我想说实现这取决于您的应用程序应该有多大,以及您需要多久更新一次代码。另外,如果您将设计委托给非程序员,他/她很可能会在深入研究您的代码之前参加一些编程课程,诚然,与模板语法相比,这些课程更难理解。

    【讨论】:

      【解决方案3】:

      我要做的第一件事是:

      #index.html.erb
      <%= render @beasts %>
      
      #_beast.html.erb
      <%= beast.body %>
      <%= link_to_next_beast_action(beast) %>    
      
      #beast_helper.rb
      def link_to_next_beast_action(beast)
        if beast.dead?
          link_to "bury", bury_beast_path( :id => beast.id ) 
        else
          link_to "kill!", kill_beast_path( :id => beast.id )
        end
      end
      

      我所做的是将野兽的渲染分离为使用集合语义的部分。

      然后,我将显示杀死/埋葬链接的逻辑移到了野兽助手中。这样,如果您决定添加另一个动作(例如,'bring back from dead'),您只需更改您的助手。

      这有帮助吗?

      【讨论】:

      • 您甚至可以使用render @beasts,因为它会意识到这是一个集合。
      • 看来您已经完成了与我相同的重构。我在写我的时候没有注意到新的答案。
      • @EmFi 我对你的答案投了赞成票,因为你更详细地了解了何时使用 partials/helpers。
      • 我很感激,但没有必要。我已经过了回答声誉问题的阶段。
      • 即便如此,你还是值得被认可的=)
      【解决方案4】:

      重构使您的视图更易于维护。问题在于选择重构代码的去向。

      您的两个选择是 partialshelpers。没有一成不变的规则规定应该在哪里使用。有几条准则漂浮在各处,例如声明助手不应包含 HTML 的准则。

      通常,partials 更适合于重构 HTML/ERB/H​​AML 而非 ruby​​ 的部分。另一方面,Helper 用于具有最少 HTML 或从参数生成简单 HTML 的 ruby​​ 代码块。

      但是,我不同意助手根本不应该包含 HTML 的观点。一点点就好了,不要过度。处理助手的方式阻碍了它们用于生成大量 HTML。这就是为什么建议您的助手包含最少量的 HTML 的原因。如果您查看 Rails 附带的帮助程序的源代码,您会注意到它们中的大多数都生成 html。少数没有,主要用于生成参数和评估常见条件。

      例如,任何形式的帮助程序或 link_to 变体都适合第一种形式的帮助程序。而像url_for和logged_in这样的东西?由各种身份验证模型提供的属于第二种。

      这是我用来确定是将视图中的代码分解为部分代码还是辅助函数的决策链。

      1. 重复或几乎相同的语句产生单个浅 html 标记? => 帮手。
      2. 用作另一个助手参数的通用表达式? => 帮手。
      3. 长表达式(超过 4 个术语)用作另一个助手的参数? => 帮手。
      4. 4 行或更多行 ruby​​(未评估为 HTML)? => 帮手。
      5. 几乎所有其他内容 => 部分。

      我将以您要重构的代码为例:

      我会以这种方式重构问题中的视图:

      app/helpers/beast_helper.rb:

      def beast_action(beast)
        if beast.dead?
          link_to "bury", bury_beast_path(beast)
        else
          link_to "kill!", kill_beast_path(beast)
        end
      end
      

      app/views/beasts/_beast.html.erb:

      <%= beast.body %>
      <%= beast_action(beast) %>
      

      app/views/beasts/index.html.erb:

      <%= render :partial => "beast", :collection => @beasts %>
      

      它在技术上更复杂,因为它是 3 个文件,总共 10 行,而不是 1 个文件和 10 行。这些视图现在只有 3 行组合,分布在 2 个文件中。最终结果是您的代码更加干燥。允许您在其他控制器/动作/视图中重用部分或全部,而增加的复杂性最小。

      至于您的身体标签 ID。你真的应该使用content_for/yield。对于那种事。

      app/views/layouts/application.html.erb

      ...
      <body id="<%= yield(:body_id) %>">
      ...
      

      app/views/beasts/index.html.erb

      <% content_for :body_id, controller_action %>
      ...
      

      这将允许您在任何需要它的视图中覆盖 body 的 id。例如:

      app/views/users/preferences.html.erb

      <% content_for :body_id, "my_preferences" %>
      

      【讨论】:

      • 只要部分在beasts/_beast.html.erb,你就可以做到render @beasts
      • @Garrett:这只适用于 Rails 2.3 或更高版本。我不知道为什么新用户会使用旧版本。但我觉得那个版本更安全。
      • 好点,Rails 3 即将推出,我认为开始推送 render @beasts 是安全的
      • 谢谢!您的回答非常清楚;我会经常回来检查您建议的决策链,直到它沉入其中。我仍然需要尝试您的代码,但我想它会起作用。它当然看起来好多了。再次感谢。
      猜你喜欢
      • 2020-02-08
      • 2015-03-16
      • 2011-07-16
      • 1970-01-01
      • 2011-06-27
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2020-12-20
      相关资源
      最近更新 更多