在我的控制器中包含.rb文件中的代码的好方法是什么?

时间:2014-11-17 16:21:35

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

我有一个对象FooFoo有一个控制器,控制器有一个标准索引,可以显示包含每个Foo属性的表中的所有Foo,并允许用户对Foo进行过滤和排序1}}由这些属性。这部分只是花花公子。

class FoosController < ApplicationController
    def index
        @foos = Foo.filter_data_by_params
        @foos = Foo.sort_some_data_by_params

        respond_to do |format|
            format.html { standard_index } # Has link to format xls
            format.xls do
                require_relative '../path/to/spreadsheet_view.rb' # Uh oh.
                create_spreadsheet # method in spreadsheet_view that generates and returns xls file
            end
        end
    end
end

但另一部分对我来说似乎很危险。索引视图还为用户提供了将收集的索引数据视为电子表格的选项。但是,因为尝试使用XLS ERB返回XML 2004文件而不是与我们公司的工作流程兼容的97-2004文件,所以我必须调用使用Spreadsheet gem的常规.rb文件来生成97-2004兼容的电子表格而不是.erb文件。

def create_spreadsheet
    book = Spreadsheet::Workbook.new
    sheet = book.create_worksheet(:name => 'Sheet1')

    fields = Foo.columns

    sheet.row(0).concat(fields)

    @foos.each_with_index do |foo, index|
        fields.each do |field|
            sheet.row(index + 1).push foo.field
        end
    end

    spreadsheet = StringIO.new
    book.write spreadsheet
    send_data spreadsheet.string, :filename => "foos.xls", :type => "application/vnd.ms-excel"
end

现在,我不确定使用外部.rb文件而不是.erb文件本身是如此糟糕,但我确实认识到require_relative这可能是一种可怕的方式。

如果我错了,请纠正我,但根据我的理解,require_relative每个Rails会话只能使用一次(与任何Ruby require一样),并定义&#34 ;创建电子表格&#34;关于整个应用程序范围的方法,而不仅仅是我使用FoosController的当前实例。

tl;博士:我需要的是一种向FoosController#index公开方法的方法,该方法可以使用@foos填充的index变量来生成电子表格,就像查看文件一样会不会搞砸其他所有东西 - 也就是说,它可以在不重新加载服务器的情况下进行编辑,并且不会污染我应用程序的其余部分。

1 个答案:

答案 0 :(得分:0)

通常在Rails 4中用Concerns处理。你可以通过声明一个模块并使用它来在Rails 3中实现类似的效果:

module SpreadsheetCreator
  def create_spreadsheet
    # ...
  end
end

只要将其命名为spreadsheet_creator.rb且位于ActiveSupport自动加载器可以找到的路径中,您就不需要做任何特殊的事情来包含它。 app/helpersapp/models应该是其中一个,即使它不够理想。对于像这样的非模型非控制器助手,Rails 4有一个concerns位置。

然后在另一个背景下:

class FoosController < ApplicationController
  include SpreadsheetCreator
end

这是一个非常草率的设计,因为你的创造者真的很想进入控制器并对控制器的结构做出很多假设。例如,@foos没有明确的依赖关系,它只是神奇地传播。在单独的实现文件之间共享这样的实例变量会使对代码的理解变得复杂。

更好的是制作一个没有假设的自包含辅助类,而不是明确定义的输入和输出:

class SpreadsheetCreator
  def initialize(foos)
    @foos = foos
  end

  def string
    book = Spreadsheet::Workbook.new
    sheet = book.create_worksheet(name: 'Sheet1')

    fields = Foo.columns

    sheet.row(0).concat(fields)

    @foos.each_with_index do |foo, index|
      fields.each do |field|
        sheet.row(index + 1).push foo.field
      end
    end

    spreadsheet = StringIO.new
    book.write spreadsheet

    spreadsheet.string
  end
end

然后你重写的控制器有一个明确的交接,并保持对控制器本身输出内容的完全控制:

 respond_to do |format|
   format.html { standard_index } # Has link to format xls
   format.xls do
     spreadsheet = SpreadsheetCreator.new(@foos)

     send_data spreadsheet.string, filename: "foos.xls", type: "application/vnd.ms-excel"
   end
 end

将责任交给某个神秘模块通常是你反对谷物的标志。