Search code examples
ruby-on-railsxmlactiveresource

How Can I Make ActiveResource XML Parsing More Consistent?


I'm using ActiveResource to consume a REST webservice provided by Redmine (a bug-tracking tool). That webservice produces XML like the following:

<custom_field name="Issue Owner" id="15">Fred Fake</custom_field> 
<custom_field name="Needs Printing" id="16">0</custom_field> 
<custom_field name="Review Assignee" id="17">Fran Fraud</custom_field> 
<custom_field name="Released On" id="20"></custom_field> 
<custom_field name="Client Facing" id="21">0</custom_field> 
<custom_field name="Type" id="22">Bug</custom_field> 
<custom_field name="QA Assignee" id="23"></custom_field> 
<custom_field name="Company Name" id="26"></custom_field> 
<custom_field name="QA Notes" id="27"></custom_field> 
<custom_field name="Failed QA Attempts" id="28">2</custom_field> 

However, when ActiveResource parses that, and I iterate through the results printing them out, I get:

Fred Fake
0
Fran Fraud
#<Redmine::Issue::CustomFields::CustomField:0x5704e95d>
0
Bug
#<Redmine::Issue::CustomFields::CustomField:0x32fd963>
#<Redmine::Issue::CustomFields::CustomField:0x3a68f437>
#<Redmine::Issue::CustomFields::CustomField:0x407964d6>
2

That's right, it throws out all of the attribute info from anything with a value, but keeps the attribute info from the empty elements.

Needless to say, this makes things rather difficult when you're trying to find the value for id 15 (or whatever). Now I can reference things by their position, but that's very brittle, because those elements are likely to change in the future. I assume there has to be some way to make ActiveResource keep the attribute info, but since I'm not doing anything special.

(My ActiveResource extension is just five lines long: it extends ActiveResource, defines the url, username and password of the service, and that's it).

So, does anyone know how I can make ActiveResource not parse this XML so strangely?


Solution

  • This is a known issue with ActiveResource apparently:

    https://github.com/rails/rails/issues/588

    Unfortunately, nothing appears to be done about it & the issue was closed. If you're feeling up to it, the Rails 3 code for updating ActiveResource and Hash.from_xml to preserve all attributes are all in the gist below and you could create a tailored version in your Redmine module to fix it:

    https://gist.github.com/971598

    Update:

    An alternative, as it appears ActiveResource will not be part of Rails 4 core and will be spun out as a separate gem, would be to use an alternative ORM for REST APIs, like Her.

    Her allows you to use a custom parser for your XML. This is an example custom parser called Redmine::ParseXML:

    https://gist.github.com/3879418

    So then all you need to do is create a file like config/initializers/her.rb:

    Her::API.setup :url => "https://api.xxxxx.org" do |connection|
      connection.use Faraday::Request::UrlEncoded
      connection.use Redmine::ParseXML
      connection.use Faraday::Adapter::NetHttp
    end
    

    and you get a Hash like the following:

    #<Redmine::Issue(issues) issues={:attributes=>{:type=>"array", :count=>1640}, 
    :issue=>{:id=>4326, 
        :project=>{:attributes=>{:name=>"Redmine", :id=>1}}, 
        :tracker=>{:attributes=>{:name=>"Feature", :id=>2}}, 
        :status=>{:attributes=>{:name=>"New", :id=>1}}, 
        :priority=>{:attributes=>{:name=>"Normal", :id=>4}}, 
        :author=>{:attributes=>{:name=>"John Smith", :id=>10106}}, 
        :category=>{:attributes=>{:name=>"Email notifications", :id=>9}}, 
        :subject=>"\n      Aggregate Multiple Issue Changes for Email Notifications\n    ",
        :description=>"\n      This is not to be confused with another useful proposed feature that\n      would do digest emails for notifications.\n    ", 
        :start_date=>"2009-12-03", 
        :due_date=>{}, 
        :done_ratio=>0, 
        :estimated_hours=>{}, 
        :custom_fields=>{
           :custom_field=>[
              {:attributes=>{:name=>"Issue Owner", :id=>15}, "value"=>"Fred Fake"},
              {:attributes=>{:name=>"Needs Printing", :id=>16}, "value"=>0}, 
              {:attributes=>{:name=>"Review Assignee", :id=>17}, "value"=>"Fran Fraud"},
              {:attributes=>{:name=>"Released On", :id=>20}}, 
              {:attributes=>{:name=>"Client Facing", :id=>21}, "value"=>0}, 
              {:attributes=>{:name=>"Type", :id=>22}, "value"=>"Bug"}, 
              {:attributes=>{:name=>"QA Assignee", :id=>23}}, 
              {:attributes=>{:name=>"Company Name", :id=>26}}, 
              {:attributes=>{:name=>"QA Notes", :id=>27}}, 
              {:attributes=>{:name=>"Failed QA Attempts", :id=>28}, "value"=>2}]},
        :created_on=>"Thu Dec 03 15:02:12 +0100 2009", 
        :updated_on=>"Sun Jan 03 12:08:41 +0100 2010"}}>