QA@IT

RubyでXMLに対応したクラスを扱うときのベストプラクティス

3987 PV

以下の2つのことをやりたいとします。

  • XMLが返ってくるAPIで得られたレスポンスを使ってXMLの構造に対応したクラスを生成できる
  • クラスを自分でnewしていろいろパラメータを設定したあとto_xmlでXML文字列にできる

xmlのざっくりした構造を書くとこんな感じものを想定しています。

<?xml version="1.0" encoding="utf-8" ?>
<feed xmlns="">
  <entry>
    ..
  </entry>
  <entry>
    ..
  </entry>
  ..
</feed>

この内、繰り返し要素であるentryに相当するEntryのようなクラスを用意して次のような形で使うのはどうかと考えています。

class Entry
  def self.from_xml(xml)
    # initialize from xml
  end

  def to_xml
    # generate xml string 
  end
end

entry = Entry.from_xml("<entry>..</entry>")

# edit entry then convert to xml string
entry.to_xml

前置きが長くてすみませんが、ここからが質問です。

このような場合に適していると思われる実装はどういったものがあるでしょうか?もしくは、XMLを扱うならクラスにしないほうがいいなどそもそもの考え方がずれていたらご指摘いただけたらと思います。

自分で考えたのは以下の2つの方法ですがどう考えてもイマイチにしか思えませんでした。

  • 全てのフィールド(?)に対応する変数を用意する、@titleなど。 → 実装が冗長になる
  • XMLをハッシュに変換しそのハッシュを持つ。→値の出し入れがすごく大変

回答

用途次第だと思いますが、私はRubyのクラスにマッピングする方が良いと思います。

これがベストかどうかは分かりませんが、Railsのコンポーネントの一部であるactivemodelを利用するのはどうでしょうか?
XMLとRubyオブジェクトを相互変換する機能があり、単体のライブラリとして利用する事ができます。

サンプルで書くとこんな感じでしょうか。

require 'active_model'

class Entry
  include ActiveModel::Model
  include ActiveModel::Serializers::Xml

  attr_accessor :title, :body, :foo

  def attributes=(hash)
    hash.each do |key, value|
      instance_variable_set("@#{key}", value) # インスタンス変数を動的に定義する
    end
  end

  def attributes
    {"title" => nil, "body" => nil, "foo" => nil}
  end
end

entry = Entry.new("title" => "EntryTitle", "body" => "EntryBody")
puts entry.to_xml
# <?xml version="1.0" encoding="UTF-8"?>
# <entry>
#   <title>EntryTitle</title>
#   <body>EntryBody</body>
#   <foo nil="true"/>
# </entry>


xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<entry>
  <title>Title</title>
  <body>Body</body>
</entry>
XML

new_entry = Entry.new
p new_entry.from_xml(xml)
# => #<Entry:0x007ff8f3848a98 @title="Title", @body="Body">

attributesattributes=はこのライブラリが要求している規約です。それぞれfrom_xmlメソッドとto_xmlメソッドの内部で利用されます。
詳しくはリファレンスに説明があります。

上記の例ではアクセサメソッドは普通に定義していますが、method_missingdefine_methodを組み合わせる事でもっと動的に属性値を定義する事は可能です

ただ、あるRailsが想定している形に縛られてしまうので、上手く噛み合わない場合は無理に使わない方が良いでしょう。

自分でパーサを準備する場合も、このサンプルの様な形で動的にインスタンス変数を定義したりmethod_missingdefine_methodを使えば、ある程度冗長な記述を削減する事ができると思います。

もしXMLをパースする場合はNokogiriというライブラリを利用する事が多いですね。

編集 履歴 (0)
  • ありがとうございます、とてもよさそうです。早速試してみます! -
  • 追記:
    ちょっとミスがありました。
    このサンプルだとActiveModel::Modelのincludeは必要無かったですね。失礼しました。
    -
  • 試してみてわかったのですが中で使われているHash.from_xmlのときにXMLのattributeが抜けてしまうのですね。attributeを何とか使えるようにNokogiriを使って似たようなアプローチで作ってみるのが良さそうな気がしています。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。