Rails 5:从控制器中提取动作

时间:2018-07-17 16:43:28

标签: ruby-on-rails ruby dry abstraction ruby-on-rails-5.1

RoR 5.1.4。我的应用程序由MongoDB via Mongoid支持,许多模型具有类似的处理方式。我正在尝试使其尽可能地受数据驱动,这意味着查询Mongoid的模型和Rails的路线,由于急切的加载和尚未完成的路线,在初始化器中都无法轻松地完成这两项设定。模式信息(控制器/模型关系,模型字段,)存储在单个位置,并且使 发生并且可以从类和实例环境访问PITA,但是那是背景。

大多数控制器支持两种上传操作,一种通过表单文件规范进行通过,另一种通过请求内容主体通过通过。我分别对它们使用webuploadupload动作,由于它们总是相同的(差异是通过检查上述模式信息来确定的),因此我想抽象这两种动作方法,及其相关的之前/之后/ ..关系定义,放在一个单独的模块中。

控制器的关注点无法实现OOTB,因为没有在继承中声明回调( eg :before_action和朋友),而且我无法弄清楚哪个模块我需要include来解决这个问题。

助手之所以退出,是因为它们已不被包含在控制器中(您需要采取额外的步骤来获取它们),并且无论如何它们主要是用于视图。

那么编写/放置/包括专门用于控制器,模型和测试的模块的模式是什么?那些功能具有适当的方法继承吗? (例如, :before_action用于控制器,:validates用于模型,依此类推。)

RoR是功能和挂钩的丰富宝库,但是我发现很难对它应用抽象和DRY模式,这仅仅是因为它如此如此丰富。

感谢您的帮助或指示!

[EDIT]有人建议我加入一些代码。因此,这里是我为解决控制器问题而做的尝试的简短摘录。

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:

1 个答案:

答案 0 :(得分:3)

您已经完全误解了关于ActiveSupport::Concern以及模块mixin如何工作的所有内容。

让我们开始使用合成来分离问题。例如:

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