Rails 5:从控制器中抽象出动作

Rails 5: Abstracting actions out of controllers

RoR 5.1.4。我的应用程序由 MongoDB via Mongoid 支持,许多模型都有类似的处理。我试图让它尽可能地成为数据驱动的,这意味着查询 Mongoid 模型和 Rails 路线,由于急切的加载和路线,这两者都不能在初始化程序中轻松完成尚未设置。架构信息(controller/model 关系、模型字段、 存储在单个位置,并使 that 发生并成为可从 class 访问,实例环境已成为 PITA。但是,那是背景。

大多数控制器支持两种上传操作,一种是via 表单文件规范,另一种是via 请求内容主体。我分别对这些使用 webuploadupload 操作,并且由于它们的处理方式始终相同(差异是通过检查上述模式信息确定的),我想抽象出这两种操作方法,及其相关的 before/after/.. 关系定义,放入单独的模块中。

控制器问题不会在 OOTB 时执行,因为回调(例如:before_action 和朋友)没有在继承中声明,我可以'不知道我需要 include 关注什么模块才能得到它们。

Helpers 已被弃用,因为它们已被控制器包含所弃用(您需要采取额外的步骤来获取它们),而且它们主要用于视图。

那么 writing/placing/including 专门用于控制器、模型和测试的模块的模式是什么?那些功能的方法有适当的继承吗? (例如:before_action 控制器,:validates 模型等。)

RoR 是一个丰富的功能和钩子宝库,但我发现很难将抽象和 DRY 模式应用于它只是因为它 太丰富了。

感谢任何帮助或指点!

[编辑] 有人建议我包含一些代码。所以这里是我尝试通过控制器问题进行此操作的简短摘录。

module UploadAssistant

  extend ActiveSupport::Concern
  #
  # Code to execute when something does a `include ControllerAssistant`.
  #
  included do
    #
    # Make the application's local storage module more easily
    # accessible, too.
    #
    unless (self.const_defined?('LocalStore'))
      self.const_set('LocalStore', ::PerceptSys::LocalStore)
    end

    def set_title_uploading
      title.base        = 'Uploading records'
      title.add(model_info.friendly)
    end                         # def set_base_title

    #+
    # Supply these routes to the controllers so they needn't define
    # them.
    #

    #
    # GET /<model>/upload(.:format)
    #
    def webupload
    end                         # def webupload

    #
    # POST /<model>/upload(.:format)
    #
    def upload
      title.add('Results')
      @uploads          = {
        :success                => {},
        :failure                => {},
      }
      errors            = 0
      @upload_records.each do |record|
        #
        # Stuff happens here.
        #
      end
      successes         = @uploads[:success].count
      failures          = @uploads[:failure].count
      respond_to do |format|
        format.html {
          render(:upload,
                 :status   => :ok,
                 :template => 'application/upload.html')
        }
        format.json {
          render(:json     => @uploads)
        }
      end
    end

    def upload_file_params
      if (params[:file])
        params.require(:file).require(:upload)
        colname         = model_info.collection
        file_id         = params[:file][:upload]
        #
        # Get the file contents.
        #
      end
      @upload_records   = params.delete(model_info.collection.to_sym)
    end                         # def upload_file_params

    def upload_params
      @upload_records   = params.require(model_info.collection.to_sym)
    end                         # def upload_params

    def set_file_upload
      file_id           = params.require(:file).require(:upload)
      #
      # Read/decompress the file.
      #
      data              = JSON.parse(data)
      params[model_info.collection] = data[model_info.collection]
    end                         # def set_file_upload

  end                           # included do

  #+
  # Insert here any class methods we want added to our including class
  # or module.
  #
  class_methods do

    #
    # Stuff relating specifically to bulk uploading.
    #
    before_action(:set_title_uploading,
                  :only => [
                    :upload,
                    :webupload,
                  ])
    before_action(:set_file_upload,
                  :only => [
                    :upload,
                  ])
    before_action(:upload_params,
                  :only => [
                    :upload,
                  ])

  end                           # class_methods do

end                             # module ControllerAssistant

# Local Variables:
# mode: ruby
# eval: (fci-mode t)
# End:

你完全误解了关于 ActiveSupport::Concern 的几乎所有内容以及模块 mixins 的工作原理。

让我们从使用组合来分离关注点开始。例如:

module LocalStorage
  extend ActiveSupport::Concern
  class_methods do
    # use a memoized helper instead of a constant
    # as its easier to stub for testing
    def local_storage
      @local_storage ||= ::PerceptSys::LocalStore
    end
  end
end

提取到单独的关注点是有意义的,因为它是一种可重用的行为。

然后我们可以写一个Uploadable问题:

module Uploadable
  # we compose modules by extending
  extend ActiveSupport::Concern
  extend LocalStorage 

  # put instance methods in the module body

  # GET /<model>/upload(.:format)
  def webupload
      # ...
  end

        #
  # POST /<model>/upload(.:format)
  #
  def upload
    # ...

  end

  # don't abuse use a callback for this - just use a straight 
  # method that returns a value and preferably does not have side effects
  def upload_params
    # ...
  end

  # ...

  # use "included" to hook in the class definition
  # self here is the singleton class instance
  # so this is where you put callbacks, attr_accessor etc
  # which would normally go in the class defintion
  included do
    before_action(:set_title_uploading,
              :only => [
                :upload,
                :webupload,
              ])
    before_action(:set_file_upload,
              :only => [
                :upload,
              ])
  end

  # just use class_methods for actual class_methods!
  class_methods do
     # for example to derive the name of a model from the controller name
     def resource_class_name
       controller_name.singularize
     end

     def resource_class
       @resource_class ||= resource_class_name.classify.constantize
     end
  end
end