过去几天我一直在努力为用户创建上传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导入,但不是必需的。)
感谢任何花时间打破这个并帮助我的人。
答案 0 :(得分:1)
所以在发布这个没有回复的问题之后,我决定利用我的Ruby IRB技能来完成整个过程,并分析每一步的数据。 经过多次反复试验,我终于完成了这项任务!
简要介绍我的最终流程:
按照此SO问题的答案方法Ruby on Rails: Validate CSV file,我决定
Parse
method,我将每一行作为哈希传递给OrderImportValidator.new()
row[:property]
方法传递import_address_parse
对。row[:property] && row[:owner]
方法将.find_or_initialize_by
实例化为各自的模型。:property
和:owner
个对象配对到OrderImportValidator
实例变量(@order_objects
)中的哈希值,并允许从我的OrderImport
模型访问它通过一种方法(非常类似于链接的SO CSV问题中返回@errors
的方式)。save
,为每对创建了正确的关联,并且适当order
。validate :import_error_check, on: :create
模型添加了自定义OrderImport
。each.with_index
,检查是否存在错误,清除对象上的错误,并将{}自定义消息添加到errors[:base]
Order Import
及其相应的index
并显示在页面上。相当抽水我想通了,并进一步增强了我对数据操作,对象和ruby语法的了解。也许我的过程大纲有一天会帮助别人。欢呼声。