我想使用Builder根据ActiveRecord模型表构建一组XML文件。我有近一百万行,所以我需要使用find_each(batch_size: 5000)
迭代记录并为每批记录写一个XML文件,直到记录耗尽为止。如下所示:
filecount = 1
count = 0
xml = ""
Person.find_each(batch_size: 5000) do |person|
xml += person.to_xml # pretend .to_xml() exists
count += 1
if count == MAX_PER_FILE
File.open("#{filecount}.xml", 'w') {|f| f.write(xml) }
xml = ""
filecount += 1
count = 0
end
end
这对于Builder的界面效果不佳,因为它想要在块中工作,如下所示:
xml = builder.person { |p| p.name("Jim") }
一旦块结束,Builder关闭其当前节;你不能保持对p的引用并在块之外使用它(我试过)。基本上,Builder希望“拥有”迭代。
为了使这个与构建器一起工作,我必须做类似的事情:
filecount = 0
offset = 0
while offset < Person.count do
count = 0
builder = Builder::XmlMarkup.new(indent: 5)
xml = builder.people do |people|
Person.limit(MAX_PER_FILE).offset(offset).each do |person|
people.person { |p| p.name(person.name) }
count += 1
end
end
File.open("#output@file_count.xml", 'w') {|f| f.write(xml) }
filecount += 1
offset += count
end
有没有办法在没有块语法的情况下使用Builder?有没有办法以编程方式告诉它“关闭当前节”而不是依赖于块?
答案 0 :(得分:1)
根据Larry K的建议,我最终手动生成了XML。 Ruby的内置XML编码使这件事变得轻而易举。我不确定为什么这个功能没有得到更广泛的宣传......我浪费了很多时间谷歌搜索和尝试各种to_xs
实现之前我偶然发现内置"foo".encode(xml: :text)
。
我的代码现在看起来像:
def run
count = 0
Person.find_each(batch_size: 5000) do |person|
open_new_file if @current_file.nil?
# simplified- I actually have many more fields and elements
#
@current_file.puts " <person>#{person.name.encode(xml: :text)}</person>"
count += 1
if count == MAX_PER_FILE
close_current_file
count = 0
end
end
close_current_file
end
def open_new_file
@file_count += 1
@current_file = File.open("people#{@file_count}.xml", 'w')
@current_file.puts "<?xml version='1.0' encoding='UTF-8'?>"
@current_file.puts " <people>"
end
def close_current_file
unless @current_file.nil?
@current_file.puts " </people>"
@current_file.close
@current_file = nil
end
end
答案 1 :(得分:1)
我的建议:不要使用建设者。
只要正确地转义xml实体,XML就是一种简单的格式。
批处理数据库检索,然后将批处理作为xml写入文件句柄。如示例所示,不要通过字符串缓冲。只需写入文件句柄即可。让操作系统处理缓冲。文件可以是任何大小,为什么限制?
另外,不要包含百万行的缩进空格,它们会加起来。
<强>加强> 在编写xml文件时,我还在文件顶部包含了xml注释: