2012-06-25 19 views
2

我想使用Builder根據ActiveRecord模型表構建一組XML文件。我有近一百萬行,所以我需要使用find_each(batch_size: 5000)遍歷記錄併爲每批記錄寫一個XML文件,直到記錄耗盡。像下面這樣:Ruby/Builder API:不使用塊創建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 

這不會生成器的接口很好地工作,因爲它要在塊的工作,像這樣:

xml = builder.person { |p| p.name("Jim") } 

一旦塊結束時,生成器將關閉其當前的節;你不能保留對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("#[email protected]_count.xml", 'w') {|f| f.write(xml) } 
    filecount += 1 
    offset += count 
    end 

是否有使用生成器,而不塊語法的方法嗎?有沒有辦法通過編程的方式告訴它「關閉當前節」而不是依賴塊?

+2

請勿使用構建器。只要您正確地轉義xml實體,XML就是一種簡單的格式。批量你的數據庫檢索,然後只需將該批處理寫入xml文件句柄。如示例所示,不要通過字符串進行緩衝。只需寫入文件句柄。讓操作系統處理緩衝。文件可以是任何大小,爲什麼是限制?另外,不要在100行內加入縮進空格,它們會加起來。 –

+0

你有一點 - 建築師在這一點上增加了更多的痛苦,而不是緩解。剛剛發現了Ruby的編碼(:xml =>:text)方法,這真的讓它變得簡單。至於極限 - 我正在生成站點地圖,必須將其分解爲<50MB的塊。還有其他的業務要求,使文件塊,而不是一個大的文件。 –

+0

由於上傳和使用它,我正在提交我的評論作爲答案。見下文。謝謝。 –

回答

1

我的建議:不要使用生成器。

只要您正確地轉義xml實體,XML就是一種簡單的格式。

批處理您的數據庫檢索然後只寫出批處理爲XML到文件句柄。如示例所示,不要通過字符串進行緩衝。只需寫入文件句柄。讓操作系統處理緩衝。文件可以是任何大小,爲什麼是限制?

另外,不要在100行中包含縮進空格,它們會加起來。

新增 當編寫XML文件,我還需要在文件的頂部XML註釋:

  • 生成XML文件
  • 日期的軟件和版本的名稱/時間戳文件寫入
  • 其他有用的信息。例如,在這種情況下,您可以說該文件是原始數據集的批次#x。
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