ruby to_xml set root attributes - ruby-on-rails

I need to convert a ruby hash to xml. Here is the hash:
hash = {
"AffiliateInfo" => {
"Username" => '123456',
"Password" => "Mypass",
"TrackingCampaign" => "MyTrackingCampaign",
"Env" => "production"
}
}
and the xml I wanted to generate:
<?xml version="1.0" encoding="UTF-8"?>
<InsuranceRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AffiliateInfo>
<Username>12696</Username>
<Password>MyPassword</Password>
<TrackingCampaign>MyTrackingCampaign</TrackingCampaign>
<LeadSourceID>SourceID</LeadSourceID>
<ProductionEnvironment>true</ProductionEnvironment>
</AffiliateInfo>
</InsuranceRequest>
When I do:
hash.to_xml(root: 'InsuranceRequest')
I get the following xml output
<?xml version="1.0" encoding="UTF-8"?>
<InsuranceRequest>
<AffiliateInfo>
<Username>123456</Username>
<Password>Mypass</Password>
<TrackingCampaign>MyTrackingCampaign</TrackingCampaign>
<Env>production</Env>
</AffiliateInfo>
</InsuranceRequest>
The output is missing the properties of the root node attributes:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
I cannot add attributes to the root node. Is there a way to add these attributes using to_xml method?
Please suggest if there is any other means to solve my problem.

You need to use custom builder. Here is example with Nokogiri builder
require 'nokogiri'
hash = {"AffiliateInfo" => {
"Username" => '123456',
"Password" => "Mypass",
"TrackingCampaign" => "MyTrackingCampaign",
"Env" => "production"
}
}
builder = Nokogiri::XML::Builder.new do |xml|
xml.InsuranceRequest('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema') do
xml.AffiliateInfo do
hash['AffiliateInfo'].each do |k, v|
xml.send(k, v)
end
end
end
end
builder.to_xml
This produces the following XML document
<?xml version="1.0"?>
<InsuranceRequest
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AffiliateInfo>
<Username>123456</Username>
<Password>Mypass</Password>
<TrackingCampaign>MyTrackingCampaign</TrackingCampaign>
<Env>production</Env>
</AffiliateInfo>
</InsuranceRequest>
Please note that hash should be defined before builder
Here is Nokogiri documentation http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Builder

Related

Ruby to_xml change attributes name for Api request

I want to convert the keys for xml response So that they match with third party Api request.
class Person1
include ActiveModel::Serializers::Xml
attr_accessor :name, :age
def attributes
{'name' => nil, 'age' => nil}
end
def capitalized_name
name.capitalize
end
end
p = Person1.new
p.name = "test"
puts p.to_xml
output ::-
<?xml version="1.0" encoding="UTF-8"?>
<person1>
<age nil="true"/>
<name>test</name>
</person1>
I am looking for a way to change keys in xml output like.
<?xml version="1.0" encoding="UTF-8"?>
<person1>
<Age nil="true"/>
<Name>test</Name>
</person1>
How about:
puts p.to_xml(:camelize => true)
<?xml version="1.0" encoding="UTF-8"?>
<Person1>
<Age nil="true"/>
<Name>test</Name>
</Person1>
Or if the uppercase Person bothers you, I guess you can do something like that:
puts p.to_xml(:camelize => true).sub('<Person1>','<person1>').sub('</Person1>','</person1>')
<?xml version="1.0" encoding="UTF-8"?>
<person1>
<Age nil="true"/>
<Name>test</Name>
</person1>

How to organize the syntax in this SOAP request packet sent with gem Savon in a ruby script?

I am trying to send a SOAP request to a resource on the web. It is expecting to receive this:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<DiscoverParameterValues xmlns="http://comscore.com/">
<parameterId>loc</parameterId>
<query xmlns="http://comscore.com/ReportQuery">
<Parameter KeyId="geo" Value="124" />
</query>
</DiscoverParameterValues>
</soap:Body>
</soap:Envelope>
This is the Ruby code that Im using to send a request that will result in an XML similar to the one above.
require 'savon'
#Connect to the Comscore API
client = Savon.client(
wsdl:"https://api.comscore.com/KeyMeasures.asmx?WSDL",
basic_auth: ["username", "pw" ],
log: true, :pretty_print_xml=>true)
And this is the XML thats generated:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://comscore.com/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ins0="http://comscore.com/Report"
xmlns:ins1="http://comscore.com/ReportQuery"
xmlns:ins2="http://comscore.com/ReportModel"
xmlns:ins3="http://comscore.com/FetchMedia"
xmlns:ins4="http://comscore.com/Media/Response">
<env:Body>
<tns:DiscoverParameterValues>
<tns:parameterId>loc</tns:parameterId>
==> <tns:query>
<tns:Parameter>
<tns:attributes>
<tns:KeyId>geo</tns:KeyId>
<tns:Value>124</tns:Value>
</tns:attributes>
</tns:Parameter>
==> </tns:query>
</tns:DiscoverParameterValues>
</env:Body>
</env:Envelope>
As you can see there is a difference in how the XML from my code and the XML thats required looks. Ive marked it with "=>"
Here is the gem documentation page that Im using to structure my query:
http://savonrb.com/version2/locals.html
Ive tried playing around with this but I dont understand how to get the "attributes" to work right.
Any suggestions?
UPDATE:
After reading this post, I changed my ruby syntax to the following:
attributes = { "KeyId"=>"geo", "Value" =>"124"}
parameter = {
:parameterId=>"loc",
:query=>{ "Parameter" => { attributes: attributes}}
}
response = client.call(:discover_parameter_values,
message:{:parameterId=>"loc",
:query =>{ "Parameter"=> "", :attributes! =>
{ "Parameter" =>
{ "KeyId" => "geo" , "Value"=>"124" }}},
},
:attributes => {"xmlns" => "http://comscore.com/" })
My XML changed to this:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://comscore.com/"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ins0="http://comscore.com/Report"
xmlns:ins1="http://comscore.com/ReportQuery"
xmlns:ins2="http://comscore.com/ReportModel"
xmlns:ins3="http://comscore.com/FetchMedia"
xmlns:ins4="http://comscore.com/Media/Response">
<env:Body>
<tns:DiscoverParameterValues xmlns="http://comscore.com/">
<tns:parameterId>loc</tns:parameterId>
<tns:query>
<tns:Parameter KeyId="geo" Value="124"/>
</tns:query>
</tns:DiscoverParameterValues>
</env:Body>
</env:Envelope>
So everything works EXCEPT - I cannot set "attributes" on the "query" tag.
I would like to set this as an attribute on the query: "xmlns=http://comscore.com/ReportQuery"
How do I do that?
Question: Does it work?
Savon uses the namespace qualifiert in a different way than your first example.
In the following snippet the tags within have an implicit namespace as specified in the attribute xmlns.
<DiscoverParameterValues xmlns="http://comscore.com/">
<parameterId>loc</parameterId>
<query xmlns="http://comscore.com/ReportQuery">
<Parameter KeyId="geo" Value="124" />
</query>
</DiscoverParameterValues>
Savon uses a different syntax. It defines all needed namespace in the envelope and uses the prefixes per tag later. Different syntax for the same expression.
SoapUI uses often the former variant, Savon the latter.
ParameterIdcomes in both cases from the namespace http://comscore.com/.
UPDATE response:
You can go different ways:
Is usually cheat: I just write fully qualified keys, eg.
...
:query => { "Parameter" => "", :attributes! =>
{ "Parameter" =>
{ "ins1:KeyId" => "geo", "ins1:Value" => "124" }}},
...
If you absolutely want to put the key as attribute on "query" then you can define it like this:
response = client.call(:discover_parameter_values,
message:{:parameterId=>"loc",
:query =>{ "Parameter"=> "", :attributes! =>
{ "Parameter" =>
{ "KeyId" => "geo" , "Value"=>"124" }}},
:attributes! => {'tns:query' =>
{ "xmlns" => "http://comscore.com/ReportQuery" }
},
:attributes => {"xmlns" => "http://comscore.com/" })

Rendering XML with Stylesheet in Rails

I can't figure out how to get a rendered collection (as XML) to include a style sheet line such as:
<?xml-stylesheet type="text/xsl" href="example.xsl" ?>
This guy says to add a proc as such:
proc = Proc.new { |options| options[:builder].instruct!(:xml-stylesheet, type=>'text/xsl', :href=>'something.xsl') }
#foo.to_xml :procs => [proc]
But I can't get that to work. Any suggestions?
Take a look at Nokogiri gem: http://nokogiri.org/Nokogiri/XSLT/Stylesheet.html
doc = Nokogiri::XML(File.read('some_file.xml'))
xslt = Nokogiri::XSLT(File.read('some_transformer.xslt'))
puts xslt.transform(doc)
You can use the atom_feed helper which is included with Rails:
atom_feed(instruct: {
'xml-stylesheet' => {type: 'text/xsl', href: 'styles.xml'}
}) do |feed|
feed.title "My Atom Feed"
# entries...
end
Which results in (showing only first 3 lines):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="styles.xml"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
Or, if not rendering an atom_feed, you can simply use instruct inside your builder:
xml.instruct! 'xml-stylesheet', href: 'style.xml', type: 'text/xsl'

How can I use :only in conjunction with :include in the to_xml in a Rails app?

I'm trying to make use of both :only and :include options in to_xml call in a Rails app. Here's the code:
current_user.to_xml(
:include => :subscription,
:only => [:email, :username]
)
The result of this is somthing like:
<?xml version="1.0" encoding="UTF-8"?>
<user>
<username>cristi</username>
<email>cristi#example.com</email>
<subscription>
</subscription>
</user>
The problem is that subscription has more fields, which are not included. I assume its because of the :only option.
Is there a way to overcome this (show all fields in the subsscription element), without using :except as an option in to_xml ?
I am using Rails 3.
Yes, you can. It works perfectly for me.
>> u = User.first
>> puts u.to_xml(:only => %w( id name ))
<?xml version="1.0" encoding="UTF-8"?>
<user>
<name>Simone Carletti</name>
<id type="integer">1</id>
</user>
>> puts u.to_xml(:only => %w( id name ), :include => :domains)
<?xml version="1.0" encoding="UTF-8"?>
<user>
<name>Simone Carletti</name>
<id type="integer">1</id>
<domains type="array">
<domain>
<name>...</name>
<id type="integer">0</id>
</domain>
<domain>
<name>...</name>
<id type="integer">0</id>
</domain>
</domains>
</user>
Make sure the :subscription association is correct. If it's a one-to-many association, you need to use the pluralized version :subscriptions.

to_xml on Hash with string array fails with Not all elements respond to to_xml

to_xml on Hash with string array fails with Not all elements respond to to_xml
>>r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
>>r.to_xml
RuntimeError: Not all elements respond
to to_xml from
/jruby/../1.8/gems/activesupport2.3.9/lib/active_support/core_ext/array/conversions.rb:163:in `to_xml'
Is there a rails preferred way to change the Hash.to_xml behavior to return
<records>
<record>001</record>
<record>002</record>
</records>
...
Just like DigitalRoss said, this appears to work out of the box in Ruby 1.9 with ActiveSupport 3:
ruby-1.9.2-p0 > require 'active_support/all'
=> true
ruby-1.9.2-p0 > r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
ruby-1.9.2-p0 > puts r.to_xml
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<records type="array">
<record>001</record>
<record>002</record>
</records>
</hash>
At least with MRI (you're using JRuby, though), you can get similar behavior on Ruby 1.8 with ActiveSupport 2.3.9:
require 'rubygems'
gem 'activesupport', '~>2.3'
require 'active_support'
class String
def to_xml(options = {})
root = options[:root] || 'string'
options[:builder].tag! root, self
end
end
Which gives you...
ruby-1.8.7-head > load 'myexample.rb'
=> true
ruby-1.8.7-head > r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
ruby-1.8.7-head > puts r.to_xml
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<records type="array">
<record>001</record>
<record>002</record>
</records>
</hash>
Note that my code doesn't work with Ruby 1.9 and ActiveRecord 3.
No, because there is no way that "001" and "002" know how to become <record>001</record>. These strings are just that: strings. They don't know that they are used in a hash with an array, let alone that these strings share a key, that needs to be singularized.
You could do something like:
record = Struct.new(:value) do
def to_xml
"<record>#{value}</record>"
end
end
r = { "records" => [ record.new("001"), record.new("002") ] }
r.to_xml
Or, use a tool like Builder to make the xml separately from the data structure.

Resources