Partials в разработчике XML оказываются нетривиальными.
После некоторого начального поиска Google я нашел, что следующее работало, хотя это не 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 в шаблон, но затем вещами просто не является DRY.
К сожалению, нет однозначного решения этой проблемы. При просмотре кода, которым ActionPack инициализирует объект Builder, размер отступа жестко задан равным 2, а размер поля не установлен. Жалко, что в настоящее время нет механизма, позволяющего это преодолеть.
Идеальным решением здесь было бы исправление ActionPack, позволяющее передавать эти параметры разработчику, но это потребует некоторых временных затрат. У меня есть 2 возможных решения для вас. Оба грязные, вы можете выбрать, что кажется менее грязным.
Измените рендеринг партиала для рендеринга в строку, а затем примените к нему Regex. Это будет выглядеть так
_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 работал так, как мы хотим
config / initializers / 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)
Может быть, вам стоит сделать:
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 .
В последнем варианте вы можете заменить внутренний цикл на:
xml.bars render(foo.bars) # will loop over bars automatically using bar/_bar
Возможно, вы также можете попробовать:
xml << foo.to_xml(:include => :bars)
, если хотите включить все поля в результат.
Я не уверен насчет отступа всех этих элементов, поэтому вам может потребоваться отступить, чтобы создать содержимое внутреннего цикла так же, как и во внешнем блоке, например, без партиала.