2010-06-06 39 views
16

XML構建器中的部分被證明是不平凡的。Ruby on Rails:使用XML Builder Partial

經過一番初步谷歌搜索,我發現下面的工作,雖然它不是100%

xml.foo do 
    xml.id(foo.id) 
    xml.created_at(foo.created_at) 
    xml.last_updated(foo.updated_at) 
    foo.bars.each do |bar| 
     xml << render(:partial => 'bar/_bar', :locals => { :bar => bar }) 
    end 
end 

這將這樣的伎倆,除了XML輸出不正確縮進。輸出看起來類似於:

<foo> 
    <id>1</id> 
    <created_at>sometime</created_at> 
    <last_updated>sometime</last_updated> 
<bar> 
    ... 
</bar> 
<bar> 
    ... 
</bar> 
</foo> 

<bar>元素應該對準<last_updated>元件下方,它是<foo>像這樣的孩子:

<foo> 
    <id>1</id> 
    <created_at>sometime</created_at> 
    <last_updated>sometime</last_updated> 
    <bar> 
    ... 
    </bar> 
    <bar> 
    ... 
    </bar> 
</foo> 

偉大的作品,如果我從欄中複製內容/ _bar.xml.builder放入模板中,但事情並不是乾的。

+0

這個問題就解決了Rails中4 – hcarreras 2014-07-28 13:14:16

回答

16

不幸的是沒有一個直接的解決方案。在查看ActionPack將初始化Builder對象的代碼時,縮進大小將被硬編碼爲2,而邊距大小未設置。令人遺憾的是,目前沒有任何機制可以覆蓋這一點。

這裏的理想解決方案是修復ActionPack以允許將這些選項傳遞給構建器,但這需要一些時間投入。我有2個可能的修復程序給你。既髒你可以選擇你覺得不太髒。

將部分呈現渲染修改爲字符串,然後對其執行一些正則表達式。這應該是這樣的

_bar.xml.builder

xml.bar do 
    xml.id(bar.id) 
    xml.name(bar.name) 
    xml.created_at(bar.created_at) 
    xml.last_updated(bar.updated_at) 
end 

FOOS/index.xml.builder

xml.foos do 
    @foos.each do |foo| 
    xml.foo do 
     xml.id(foo.id) 
     xml.name(foo.name) 
     xml.created_at(foo.created_at) 
     xml.last_updated(foo.updated_at) 
     xml.bars do 
     foo.bars.each do |bar| 
      xml << render(:partial => 'bars/bar', 
       :locals => { :bar => bar }).gsub(/^/, '  ') 
     end 
     end 
    end 
    end 
end 

注意GSUB在渲染行的末尾。這將產生以下結果

<?xml version="1.0" encoding="UTF-8"?> 
<foos> 
    <foo> 
    <id>1</id> 
    <name>Foo 1</name> 
    <created_at>2010-06-11 21:54:16 UTC</created_at> 
    <last_updated>2010-06-11 21:54:16 UTC</last_updated> 
    <bars> 
     <bar> 
     <id>1</id> 
     <name>Foo 1 Bar 1</name> 
     <created_at>2010-06-11 21:57:29 UTC</created_at> 
     <last_updated>2010-06-11 21:57:29 UTC</last_updated> 
     </bar> 
    </bars> 
    </foo> 
</foos> 

這是一個有點哈克,肯定很骯髒,但有被包含在您的代碼中的優勢。接下來的解決辦法是猴子補丁ActionPack的得到Builder實例的工作,我們所希望的方式

配置/初始化/ builder_mods.rb

module ActionView 
    module TemplateHandlers 
    class BuilderOptions 
     cattr_accessor :margin, :indent 
    end 
    end 
end 

module ActionView 
    module TemplateHandlers 
    class Builder < TemplateHandler 

     def compile(template) 
     "_set_controller_content_type(Mime::XML);" + 
      "xml = ::Builder::XmlMarkup.new(" + 
      ":indent => #{ActionView::TemplateHandlers::BuilderOptions.indent}, " + 
      ":margin => #{ActionView::TemplateHandlers::BuilderOptions.margin});" + 
      "self.output_buffer = xml.target!;" + 
      template.source + 
      ";xml.target!;" 
     end 
    end 
    end 
end 

ActionView::TemplateHandlers::BuilderOptions.margin = 0 
ActionView::TemplateHandlers::BuilderOptions.indent = 2 

這造成在Rails的初始化稱爲BuilderOptions一個新的類其唯一目的是爲縮進和保證金提供2個值(儘管我們只需要保證金值)。我曾嘗試將這些變量作爲類變量直接添加到Builder模板類中,但該對象被凍結,我無法更改這些值。

一旦創建了這個類,我們在TemplateHandler中修改編譯方法以使用這些值。然後

模板如下所示: -

xml.foos do 
    @foos.each do |foo| 
    xml.foo do 
     xml.id(foo.id) 
     xml.name(foo.name) 
     xml.created_at(foo.created_at) 
     xml.last_updated(foo.updated_at) 
     xml.bars do 
     ActionView::TemplateHandlers::BuilderOptions.margin = 3  
     foo.bars.each do |bar| 
      xml << render(:partial => 'bars/bar', :locals => { :bar => bar }) 
     end 
     ActionView::TemplateHandlers::BuilderOptions.margin = 0 
     end 
    end 
    end 
end 

的基本思想是設定臨界值的縮進級別,我們是渲染局部當。生成的XML與上面顯示的相同。

請不要複製/粘貼此代碼,也不要根據您的Rails版本進行檢查,以確保它們來自相同的代碼庫。 (我認爲上面是2.3.5)

+0

我喜歡猴子補丁的路線,雖然一個警告:升級軌也可能會中斷這個補丁或新功能。 – 2010-06-14 13:28:30

+0

事實上,我認爲這在Rails3中不起作用(不看),這就是爲什麼我評論說這是針對2.3.5的。很遺憾,沒有一種直接的方法來鉤住這種行爲,因爲我已經看到其他人希望將縮進設置爲0,這也將解決它們的情況。 – 2010-06-14 16:27:38

0

也許你應該做的:

xml.foo do 
    xml.id(foo.id) 
    xml.created_at(foo.created_at) 
    xml.last_updated(foo.updated_at) 
    xml.bars do 
    foo.bars.each do |bar| 
     xml.bar bar.to_xml # or "xml.bar render(:xml => bar)" 
         # or "xml.bar render(bar)" (loads bar/_bar partial) 
    end 
    end 
end 

看一看this link about the xml builder

在最後的選擇,你可以用更換內環:

xml.bars render(foo.bars) # will loop over bars automatically using bar/_bar 

你或許可以搜索:

xml << foo.to_xml(:include => :bars) 

,如果你想在結果所有領域。

我不確定所有這些的縮進情況,因此您可能需要回退以創建內部循環的內容,就像在外部程序段中一樣,例如不使用部分內容。

28

我通過在部分中傳入構建器引用作爲本地來解決此問題。不需要修補猴子。使用原始示例:

xml.foo do 
    xml.id(foo.id) 
    xml.created_at(foo.created_at) 
    xml.last_updated(foo.updated_at) 
    foo.bars.each do |bar| 
     render(:partial => 'bar/_bar', :locals => {:builder => xml, :bar => bar }) 
    end 
end 

然後在您的部分中,確保使用「builder」對象。

builder.bar do 
    builder.id bar.id 
end 
+1

不錯,謝謝Alex!一個警告:確保您重命名本地哈希中的xml變量。我正在做':locals => {:xml => xml}',那不行。 – ipd 2012-11-15 00:43:43

+6

這應該是公認的答案! – rewritten 2014-01-17 17:15:57

+2

這是導軌4的正確答案! – 2014-10-28 11:27:45