【问题标题】:where to put helper methods for controllers only?仅在哪里放置控制器的辅助方法?
【发布时间】:2012-08-14 10:41:23
【问题描述】:

我希望编写某些方法来处理字符串,以及在我的许多控制器中执行的其他任务。我知道在控制器中包含助手是不好的做法,所以我只是想知道,在哪里放置控制器中使用的应用程序范围的方法的最佳位置?

我知道你们中的一些人会说将它们放入模型中,但您必须意识到并非我的所有控制器都有关联的模型。任何和所有输入将不胜感激。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3


    【解决方案1】:

    我倾向于将它们放入助手中。它们包含在视图中的事实 自动对我来说不是问题。您也可以将它们放入 app/concerns/ 或 lib/ 之类的东西

    我不喜欢用私有方法弄乱 ApplicationController 因为这通常会变得一团糟。

    例子:

    module AuthenticationHelper
      def current_user
        @current_user # ||= ...
      end
    
      def authenticate!
        redirect_to new_session_url unless current_user.signed_in?
      end
    end
    
    module MobileSubdomain
      def self.included(controller)
        controller.before_filter :set_mobile_format
      end
    
      def set_mobile_format
        request.format = :mobile if request.subdomain == "m"
      end
    end
    
    class ApplicationController
      include AuthenticationHelper
      include MobileSubdomain
    end
    

    【讨论】:

    • 我也是这样做的。
    • 这对我来说比将每个辅助方法都塞进应用程序控制器更有意义。
    【解决方案2】:

    如果您需要在应用程序范围内使用方法,那么我建议您将这些方法保留在应用程序控制器中,以便在视图中使用它们。将它们声明为辅助方法。

    例如,

    class ApplicationController < ActionController::Base
     helper_method :current_user, :some_method
    
     def current_user
       @user ||= User.find_by_id(session[:user_id])
     end
    
     def some_method
     end
    end
    

    【讨论】:

    • 问题是如果有很多辅助方法,ApplicationController 会变得笨拙。我更喜欢按类别将方法分解为帮助文件,并包括 ApplicationController 中的文件,正如 Semyon Perepelitsa 的回答中所建议的那样。
    【解决方案3】:

    我建议将它们放在lib 文件夹中。所以例如我有:

    lib/utils/string_utils
    
    module StringUtils
    
      def foo
         ...
      end
    end
    
    class BarController < ActionController::Base
      include StringUtils
    end
    

    这展示了称为Fat model,Thin controller的好方法,在这种情况下,我们使用Mixins而不是Models来分离逻辑,但想法是一样的。您希望您的控制器尽可能简单。

    【讨论】:

      【解决方案4】:

      这一切都取决于您的需求。我将在这里提供 2 个示例。它们都只是一个自定义库,位于lib 目录下。

      第一个例子——“自定义字符串处理”

      # lib/filters.rb
      module Filters
      
        # Converts value to canonical view
        def self.phone(value)
          # remove all non-digits
          clean_value = value.gsub(/\D/, '')
      
          country_codes = configus.phone.country_codes
          area_code = configus.phone.defaults.area_code
      
          case clean_value.length
            when 7
              "#{area_code}#{clean_value}"
            when 11
              # remove country code only if phone starts with the allowed country code
              if country_codes.include?(clean_value[0].to_i)
                clean_value[1..-1]
              else
                clean_value
              end
            else clean_value
          end
        end
      
      # usage
      # app/api/phones_controller.rb
      class Api::PhonesController < Api::ApplicationController
      
        def exists
          if params[:q]
            clean_value = Filters.phone(params[:q])
            ...
          end
        end
      end
      

      第二个例子 - flash 消息的助手

      # lib/flash_helper.rb
      module FlashHelper
        def flash_translate(key, options = {})
          scope = [:flash, :controllers]
          scope += params[:controller].split('/')
          scope << params[:action]
      
          t(key, {:scope => scope}.merge(options))
        end
      end
      
      # app/application_controller.rb
      class ApplicationController < ActionController::Base
        include FlashHelper
      end
      
      # usage
      # app/your_controller.rb
      class YourController < ApplicationController
      
        def create
          @object = Object.new(params[:object])
      
          if @object.save
            flash[:success] = flash_translate(:success)
            ...
          end
        end
      end
      

      注意:不要忘记将lib 目录添加到自动加载路径中。在config/application.rb 添加/修改这一行config.autoload_paths += %W(#{config.root}/lib)。 所以对我来说答案是lib 目录。

      【讨论】:

        【解决方案5】:

        从 Rails 4 开始,有一个专用文件夹 app/controllers/concerns。因此,您可以在那里创建一个模块,然后将其包含在特定的控制器中,或者如果您需要它在所有控制器中都可用,则包含在 ApplicationController 中。

        【讨论】:

          【解决方案6】:

          如果这些方法在许多控制器中使用,我会在application_controller.rb 中定义它们。每个控制器都将从它继承,并且能够使用那里定义的任何方法

          【讨论】: