Ruby on Rails - Need to Read XML File into an Array - ruby-on-rails

I am using Ruby on Rails and need to read the contents of an xml file into an array?
I pulled the data from an API using the following:
uri = URI.parse("myAPIurl.com&show=storeID,name")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
Looking for a lean method to iterate this xml file into an array using Ruby on Rails.

For simple use cases you can use:
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<foo type="integer">1</foo>
<bar type="integer">2</bar>
</hash>
XML
hash = Hash.from_xml(xml)
# => {"hash"=>{"foo"=>1, "bar"=>2}}
Hash - Ruby on Rails

Try using Nokogiri gem. It's super easy to use, the website has some nice examples.
require 'open-uri'
doc = Nokogiri::HTML(open("myAPIurl.com&show=storeID,name"))
you can then query your document using xpath or css selectors. It then returns a NodeSet that you can iterate over like you would do with an array
Hope it helps

Related

Ruby Nokogiri XML formatting for post request

I am setting up a method in a Rails app which posts 2 bits of data to an endpoint api.
Using Nokogiri to parse the data, and Faraday to send request, I have got very close to what is needed for successful post of multiple order items data, excepting a key issue.
This is that I am getting unwanted repeats of the top level node, because it is inside an each loop, which is needed for the items, whereas we only need that once. Below is the method, whereby an object is parsed and the order items put in to place in xml with Nokogiri and here we are just accessing the closed orders in the data object by definition, and just sending the id and quantity for each of them.
def update_backoffice_closed_orders(bj_update)
bj_update.each do |item|
if item['order_status'] == 'closed'
id = item['order_number']
del = item['quantity_fulfilled']
# Set up the connection to the Backoffice API and items to be updated
#xml_post = Nokogiri::XML::Builder.new do |xml|
xml.supplier {
xml.sid SID
xml.password PASSWORD
xml.order {
xml.id id
xml.volume_Delivered del
}
}
end
# Send the request to the Backoffice API
#response = CONNECTION.post do |req|
req.url '/suppliers/orderDataUpdate.php'
req.body = #xml_post.to_xml
end
end
end
end
This is working OK except failing at api because the top level nodes repeat like so:
<?xml version="1.0"?>
<supplier>
<sid>344</sid>
<password>4657gdhtey6</password>
in each order, whereas all are only needed once at top, with supplier closing tag after all the orders in between, as in the example below.
<?xml version="1.0"?>
<supplier>
<sid>344</sid>
<pw>4657gdhtey6</pw>
<order>
<id>3686414</id>
<volume_delivered>480</volume_delivered>
</order>
<order>
<id>3686425</id>
<volume_delivered>480</volume_delivered>
</order>
<order>
<id>3686443</id>
<volume_delivered>480</volume_delivered>
</order>
</supplier>
CONNECTION is a Faraday constant and works fine, as are SID and PASSWORD, if I can get the Nokogiri #xml_post like above, then it is exactly as needed and used in Postman tests for the api, and should work. If anyone knows how to adjust the xml builder to achieve that in this method, please share?
Biggest problem seems the <?xml opening tag which is default with Nokogiri.
Many Thanks

XML Nokogiri Request won't stop auto ordering hash keys?

I am using ruby 2.0.0 and the Nokogiri gem. I have the following code:
builder = Nokogiri::XML::Builder.new do |xml|
xml['s'].Envelope('xmlns:s' => "http://schemas.xmlsoap.org/soap/envelope/") {
xml['s'].Body {
xml.GetList('xmlns' => "http://tempuri.org/") {
xml.listRequest('i:type' => "b:NpsListRequest", 'xmlns:a' => "http://schemas.datacontract.org/2004/07/Services.List", 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:b' => "http://schemas.datacontract.org/2004/07/Services.List.Strategies")
}
}
}
end
The above is incomplete. I need to pass in an ID to actually make the call, however this is what the above returns:
<?xml version="1.0" encoding="ISO-8859-1"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetList xmlns="http://tempuri.org/">
<listRequest xmlns:a="http://schemas.datacontract.org/2004/07/Services.List" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://schemas.datacontract.org/2004/07/Services.List.Strategies" i:type="b:NpsListRequest"/>
</GetList>
</s:Body>
</s:Envelope>
The issue is with the <listRequest xmlns:a code block. In the XML I am creating, I am passing in i:type as the first key, however the response is putting xmlns:a as the first key. Why is that happening?
You are using Nokogiri Builder to create XML attributes.
The XML specification declares that attribute order is not significant. See the XML Specification specifically Section 3.1 where it says:
the order of attribute specifications in a start-tag ... is not significant.
Therefore, the specific way it is handled by an XML library is undefined, but all will produce valid results.

Rails/Ruby equivalent of PHP file_get_contents

Would like to know the Rails/Ruby equivalent of the following PHP:
$data = json_decode(file_get_contents(URL_GOES_HERE))
The URL is an external resource that returns json data (Facebook's API).
I've tried:
data = JSON.parse(URL_GOES_HERE)
but I assume I still need the `file_get_contents' part? How do I do this in Rails 4?
Try this
require 'open-uri'
file = open(URL_GOES_HERE)
data = JSON.parse file.read

Ruby Net::HTTP::Get and JSON responses

I'm trying to connect to an API and retrieve the json results with my rails app, however it doesn't seem to work.
Take for example:
#request = Net::HTTP::Get.new "http://example.com/?search=thing&format=json"
When I try the url in my browser it works! and I get JSON data, however when I try that in Ruby the body is nil.
>> y #request
--- !ruby/object:Net::HTTP::Get
body:
body_stream:
header:
accept:
- "*/*"
user-agent:
- Ruby
method: GET
path: http://example.com/?search=thing&format=json
request_has_body: false
response_has_body: true
Any thoughts?
I usually use open-uri
require 'open-uri'
content = open("http://your_url.com").read
`
You need to actually send the request and retrieve a response object, like this:
response = Net::HTTP.get_response("example.com","/?search=thing&format=json")
puts response.body //this must show the JSON contents
Regards!
PS: While using ruby's HTTP lib, this page has some useful examples.

What's the best way to use SOAP with Ruby?

A client of mine has asked me to integrate a 3rd party API into their Rails app. The only problem is that the API uses SOAP. Ruby has basically dropped SOAP in favor of REST. They provide a Java adapter that apparently works with the Java-Ruby bridge, but we'd like to keep it all in Ruby, if possible. I looked into soap4r, but it seems to have a slightly bad reputation.
So what's the best way to integrate SOAP calls into a Rails app?
I built Savon to make interacting with SOAP webservices via Ruby as easy as possible.
I'd recommend you check it out.
We used the built in soap/wsdlDriver class, which is actually SOAP4R.
It's dog slow, but really simple. The SOAP4R that you get from gems/etc is just an updated version of the same thing.
Example code:
require 'soap/wsdlDriver'
client = SOAP::WSDLDriverFactory.new( 'http://example.com/service.wsdl' ).create_rpc_driver
result = client.doStuff();
That's about it
We switched from Handsoap to Savon.
Here is a series of blog posts comparing the two client libraries.
I also recommend Savon. I spent too many hours trying to deal with Soap4R, without results. Big lack of functionality, no doc.
Savon is the answer for me.
Try SOAP4R
SOAP4R
Getting Started with SOAP4R
And I just heard about this on the Rails Envy Podcast (ep 31):
WS-Deathstar SOAP walkthrough
Just got my stuff working within 3 hours using Savon.
The Getting Started documentation on Savon's homepage was really easy to follow - and actually matched what I was seeing (not always the case)
Kent Sibilev from Datanoise had also ported the Rails ActionWebService library to Rails 2.1 (and above).
This allows you to expose your own Ruby-based SOAP services.
He even has a scaffold/test mode which allows you to test your services using a browser.
I have used HTTP call like below to call a SOAP method,
require 'net/http'
class MyHelper
def initialize(server, port, username, password)
#server = server
#port = port
#username = username
#password = password
puts "Initialised My Helper using #{#server}:#{#port} username=#{#username}"
end
def post_job(job_name)
puts "Posting job #{job_name} to update order service"
job_xml ="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns=\"http://test.com/Test/CreateUpdateOrders/1.0\">
<soapenv:Header/>
<soapenv:Body>
<ns:CreateTestUpdateOrdersReq>
<ContractGroup>ITE2</ContractGroup>
<ProductID>topo</ProductID>
<PublicationReference>#{job_name}</PublicationReference>
</ns:CreateTestUpdateOrdersReq>
</soapenv:Body>
</soapenv:Envelope>"
#http = Net::HTTP.new(#server, #port)
puts "server: " + #server + "port : " + #port
request = Net::HTTP::Post.new(('/XISOAPAdapter/MessageServlet?/Test/CreateUpdateOrders/1.0'), initheader = {'Content-Type' => 'text/xml'})
request.basic_auth(#username, #password)
request.body = job_xml
response = #http.request(request)
puts "request was made to server " + #server
validate_response(response, "post_job_to_pega_updateorder job", '200')
end
private
def validate_response(response, operation, required_code)
if response.code != required_code
raise "#{operation} operation failed. Response was [#{response.inspect} #{response.to_hash.inspect} #{response.body}]"
end
end
end
/*
test = MyHelper.new("mysvr.test.test.com","8102","myusername","mypassword")
test.post_job("test_201601281419")
*/
Hope it helps. Cheers.
I have used SOAP in Ruby when i've had to make a fake SOAP server for my acceptance tests. I don't know if this was the best way to approach the problem, but it worked for me.
I have used Sinatra gem (I wrote about creating mocking endpoints with Sinatra here) for server and also Nokogiri for XML stuff (SOAP is working with XML).
So, for the beginning I have create two files (e.g. config.rb and responses.rb) in which I have put the predefined answers that SOAP server will return.
In config.rb I have put the WSDL file, but as a string.
##wsdl = '<wsdl:definitions name="StockQuote"
targetNamespace="http://example.com/stockquote.wsdl"
xmlns:tns="http://example.com/stockquote.wsdl"
xmlns:xsd1="http://example.com/stockquote.xsd"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
.......
</wsdl:definitions>'
In responses.rb I have put samples for responses that SOAP server will return for different scenarios.
##login_failure = "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<LoginResponse xmlns="http://tempuri.org/">
<LoginResult xmlns:a="http://schemas.datacontract.org/2004/07/WEBMethodsObjects" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Error>Invalid username and password</a:Error>
<a:ObjectInformation i:nil="true"/>
<a:Response>false</a:Response>
</LoginResult>
</LoginResponse>
</s:Body>
</s:Envelope>"
So now let me show you how I have actually created the server.
require 'sinatra'
require 'json'
require 'nokogiri'
require_relative 'config/config.rb'
require_relative 'config/responses.rb'
after do
# cors
headers({
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "POST",
"Access-Control-Allow-Headers" => "content-type",
})
# json
content_type :json
end
#when accessing the /HaWebMethods route the server will return either the WSDL file, either and XSD (I don't know exactly how to explain this but it is a WSDL dependency)
get "/HAWebMethods/" do
case request.query_string
when 'xsd=xsd0'
status 200
body = ##xsd0
when 'wsdl'
status 200
body = ##wsdl
end
end
post '/HAWebMethods/soap' do
request_payload = request.body.read
request_payload = Nokogiri::XML request_payload
request_payload.remove_namespaces!
if request_payload.css('Body').text != ''
if request_payload.css('Login').text != ''
if request_payload.css('email').text == some username && request_payload.css('password').text == some password
status 200
body = ##login_success
else
status 200
body = ##login_failure
end
end
end
end
I hope you'll find this helpful!
I was having the same issue, switched to Savon and then just tested it on an open WSDL (I used http://www.webservicex.net/geoipservice.asmx?WSDL) and so far so good!
https://github.com/savonrb/savon

Resources