【问题标题】:Clean up fat rails helpers清理脂肪轨助手
【发布时间】:2011-10-28 02:37:55
【问题描述】:

今天,试图干掉一些代码,我提取了一些重复的 File.exists?多个辅助方法使用的代码转换为私有方法

def template_exists?(*template) 并意外修补了这个已经存在的 Rails 辅助方法。这是代码异味的一个非常明显的指标,我不需要任何继承的方法,但我继承了它们。此外,我重构的方法在这个助手中做了什么?

所以,这个助手做得太多,因此违反了 SRP(单一责任原则)。我觉得 Rails 助手本质上很难保留在 SRP 中。我正在查看的助手是其他助手的超类助手,它本身就有 300 多行。它是一个非常复杂的表单的一部分,使用 javascript 来掌握交互流程。 fat helper 中的方法短小精悍,所以没有那么糟糕,但毫无疑问,它需要关注点分离。

我应该怎么去?

  1. 将方法分成多个助手?
  2. 将辅助方法中的代码提取到类中并委托给它们?您会确定这些类的范围(即 Mydomain::TemplateFinder)吗?
  3. 将逻辑分成模块并将它们列为包含在顶部?
  4. 其他方法?

正如我所见,没有 2 更安全 wrt 意外的猴子补丁。也许是一个组合?

感谢代码示例和强烈的意见!

【问题讨论】:

    标签: ruby-on-rails ruby single-responsibility-principle


    【解决方案1】:

    好的,这是一个很难回答的问题。 Rails 有点引导你走上视图助手的道路,但当你超越它时,它真的没有给你一个像样的烘焙替代品。

    帮助器只是包含在视图对象中的模块这一事实并不能真正帮助分离关注点和耦合。您需要找到一种方法将这些逻辑完全从模块中取出,并在自己的类中找到它的归宿。

    我将从阅读 Presenter 模式开始,并尝试考虑如何将它应用为视图和模型之间的中间层。尽量简化视图,把逻辑移到presenter或者model。将 javascript 移出视图,而是在 .js 文件中编写不显眼的 javascript,以增强现有 javascript 的功能。它绝对不需要出现在视图中,如果在视图中填充 js 是让你陷入困境的原因,你会发现这有助于清理工作。

    这里有一些阅读链接:

    http://blog.jayfields.com/2007/03/rails-presenter-pattern.html

    About presenter pattern in rails. is a better way to do it?

    http://blog.jayfields.com/2007/01/another-rails-presenter-example.html

    http://blog.jayfields.com/2007/09/railsconf-europe-07-presenter-links.html

    不要太拘泥于特定的代码示例,而是尝试了解该模式试图完成的任务,并思考如何将其应用于您的特定问题。 (虽然我真的很喜欢上面第二个链接中的示例;堆栈溢出一个)。

    这有帮助吗?

    【讨论】:

    • 谢谢。我喜欢 Presenter 的抽象,虽然用 Presenter 后缀是多余的,让我想起了 java。 js 不显眼,它隐藏/显示一个巨大表单的部分。构建这个巨大的表格需要很多帮助。我觉得文章中介绍的Presenter是混合角色的,除了帮助视图之外,它还做了controller+model应该处理的事情。演示者似乎不适合视图助手,因为助手抽象 html 标记而不是注入动态值。不过,我可能弄错了。无论如何都应该点赞。
    【解决方案2】:

    将辅助方法提取到类中(解决方案 n°2)

    可以解决这种清理助手的特定方法 1st 我挖出了这个旧的 railscast :

    http://railscasts.com/episodes/101-refactoring-out-helper-object

    当时它启发了我创建一个小标签系统(在我的一个应用中与状态机一起工作):

    module WorkflowHelper
    
      # takes the block  
      def workflow_for(model, opts={}, &block)
        yield Workflow.new(model, opts[:match], self)
        return false
      end
    
      class Workflow
        def initialize(model, current_url, view)
          @view = view
          @current_url = current_url
          @model = model
          @links = []
        end
    
        def link_to(text, url, opts = {})
          @links << url
          url = @model.new_record? ? "" : @view.url_for(url)
          @view.raw "<li class='#{active_class(url)}'>#{@view.link_to(text, url)}</li>"
        end
    
      private
        def active_class(url)
          'active' if @current_url.gsub(/(edit|new)/, "") == url.gsub(/(edit|new)/, "") ||
                     ( @model.new_record? && @links.size == 1 )
        end
    
      end #class Workflow
    end
    

    我的观点是这样的:

      -workflow_for @order, :match => request.path do |w|
        = w.link_to "✎ Create/Edit an Order", [:edit, :admin, @order]
        = w.link_to "√ Decide for Approval/Price", [:approve, :admin, @order]
        = w.link_to "✉ Notify User of Approval/Price", [:email, :admin, @order]
        = w.link_to "€ Create/Edit Order's Invoice", [:edit, :admin, @order, :invoice] 
    

    如您所见,这是一种将逻辑封装在一个类中并且在帮助程序/视图空间中只有一个方法的好方法

    【讨论】:

    • 过去的爆炸!惊人的!那是一个很棒的截屏视频,即使有一些不错的元编程。我将创建像 helpers/somedomain/ 这样的子目录,以使其井井有条并保持 helpers 目录的清洁。
    • 你无法击败瑞恩贝茨 :-) !
    【解决方案3】:
    1. 你的意思是把大帮手分成小帮手?为什么不。我不太了解您的代码,但您可能需要考虑将大型代码块外包到 ./lib。
    2. 没有。 ;-) 这听起来非常复杂。
    3. 听起来也很复杂。与 1 中的建议相同:./lib.如果您访问其中的模块,它们会自动加载。
    4. 没有

    我的建议是:不要使用过多的自定义结构。如果你有大帮手,好吧,可能就是这种情况。虽然我想知道是否有解释为什么整个帮助代码不在控制器中。我将帮助器用于模板内使用的小而简单的方法。复杂的(Ruby-)逻辑应该放入控制器中。如果你真的有这么复杂的 Javascript 应用程序,为什么不用 Javascript 编写这些复杂的东西呢?如果真的必须从模板中调用它,这就是要走的路。并且可能使您的网站更加动态和灵活。

    关于猴子补丁和命名空间冲突:如果您有听起来很常见的类名、方法名等,请检查它们是否已定义。为他们提供谷歌、grep 或 rails 控制台。

    确保您了解属于哪个代码

    • 控制器:用东西填充变量,执行用户操作(基本上是页面背后的计算)
    • Helper:帮助做一些简单的事情,比如创建一个精美的超链接

      def my_awesome_hyperlink 网址,文本 “#{text} 的精美链接” 结束

    • ./lib:由多个控制器使用和/或也由其他组件(如 Cucumber 步骤定义)直接使用的更复杂的东西

    • 在模板中作为 Ruby 代码:超级易读
    • 在模板(或 ./public)内作为 Javascript 代码:品味问题。但是您的应用越动态,代码就越有可能出现在此处。

    【讨论】:

    • 胖控制器不是胖助手的好替代品......令我惊讶的是,没有人建议某种“2.”模式。在我看来,这是要走的路。可测试的单一职责类。但是,随着代码库的增长,我想了解如何在项目中构建此代码的提示。顺便说一句,我们使用 js (backbone) 来处理表单向导中的复杂性、隐藏和显示步骤。助手并不复杂,但数量众多。
    • 什么是胖控制器? 100行代码,200、500、2000?请向我们提供有关您的项目的更多详细信息。你有多少个控制器,有多少个路由,有多少个控制器方法,有多少个 helper,controller/view/helpers/lib 中有多少行代码。我很难理解为什么你最终会拥有“胖控制器”。
    • 顺便说一句...也许您可以将您的路线配置文件粘贴到文件夹中。目前我看到两种可能性:1)你试图过度构建你的项目并且你不开心,因为你有一个超过 100 行代码的控制器。 2)您有两个控制器,您将所有新路由放入同一个控制器中。控制器中唯一的功能是路由处理功能。
    【解决方案4】:

    没有代码很难提出明确的解决方案。然而,由于辅助方法都存在于同一个全局视图实例中,名称冲突是一个常见问题。

    @Slawosz 可能是一个解决方案,但并不真正适合助手哲学。

    我个人建议使用 cells gem :单元格就像轨道的组件,除了轻量级、快速、可缓存和可测试。

    还要回答您的具体问题,它们是完全隔离的。当您的视图助手变得复杂时,它们绝对是解决方案。

    (披露我不是这个宝石的创造者,只是很高兴地使用它......)

    # some view
    = render_cell :cart, :display, :user => @current_user
    
    # cells/cart_cell.rb
    # DO whatever you like in here
    class CartCell < Cell::Rails
    
      include SomeGenericHelper
    
      def display(args)
        user    = args[:user]
        @items  = user.items_in_cart
    
        render  # renders display.html.haml
      end
    end
    

    您也可以在这里使用通用助手来干燥,而不必担心名称冲突。

    【讨论】:

      【解决方案5】:

      我会创建一个小型库,负责操作您的表单。一些命名良好的类,一些继承,以及作为输入传递 ie 参数,作为输出,您可以使用 ie。在这种形式中使用的部分对象。一切都会被封装起来,而且很容易测试。

      AbstractController代码,然后看Metal,看看rails的设计有多智能,说不定你会找到解决问题的灵感。

      【讨论】:

        【解决方案6】:

        您的方法是正确的,但我更喜欢第三点,即将逻辑分成模块。

        模块开辟了许多可能性,特别是在多个类之间共享代码时,因为任意数量的类可以混合在同一个模块中。

        模块的最大优势在于它们可以帮助您进行程序设计和灵活性。

        使用这种方法,您可以实现设计模式。

        【讨论】:

        • 您介意展示代码和结构的示例吗?你会如何组织?
        • 投反对票,因为您没有回复我的评论。答案不是很有用,也没有提供任何好的见解。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-19
        • 1970-01-01
        • 1970-01-01
        • 2020-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多