5

我正在创建一个将 CSV 文件导入多个表的导入功能。我制作了一个名为的模块CsvParser,它解析 CSV 文件并创建记录。接收创建操作的模型扩展了CsvParser. 他们调用CsvParser.create并传递正确的属性顺序和一个可选的 lambda,称为value_parser. 此 lambda 将散列中的值转换为首选格式。

class Mutation < ActiveRecord::Base
  extend CsvParser

  def self.import_csv(csv_file)
    attribute_order = %w[reg_nr receipt_date reference_number book_date is_credit sum balance description]

    value_parser = lambda do |h|
      h["is_credit"] = ((h["is_credit"] == 'B') if h["is_credit"].present?)
      h["sum"] = -1 * h["sum"].to_f unless h["is_credit"]
      return [h]
    end

    CsvParser.create(csv_file, self, attribute_order, value_parser)    
  end
end

我在CsvParser.create方法内部使用 lambda 而不是检查的原因是 lambda 就像属于该模型的业务规则。

我的问题是我应该如何测试这个 lambda。我应该在模型还是 CsvParser 中测试它?我应该测试 lambda 本身还是方法数组的结果self.import?也许我应该制作另一个代码结构?

我的 CsvParser 如下所示:

require "csv"

module CsvParser

  def self.create(csv_file, klass, attribute_order, value_parser = nil)
    parsed_csv = CSV.parse(csv_file, col_sep: "|")

    records = []    

    ActiveRecord::Base.transaction do
      parsed_csv.each do |row|
        record = Hash.new {|h, k| h[k] = []}      
        row.each_with_index do |value, index|
          record[attribute_order[index]] = value
        end 
        if value_parser.blank?
          records << klass.create(record)
        else
          value_parser.call(record).each do |parsed_record|
            records << klass.create(parsed_record)
          end
        end
      end 
    end
    return records
  end

end

我正在测试模块本身:需要'spec_helper'

describe CsvParser do

  it "should create relations" do
    file = File.new(Rails.root.join('spec/fixtures/files/importrelaties.txt'))
    Relation.should_receive(:create).at_least(:once)
    Relation.import_csv(file).should be_kind_of Array 
  end

  it "should create mutations" do
    file = File.new(Rails.root.join('spec/fixtures/files/importmutaties.txt'))
    Mutation.should_receive(:create).at_least(:once)    
    Mutation.import_csv(file).should be_kind_of Array
  end

  it "should create strategies" do
    file = File.new(Rails.root.join('spec/fixtures/files/importplan.txt'))
    Strategy.should_receive(:create).at_least(:once)
    Strategy.import_csv(file).should be_kind_of Array
  end

  it "should create reservations" do
    file = File.new(Rails.root.join('spec/fixtures/files/importreservering.txt'))
    Reservation.should_receive(:create).at_least(:once)
    Reservation.import_csv(file).should be_kind_of Array
  end

end
4

1 回答 1

4

一些有趣的问题。几点注意事项:

  1. 您可能不应该在 lambda 中返回。只做最后一个陈述[h]。
  2. 如果我正确理解了代码,则 lambda 的第一行和第二行过于复杂。减少它们以使它们更具可读性和更容易重构:

    h["is_credit"] = (h['is_credit'] == 'B') # I *think* that will do the same
    h['sum'] = h['sum'].to_f   # Your original code would have left this a string
    h['sum'] *= -1 unless h['is_credit']
    
  3. 看起来你的 lambda 不依赖于任何外部(除了h),所以我会单独测试它。你甚至可以使它成为一个常数:

    class Mutation < ActiveRecord::Base
      extend CsvParser    # <== See point 5 below
    
      PARSE_CREDIT_AND_SUM = lambda do |h|
        h["is_credit"] = (h['is_credit'] == 'B') 
        h['sum'] = h['sum'].to_f
        h['sum'] *= -1 unless h['is_credit']
        [h]
      end
    
  4. 如果不知道基本原理,很难说应该把这段代码放在哪里。我的直觉是这不是 CSV 解析器的工作(尽管一个好的解析器可能会检测浮点数并将它们从字符串转换?)保持你的 CSV 解析器可重用。(注意:重读,我想你自己已经回答了这个问题——它是业务逻辑,与模型相关。跟着你的直觉走!)

  5. 最后,您正在定义和方法CsvParser.create。您无需扩展 CsvParser 即可访问它,但如果您在 CsvParser 中有其他设施,请考虑创建CsvParser.create一个名为 create_from_csv_file 之类的普通模块方法

于 2012-04-11T14:11:15.767 回答