ran*_*its 17 ruby xml ruby-on-rails xml-builder
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
Run Code Online (Sandbox Code Playgroud)
这将解决问题,除了XML输出没有正确缩进.输出看起来类似于:
<foo>
<id>1</id>
<created_at>sometime</created_at>
<last_updated>sometime</last_updated>
<bar>
...
</bar>
<bar>
...
</bar>
</foo>
Run Code Online (Sandbox Code Playgroud)
该<bar>元素应该在下面排列<last_updated>的元素,它是一个孩子<foo>是这样的:
<foo>
<id>1</id>
<created_at>sometime</created_at>
<last_updated>sometime</last_updated>
<bar>
...
</bar>
<bar>
...
</bar>
</foo>
Run Code Online (Sandbox Code Playgroud)
如果我将bar/_bar.xml.builder中的内容复制到模板中,那么效果很好,但事情就是不干.
Ale*_*oto 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
Run Code Online (Sandbox Code Playgroud)
然后在你的部分确保使用'builder'对象.
builder.bar do
builder.id bar.id
end
Run Code Online (Sandbox Code Playgroud)
此外,上面似乎只适用于Rails 4. Rails 5及以上@ srghma的评论如下
Ste*_*eet 17
遗憾的是,这并不是一个直截了当的解决方案.在查看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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
注意渲染线末尾的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>
Run Code Online (Sandbox Code Playgroud)
这有点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
Run Code Online (Sandbox Code Playgroud)
这在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
Run Code Online (Sandbox Code Playgroud)
基本思想是将边距值设置为渲染部分时的缩进级别.生成的XML与上面显示的相同.
请不要复制/粘贴此代码而不检查Rails版本,以确保它们来自相同的代码库.(我认为以上是2.3.5)