使用持久导入模型

时间:2017-09-20 04:13:57

标签: ruby-on-rails ruby csv import ruby-on-rails-5

过去几天我一直在努力为用户创建上传CSV / Excel文件的导入过程。我一直试图模仿这个Railscast剧集中完成的过程。但是,我的要求有点复杂,所以我一直在尝试修改已经完成的工作,但是在执行模型验证以确保不存在任何不良数据方面我遇到了障碍。

我正在尝试使用Roo来让我的用户能够上传CSV或Excel文件。我还希望保存 OrderImport模型,其中包含已上传的电子表格和其他一些信息,以供历史参考,以及不是。

模型

property.rb
  has_many :owners
  has_many :orders

owner.rb
  belongs_to :property

order.rb
  belongs_to :property
  has_many :owners, through: :property

order_import.rb
  #no assications
  #uses paperclip to save the attached file

order_import.rb

中的订单导入流程
def load_imported_orders(file, cid, uid) #loads file from controller along with client_id and user_id for use in order creation
  spreadsheet = Roo::Spreadsheet.open(file.path)
  header = spreadsheet.row(1)
  order_imports = (2..spreadsheet.last_row).map do |i|
    row = Hash[[header, spreadsheet.row(i)].transpose]
    property = row["address"]
    propparse = import_address_parse(property) #to parse submitted address
    newprop = if Property.exists?(propparse)
                Property.find_by(propparse)
              else
                Property.new(propparse)
              end
    owner = row["owner"]
    newowner = Owner.find_or_initialize_by(name: owner)
    newprop.owners << newowner #creates new property/owner association
    neworder = Order.new(property_id: newprop.id,
                         client_id: cid,
                         task: "Property Report",
                         submitted_by: uid)
    neworder
    newprop
    newowner
  end
  @imported_orders = order_imports
end

def import_address_parse(property)
  address = StreetAddress::US.parse(property)
  if address.blank?
    new_blank_prop = { address1: nil,
                       city: nil,
                       state: nil,
                       zipcode: nil }
  else
    new_prop = { address1: address.to_s(:line1),
                 city: address.city,
                 state: address.state_name,
                 zipcode: address.postal_code}
  end
end

def save
  if @imported_orders.map(&:valid?).all?
    @imported_orders.each(&:save!)
    true
  else
    @imported_orders.each_with_index do |order, index|
      if order.instance_of?(Property) #needed to reduce the amount of errors shown on the form
        errors.add :base, "Row #{index+2}: Please re-check the Address of your Property."
      else
        order.errors.full_messages.each do |message| #shows any errors not related to property
          errors.add :base, "Row #{index+2}: #{message} | #{order}"
        end
      end
    end
    false
  end
end

order_import controller

def create
  @order_import = OrderImport.new(params[:order_import_params])
  @order_import.load_imported_orders(params[:order_import][:document], params[:order_import][:client_id], params[:order_import][:submitted_by])
  if @order_import.save
    redirect_to external_client_orders_path(current_user.client_id), notice: "Imported Orders successfully."
  else
    render :new
  end
end    

如您所见,从控制器调用load_imported_orders()方法并解析电子表格。然后找到property & owner(如果它们恰好存在)或初始化,然后从该行中的项初始化order。 (我已尝试在:before_create模型中使用类似OrderImport过滤器的内容,但我不知道这些不同的指南如何打开通过OrderImport.new(params[:order_import_params])传递的文件而不先保存模型 - 并且当然我不想保存,除非一切都正确输入。)

import_address_parse方法的目的是因为地址作为一行提交(555 test rd,testington,tt,55555),但在我的数据库中由address1, city, state, zipcode组成。如果StreetAddress gem无法解析整个地址字符串,那么Rails API docs)将返回nil,因此我将那个catch放在那里以返回一个nil对象,希望在{{1}时失败property模型验证使用这些字段中的所有nil值初始化。

问题

由于某些原因,Property.new验证不会自行失败,我只会在Property保存并与owners中的new property相关联时收到提醒方法。所有nil属性的save仍会因某种原因加载Property.exists?(),而不是初始化property而我无法终身找出原因。

Property.new验证失败,因为order尚未保存,因为property.id尚未保存,我明白了,但我不确定如何创建{{1}来自new property方法的关联/创建(即在解析导入数据的Order方法之外)。

我认为我对导入批量记录的整个验证方面的理解是根本错误,尤其是因为即使添加save我也无法正确保存load_import_orders记录({{3}}到我的OrderImport方法,它会覆盖模型的默认rails save方法。

这可能比我要做的更容易,但我认为自己仍然是一个业余的Rails开发人员(通过教程,SO帖子,指南/文章教自己一切)并潜入我的想法这是一个复杂的方法论,所以如果有人可以花时间协助我完善这个过程,这样我就可以实现我的目标,并在一般情况下更深入地了解rails。

添加警告,如果我将所有create_or_update调用更改为save并且仅使用我知道会通过所有验证的数据,那么此过程按预期工作,但我们都知道这在现实中是不现实的世界环境。我认为,因为那部分工作让我不再重新设计这一点(如果使用内置导轨find_or_initialize_by / new而不是Roo,那么这不是世界的终点,而是我要提供的方式但是我想提供可以选择从excel导入,但不是必需的。)

感谢任何花时间打破这个并帮助我的人。

1 个答案:

答案 0 :(得分:1)

所以在发布这个没有回复的问题之后,我决定利用我的Ruby IRB技能来完成整个过程,并分析每一步的数据。 经过多次反复试验,我终于完成了这项任务!

简要介绍我的最终流程:

按照此SO问题的答案方法Ruby on Rails: Validate CSV file,我决定

  1. 将验证分解为自己的Ruby类。
  2. 使用Roo's Parse method,我将每一行作为哈希传递给OrderImportValidator.new()
  3. 通过我的row[:property]方法传递import_address_parse对。
  4. 使用row[:property] && row[:owner]方法将.find_or_initialize_by实例化为各自的模型。
  5. 将每个:property:owner个对象配对到OrderImportValidator实例变量(@order_objects)中的哈希值,并允许从我的OrderImport模型访问它通过一种方法(非常类似于链接的SO CSV问题中返回@errors的方式)。
  6. 验证返回哈希中所有值(实例化对象)的验证。
  7. 如果一切都很好,那么我就为每个人打了save,为每对创建了正确的关联,并且适当order
  8. 为我的validate :import_error_check, on: :create模型添加了自定义OrderImport
  9. 运行each.with_index,检查是否存在错误,清除对象上的错误,并将{}自定义消息添加到errors[:base] Order Import及其相应的index并显示在页面上。
  10. 相当抽水我想通了,并进一步增强了我对数据操作,对象和ruby语法的了解。也许我的过程大纲有一天会帮助别人。欢呼声。