rails inverting to_xml and getting the original model - ruby-on-rails

I did this:
[User.first, User.last].to_xml
and got this:
<users type="array">
<user>
<created-at type="datetime">2010-03-16T06:40:51Z</created-at>
<id type="integer">3</id>
<password-hash></password-hash>
<salt></salt>
<updated-at type="datetime">2010-03-16T06:40:51Z</updated-at>
<username nil="true"></username>
</user>
<user>
<created-at type="datetime">2010-03-23T03:58:15Z</created-at>
<id type="integer">7</id>
<password-hash></password-hash>
<salt></salt>
<tutorial-state nil="true"></tutorial-state>
<updated-at type="datetime">2010-03-23T03:58:15Z</updated-at>
<username nil="true"></username>
</user>
</users>
How can I take that string of xml and invert it to get the original activerecord objects back?

Try this:
Model object xml:
xml = User.first.to_xml
User.new(Hash.from_xml(xml))
Array of model xml:
xml = User.all.to_xml
users = (Hash.from_xml(xml)["users"] || []).collect{|attr| User.new(attr)}

I do know that you can do this on individual users; doing it on an array will require your own bit of XML parsing.
user = User.new
user.from_xml '<user><id type="integer">1</id></user>'

Related

Nokogiri : NoMethodError (undefined method `inner_html' for nil:NilClass)

I'm trying to parse a simple XML data with nokogiri.
this is my XML:
POST /.... HTTP/1.1
Host: ....
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://...."
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="...." xmlns:xsd="...." xmlns:soap="....">
<soap:Body>
<WS_QueryOnSec xmlns="......">
<type>string</type>
<ID>string</ID>
</WS_QueryOnSec>
</soap:Body>
</soap:Envelope>
and this is my simle request:
require "nokogiri"
#doc = Nokogiri::XML(request.body.read)
#something = #doc.at('type').inner_html
But Nokogiri can not find the Type or ID node.
When I change the data into this every thing works fine:
<soap:Body>
<type>string</type>
<ID>string</ID>
</soap:Body>
It seems the problem is the raw text above the data and the nods with xmlns or the other attributes!
What do you recommend to resolve this ?
The first "XML" isn't XML. It's text that contains XML. Remove the header information down to the blank line and try it again.
I think it'd help you to read the XML spec or to read some tutorials about creating XML which will help you understand how it's defined. XML is a tight specification and doesn't allow any deviation. The syntax is pretty flexible, but you have to play by its rules.
Consider these examples:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
foo
<root>
<node />
</root>
EOT
doc.errors # => [#<Nokogiri::XML::SyntaxError: Start tag expected, '<' not found>]
Removing the text, which is outside the root tag results in a proper parse:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<root>
<node />
</root>
EOT
doc.errors # => []
<root> isn't neccesarily the name of the "root" node, it's just the outermost tag:
doc = Nokogiri::XML(<<EOT)
<foo>
<node />
</foo>
EOT
doc.errors # => []
and still results in a valid DOM/internal representation of the document:
puts doc.to_html
# >> <foo>
# >> <node></node>
# >> </foo>
Your XML sample is using namespaces, which complicate matters somewhat. The Nokogiri documentation talks about how to deal with them, so you'll want to understand that part of parsing XML because you'll encounter it again. Here's the easy way of working with them:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="utf-8"?>
<Envelope xmlns:xsi="...." xmlns:xsd="...." xmlns:soap="....">
<Body>
<WS_QueryOnSec xmlns="......">
<type>string</type>
<ID>string</ID>
</WS_QueryOnSec>
</Body>
</Envelope>
EOT
namespaces = doc.collect_namespaces
doc.at('type', namespaces).text # => "string"

How to convert XML to Hash in ruby?

I have a XML code which I want to convert into Hash
<meta_description><language id="1"></language><language id="2"></language></meta_description>
<meta_keywords><language id="1"></language><language id="2"></language></meta_keywords>
<meta_title><language id="1"></language><language id="2" ></language></meta_title>
<link_rewrite><language id="1" >konsult-500-krtim</language><language id="2" >konsult-500-krtim</language></link_rewrite>
<name><language id="1" >Konsult 500 kr/tim</language><language id="2" >Konsult 500 kr/tim</language></name>
<description><language id="1" ></language><language id="2" ></language></description>
<description_short><language id="1" ></language><language id="2" ></language></description_short>
<available_now><language id="1" ></language><language id="2" ></language></available_now>
<available_later><language id="1" ></language><language id="2" ></language></available_later>
<associations>
<categories nodeType="category" api="categories">
<category>
<id>2</id>
</category>
</categories>
<images nodeType="image" api="images"/>
<combinations nodeType="combination" api="combinations"/>
<product_option_values nodeType="product_option_value" api="product_option_values"/>
<product_features nodeType="product_feature" api="product_features"/>
<tags nodeType="tag" api="tags"/>
<stock_availables nodeType="stock_available" api="stock_availables">
<stock_available>
<id>111</id>
<id_product_attribute>0</id_product_attribute>
</stock_available>
</stock_availables>
<accessories nodeType="product" api="products"/>
<product_bundle nodeType="product" api="products"/>
</associations>
I want to convert this xml into Hash .
I try to find functions which convert this xml to h=Hash.new
How I do this?
There is ActiveSupport's Hash#from_xml method that you can use:
xml = File.open("data.xml").read # if your xml is in the 'data.xml' file
Hash.from_xml(xml)
If you are using Rails you can use the answer provided above, otherwise you can require the ActiveSuppport gem:
require 'active_support/core_ext/hash'
xml = '<foo>bar</foo>'
hash = Hash.from_xml(xml)
=>{"foo"=>"bar"}
Note this will only work with valid xml. See comments on op. Also note that using element attributes like id="1" won't convert back the same way for example:
xml = %q(
<root>
<foo id="1"></foo>
<bar id="2"></bar>
</root>).strip
hash = Hash.from(xml)
=>{"root"=>{"foo"=>{"id"=>"1"}, "bar"=>{"id"=>"2"}}}
puts hash.to_xml
# will output
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<root>
<foo>
<id>1</id>
</foo>
<bar>
<id>2</id>
</bar>
</root>
</hash>
Use nokogiri to parse XML response to ruby hash. It's pretty fast.
require 'active_support/core_ext/hash' #from_xml
require 'nokogiri'
doc = Nokogiri::XML(response_body)
Hash.from_xml(doc.to_s)

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>

Rails XML Feed: ID as node attribute

I set up a simple XML feed for a vendor we're using (who refuses to read JSON).
<recipes type="array">
<recipe>
<id type="integer">1</id>
<name>
Hamburgers
</name>
<producturl>
http://test.com
</producturl>
...
</recipe>
...
<recipe>
However, the vendor requests that instead of having an id node, id is an attribute in the parent node. e.g.
<recipes type="array">
<recipe id="1">
<name>
Hamburgers
</name>
<producturl>
http://test.com
</producturl>
...
</recipe>
...
<recipe>
I'm building this with (basically)
xml_feed = []
recipes.each do |recipe|
xml_feed <<{id: recipe.id, name: recipe.name, ...}
end
...
render xml: xml_feed.to_xml(root: 'recipes')
But I'm unsure of how to include the id (or any field) as an attribute in the parent node like that. I googled around and couldn't find anything, nor were the http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html docs very helpful
Thanks!
I would suggest you use the nokogiri gem. It provides all you can possible need for handling XML.
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.objects {
xml.object.classy.thing!
}
}
end
puts builder.to_xml
<?xml version="1.0"?>
<root>
<objects>
<object class="classy" id="thing"/>
</objects>
</root>
The suggestion to use Nokogiri is fine. Just the sintax should be a little bit different to achive what you have requested:
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.object('type' => 'Client') {
xml.name 'John'
}
}
end
puts builder.to_xml
<?xml version="1.0"?>
<root>
<object type="Client">
<name>John</name>
</object>
</root>

Read XML responses in to Rails

I have a URL in my controller that when called return an XML response. Let say, the response looks like this;
<?xml version="1.0" encoding="UTF-8"?>
<AutoCreate>
<Response>
<Status>YES</Status>
<name>JOSEPH</name>
<location>HOME</location>
</Response>
</AutoCreate>
How can i read these values status, name and location into variables in my controller and use them.
Thank you in advance.
can you try this,
response_json = Hash.from_xml(response).to_json
So heres an update for Rails 5,
If you are receiving an XML response the header will be 'application/xml'
You access the data using
#read the response and create a Hash from the XML
response = Hash.from_xml(request.body.read)
#read value from the Hash
status = response["AutoCreate"]["Response"]["Status"]
If the value of respose.body is;
<?xml version="1.0" encoding="UTF-8"?>
<AutoCreate>
<Response>
<Status>YES </Status>
<name> JOSEPH </name>
<location> HOME </location>
</Response>
</AutoCreate>
Then i think this should be fine.
require ‘active_support’
result = Hash.from_xml(response.body)
then;
result.status == "YES"
Would this work?
You can use https://github.com/alsemyonov/xommelier
feed = Xommelier::Atom::Feed.parse(open('spec/fixtures/feed.atom.xml'))
puts feed.id, feed.title, feed.updated
feed.entries do |entry|
puts feed.id, feed.title, feed.published, feed.updated
puts feed.content || feed.summary
end

Resources