清理肥胖导轨助手

发布于 2024-11-30 00:01:59 字数 619 浏览 1 评论 0原文

今天,试图干燥一些代码,我提取了一些重复的 File.exists?将多个辅助方法使用的代码放入私有方法

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

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

我该怎么走?

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

正如我所见,对于意外的猴子补丁来说,没有哪两种方法更安全。也许是一个组合?

代码示例和强烈意见表示赞赏!

Today, trying to DRY up some code, I extracted some duplicate File.exists? code used by several helper methods into a private method

def template_exists?(*template) and accidently monkey patched this already existing Rails helper method. This was a pretty clear indicator of a code smell, I didn't need need any the inherited methods, yet I inherit them. Besides, what was my refactored method doing in this helper at all?

So, this helper does way too much and hence violates SRP (Single Responsibility Principle). I feel that rails helpers are inherently difficult to keep within SRP. The helper I'm looking at is a superclass helper of other helpers, counting 300+ lines itself. It is part of a very complex form using javascript to master the interaction flow. The methods in the fat helper are short and neat, so it's not that awful, but without a doubt, it needs separation of concerns.

How should I go forth?

  1. Separate the methods into many helpers?
  2. Extracting the code inside the helpers methods into classes and delegate to them? Would you scope these classes (i.e. Mydomain::TemplateFinder)?
  3. Separate the logic into modules and list them as includes at the top?
  4. other approaches?

As I see, no 2 is safer wrt accidential monkeypatching. Maybe a combination?

Code examples and strong opinions appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

别靠近我心 2024-12-07 00:01:59

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

可以解决这种清理帮助程序的特定方法我首先挖出了这个旧的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] 

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

Extracting helpers methods into classes (solution n°2)

ok to address this specific way to clean up helpers 1st I dug up this old railscast :

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

At the time it inspired me to create a little tabbing system (working in one of my apps in conjunction with a state machine) :

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

And my views go like this :

  -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] 

As you see it's a nice way to encapsulate the logic in a class and have only one method in the helper/view space

画离情绘悲伤 2024-12-07 00:01:59
  1. 你的意思是把大的助手分成小的助手?为什么不呢。我不太了解您的代码,但您可能需要考虑将大型代码块外包到 ./lib 中。
  2. 不。;-) 这听起来非常复杂。
  3. 听起来也很复杂。与 1 中的建议相同:./lib。如果您访问其中的模块,它们会自动加载。

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

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

确保您了解哪些代码属于

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

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

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

  • 模板内的 Cucumber 步骤定义作为 Ruby 代码:
  • 在模板(或 ./public)内作为 Javascript 代码超级容易阅读:口味问题。但您的应用程序越动态,代码就越有可能属于此处。
  1. You mean seperate large helpers into smaller helpers? Why not. I don't know your code too well, but you might want to consider outsourcing large code chunks into ./lib.
  2. No. ;-) This sounds awfully complex.
  3. Sounds complex too. Same suggestion as in 1.: ./lib. The modules in there are auto-loaded if you access them.
  4. No

My suggestion is: Hesitate from using too many custom structures. If you have large helpers, ok, might be the case. Though I wonder if there is an explanation why that whole helper code is not in the Controller. I use helpers for small and simple methods that are used inside the template. Complex (Ruby-)logic should be put into the Controller. And if you really have such a complex Javascript app, why don't you write this complex stuff in Javascript? If it really has to be called from the template, this is the way to go. And probably makes you website a bit more dynamic and flexible.

Regarding monkey patching and namespace collisions: If you have class name, method names etc. that sound common, check out if they are defined. Google, grep or rails console for them.

Make sure you understand which code belongs to

  • Controller: Fill variables with stuff, perform user actions (basically the computation behind your page)
  • Helper: Help doing simple stuff like creating a fancy hyperlink

    def my_awesome_hyperlink url, text
    "Fancy Link to #{text}"
    end

  • ./lib: More complex stuff that is used by more than one Controller and/or also used directly by other components like Cucumber step definitions

  • inside the Template as Ruby Code: Super easy to read
  • inside the Template (or ./public) as Javascript code: Matter of taste. But the more dynamic your app, the more likely code belongs in here.
独留℉清风醉 2024-12-07 00:01:59

好吧,这是一个很难回答的问题。 Rails 有点引导你走上视图助手的道路,并且当你不再需要它时,它实际上并没有给你一个像样的内置替代方案。

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

我首先阅读 Presenter 模式,并尝试考虑如何将其应用为视图和模型之间的中间层。尽可能简化视图,并将逻辑移至演示者或模型。将 javascript 移出视图,而是在 .js 文件中编写不显眼的 javascript,以增强现有 javascript 的功能。它绝对不需要在视图中,并且您会发现,如果在视图中填充 js 是让您陷入困境的原因,那么这有助于清理很多内容。

以下是一些阅读链接:

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

关于 Rails 中的演示者模式。是更好的方法吗?

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

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

不要太关注具体的代码示例,而是尝试理解该模式试图实现的目标并思考如何可以将其应用于您的具体问题。 (尽管我真的很喜欢上面第二个链接中的示例;堆栈溢出)。

这有帮助吗?

Ok this is a really hard question to answer. Rails kind of leads you down the path of view helpers and really doesn't give you a decent baked-in alternative when you out-grow it.

The fact that helpers are just modules that are included in to the view object doesn't really help with the separation of concerns and coupling. You need to find a way to take that logic out of modules altogether, and find it a home in its own class.

I'd start by reading up on the Presenter pattern, and try to think of how you might be able to apply it as a middle layer between the view and the model. Simplify the view as much as possible, and move the logic to the presenter or the model. Move javascript right out of the view, and instead write unobtrusive javascript in .js files that enhance the functionality of the existing javascript. It definitely does not need to be in the view, and you'll find that helps clean up a lot if stuffing js in your views is what got you in this mess.

Here's some links for reading:

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

Don't get too caught up in the specific code examples, rather try to understand what the pattern is trying to accomplish and think how you could apply it to your specific problem. (though I really like the example in the 2nd link above; the stack overflow one).

Does that help?

我做我的改变 2024-12-07 00:01:59

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

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

就我个人而言,我建议使用 cells gem :单元就像 Rails 的组件,除了轻量级、快速、可缓存之外并且可测试。

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

(声明我不是这个 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

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

It is difficult to suggest a clear solution without the code. However since helper methods all live in the same global view instance, name collision is a common problem.

@Slawosz may be a solution but no really fit for the helpers philosophy.

Personally I would suggest to use the cells gem : cells are like component for rails, except lightweight, fast, cacheable and testable.

Also to answer your specific problem they're completely isolated. When your view helpers get to complex they're definitely the solution.

(disclosure I am not the creator of this gem, just happily using it....)

# 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

Also you can use a generic helper for dryness here without fearing name clash.

已下线请稍等 2024-12-07 00:01:59

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

查看 AbstractController 代码,然后 Metal 到看看导轨的设计有多智能,也许你会找到解决问题的灵感。

I would create a small library which is responsible to manipulate yours forms.Some well named classes, some inheritance, and as input you pass ie parameters, and as output you can has ie. objects for partials used in this form. Everything will be encapsulated, and it will be easy to test.

Look at AbstractController code, and then Metal to see how smart are rails designed, maybe there You will find inspiration how to solve your problem.

贪了杯 2024-12-07 00:01:59

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

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

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

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

Your approach is correct but I would prefer 3rd point i.e. separate the logic into modules.

Modules open up lots of possibilities, particularly for sharing code among more than one class, because any number of classes can mix in the same module.

The greatest strength of modules is that they help you with program design and flexibility.

Using such kind of approach you can achieve design patterns.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文