18

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/_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.bar do
  builder.id bar.id
end

また、上記はRails4までしか機能しないようです。Rails5以降は以下の@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>

これは少しハッキーで間違いなくかなり汚いですが、コードに含まれているという利点があります。次の解決策は、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は、上記の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ビルダーに関するこのリンクをご覧ください。

最後の方法では、内部ループを次のように置き換えることができます。

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 に答える