2010-06-06 69 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