Ruby on Rails: Usando Pariales de XML Builder
ruby-on-rails xml-builder (3)
Lamentablemente no hay una solución directa para esto. Cuando se mira el código con el que ActionPack inicializará el objeto Builder, el tamaño de la sangría está codificado en 2 y el tamaño del margen no está establecido. Es una pena que no haya ningún mecanismo para anular esto en este momento.
La solución ideal aquí sería una solución a ActionPack para permitir que estas opciones se pasen al constructor, pero esto requeriría una inversión de tiempo. Tengo 2 soluciones posibles para usted. Ambos sucios puedes elegir el que se siente menos sucio.
Modifique la representación del parcial para representar en una cadena y luego haga un poco de expresión regular en ella. Esto se vería así
_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
Tenga en cuenta el gsub al final de la línea de procesamiento. Esto produce los siguientes resultados.
<?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>
Eso es un poco pirateado y definitivamente bastante sucio, pero tiene la ventaja de estar contenido dentro de su código. La siguiente solución es Monkey-patch ActionPack para hacer que la instancia de Builder funcione como queremos.
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
Esto crea una nueva clase en la inicialización de Rails llamada BuilderOptions cuyo único propósito es alojar 2 valores para sangría y margen (aunque realmente solo necesitamos el valor de margen). Intenté agregar estas variables como variables de clase directamente a la clase de plantilla de Builder, pero ese objeto estaba congelado y no pude cambiar los valores.
Una vez que se crea esa clase, parcheamos el método de compilación dentro de TemplateHandler para usar estos valores.
La plantilla se ve como sigue:
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
La idea básica es establecer el valor del margen en el nivel de sangría en el que estamos al procesar el parcial. El XML generado es idéntico al que se muestra arriba.
No copie / pegue este código sin compararlo con su versión de Rails para asegurarse de que sea del mismo código base. (Creo que lo anterior es 2.3.5)
Los parciales en el constructor de XML están demostrando ser no triviales.
Después de algunas búsquedas iniciales en Google, encontré lo siguiente para funcionar, aunque no es 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
esto hará el truco, excepto que la salida XML no está correctamente sangrada. La salida se ve algo similar a:
<foo>
<id>1</id>
<created_at>sometime</created_at>
<last_updated>sometime</last_updated>
<bar>
...
</bar>
<bar>
...
</bar>
</foo>
El elemento <bar>
debe alinearse debajo del elemento <last_updated>
, es un elemento secundario de <foo>
así:
<foo>
<id>1</id>
<created_at>sometime</created_at>
<last_updated>sometime</last_updated>
<bar>
...
</bar>
<bar>
...
</bar>
</foo>
Funciona bien si copio el contenido de bar / _bar.xml.builder en la plantilla, pero las cosas simplemente no están SECAS.
Tal vez deberías hacer:
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
Echa un vistazo a este enlace sobre el generador de XML .
En la última alternativa podría reemplazar el bucle interno con:
xml.bars render(foo.bars) # will loop over bars automatically using bar/_bar
Probablemente también puedes probar:
xml << foo.to_xml(:include => :bars)
Si desea incluir todos los campos en el resultado.
No estoy seguro de la sangría de todos estos, por lo que es posible que deba retroceder para crear el contenido del bucle interno de la misma manera que lo hace en el bloque externo, por ejemplo, sin usar parcial.
Trabajé alrededor de esto pasando la referencia del constructor como local en el parcial. No se necesitan parches de mono. Usando el ejemplo original:
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
Luego, en su parcial, asegúrese de usar el objeto ''constructor''.
builder.bar do
builder.id bar.id
end
Además, lo anterior parece funcionar solo hasta Rails 4. Rails 5 y hasta ver el comentario de @ srghma a continuación