18

XML 构建器中的部分被证明是不平凡的。

经过一些初步的谷歌搜索,我发现以下工作,虽然它不是 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/_bar.xml.builder 中的内容复制到模板中,效果很好,但是事情并没有干燥。

4

3 回答 3

32

我通过将构建器引用作为局部中的本地传递来解决此问题。无需猴子修补。使用原始示例:

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

此外,上述内容似乎仅适用于 Rails 4。Rails 5 及更高版本请参阅下面的@srghma 评论

于 2011-06-03T01:15:27.177 回答
20

不幸的是,对此没有直接的解决方案。当查看 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>

这有点 hacky 并且肯定很脏,但具有包含在您的代码中的优势。下一个解决方案是猴子补丁 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 中的 compile 方法以使用这些值。

然后模板如下所示:-

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)

于 2010-06-12T01:48:01.220 回答
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

看看这个关于 xml builder 的链接

在最后一种选择中,您可以将内部循环替换为:

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

您可能还可以尝试:

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

如果您想在结果中包含所有字段。

我不确定所有这些的缩进,因此您可能需要回退以创建内部循环的内容,就像在外部块中一样,例如不使用部分。

于 2010-06-11T18:57:05.160 回答