How to Extract data from XML using Nokogiri Ruby library? - ruby-on-rails

Here is a sample of a XML data that is being fetched, from a seperate GTFS server (Transit Data):
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<GetNextTripsForStopResponse xmlns="http://octranspo.com">
<GetNextTripsForStopResult>
<StopNo>3023</StopNo>
<StopLabel>HURDMAN</StopLabel>
<Error/>
<Route>
<RouteDirection>
<RouteNo>45</RouteNo>
<RouteLabel>Hospital</RouteLabel>
<Direction></Direction>
<Error>12</Error>
<RequestProcessingTime>20221212125854</RequestProcessingTime>
<Trips>
<Trip>
<Longitude/>
<Latitude/>
<GPSSpeed/>
<TripDestination>Hospital</TripDestination>
<TripStartTime>13:03</TripStartTime>
<AdjustedScheduleTime>4</AdjustedScheduleTime>
<AdjustmentAge>-1</AdjustmentAge>
<LastTripOfSchedule>false</LastTripOfSchedule>
<BusType/>
</Trip>
<Trip>
<Longitude/>
<Latitude/>
<GPSSpeed/>
<TripDestination>Hospital</TripDestination>
<TripStartTime>13:18</TripStartTime>
<AdjustedScheduleTime>19</AdjustedScheduleTime>
<AdjustmentAge>-1</AdjustmentAge>
<LastTripOfSchedule>false</LastTripOfSchedule>
<BusType/>
</Trip>
<Trip>
<Longitude/>
<Latitude/>
<GPSSpeed/>
<TripDestination>Hospital</TripDestination>
<TripStartTime>13:33</TripStartTime>
<AdjustedScheduleTime>34</AdjustedScheduleTime>
<AdjustmentAge>-1</AdjustmentAge>
<LastTripOfSchedule>false</LastTripOfSchedule>
<BusType/>
</Trip>
</Trips>
</RouteDirection>
</Route>
</GetNextTripsForStopResult>
</GetNextTripsForStopResponse>
</soap:Body>
</soap:Envelope>
I am trying to extract the data inside 'Trips' and use my maping to return the data. here is the mappinngs I am using:
#mappings = {
:next_for_route => {
'TripDestination' => { :key => :destination, :conv => lambda { |s| s } },
'TripStartTime' => { :key => :departure_from_origin, :conv => lambda { |s| hour_and_minutes_to_elapsed(s) } },
'AdjustedScheduleTime'=> { :key => :expected, :conv => lambda { |s| secs_elapsed_today + 60 * s.to_i } },
'AdjustmentAge' => { :key => :age, :conv => lambda { |s| '-1' == s ? nil : (s.to_f * 60).to_i } },
'BusType' => { :key => :bus_type, :conv => lambda { |s| s } },
'Latitude' => { :key => :latitude, :conv => lambda { |s| s.to_f } },
'Longitude' => { :key => :longitude, :conv => lambda { |s| s.to_f } },
'GPSSpeed' => { :key => :approximate_speed, :conv => lambda { |s| s.to_f } },
},
:route_summary => {
'RouteNo' => { :key => :route_no, :conv => lambda { |s| s.to_i } },
'DirectionID' => { :key => :direction_id, :conv => lambda { |s| s.to_i } },
'Direction' => { :key => :direction, :conv => lambda { |s| s } },
'RouteHeading' => { :key => :heading, :conv => lambda { |s| s } },
}
}
I tried the following but I still can't get the data. I am not sure why it isn't working I checked if my xpath works in online format checker and it worked.
def request(op, root, payload)
payload['appID'] = #app_id
payload['apiKey'] = #api_key
payload['format'] = 'xml'
resp = #conn.get("/v2.0/#{op}") do |req|
req.params = payload
end
doc = Nokogiri::XML(resp.body)
doc.namespace_inheritance = true
yield(doc.at_xpath("//#{root}"))
end
def apply_mapping(key, node, vals)
#mappings[key].each do |k, v|
n = node.css(k.to_s)
vals[v[:key].to_sym] = v[:conv].call(n.first.content) if n
end
vals
end
def next_for_route(stop_no, route_no)
# BUG: docs have <StopInfoData> as root node; running system yields <GetNextTripsForStopResult>
request('GetNextTripsForStop', 'GetNextTripsForStopAllRoutes', { 'stopNo' => stop_no, 'routeNo' => route_no }) do |root_node|
arr = root_node.at('/Trips/Trip') do |pn|
apply_mapping(:next_for_route, pn, { :stop_no => stop_no, :route_no => route_no })
end
yield(arr)
end
end

Related

Ruby - Append data to existing JSON

According to this answer, its possible to append data to JSON.
But when I try test.push I get the error NoMethodError: undefined method push' `.
NETWORK_SG = Azure::Armrest::Network::NetworkSecurityGroupService.new(conf)
network_sg = NETWORK_SG.get('testing_123', rg)
test = network_sg.properties
puts test
{
"provisioningState": "Succeeded",
"resourceGuid": "test",
"securityRules": [
{
"name": "SSH",
"id": "SSH",
"etag": "18",
"type": "Microsoft/securityRules",
"properties": {}
}
]
}
options = {"key": "value"}
test.push(options)
How can I append the following JSON data to securityRules[]? Sometimes securityRules[] can be a empty array but I still want to append.
{
:name => 'rule_2',
:properties => {
:protocol => 'TCP',
:sourceAddressPrefix => '*',
:destinationAddressPrefix => '*',
:access => 'Allow',
:destinationPortRange => '22',
:sourcePortRange => '*',
:priority => '301',
:direction => 'Inbound',
}
}
You can use Array#push like this:
test = {
:provisioningState=>"Succeeded",
:resourceGuid=>"test",
:securityRules=>[
{:name=>"SSH", :id=>"SSH", :etag=>"18",:type=>"Microsoft/securityRules", :properties=>{}}
]
}
new_rule = {
:name => 'rule_2',
:properties => {
:protocol => 'TCP',
:sourceAddressPrefix => '*',
:destinationAddressPrefix => '*',
:access => 'Allow',
:destinationPortRange => '22',
:sourcePortRange => '*',
:priority => '301',
:direction => 'Inbound',
}
}
test[:securityRules].push(new_rule)
test
# {
# :provisioningState=>"Succeeded",
# :resourceGuid=>"test",
# :securityRules=> [
# {:name=>"SSH", :id=>"SSH", :etag=>"18", :type=>"Microsoft/securityRules", :properties=>{}},
# {:name=>"rule_2",:properties=>{:protocol=>"TCP",:sourceAddressPrefix=>"*",:destinationAddressPrefix=>"*",:access=>"Allow",:destinationPortRange=>"22",:sourcePortRange=>"*",:priori# ty=>"301",:direction=>"Inbound"}}
# ]
# }

Merge two hashes in ruby

I have two collections of hashes
and_filters = [{:filter=>:brand, :value=>"Fila"}, {:filter=>:brand, :value=>"Adidas"}]
or_filters = [{:filter=>:gender, :value=>"Hombre"}]
and i need make like the following struct
:_or => [
{ :_and => [
{:gender => "Hombre"},
{:brand => "Adidas"}]
},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}]
}
]
For this i did
query[:_or] = []
or_filters.each do |or_f|
query[:_or] << {
:_and => [
and_filters.map do |and_f|
{and_f[:filter] => and_f[:value]}
end
{ or_f[:filter] => or_f[:value] }
]
}
end
but an error Expected: { shows in code. Apparently the second loop is badly syntactically
It's not pretty, but I believe this gives the desired results:
{_or: or_filters.each_with_object([]) do |or_filter, or_filter_ary|
or_filter_hsh = {or_filter[:filter] => or_filter[:value]}
and_filters.each do |and_filter|
and_filter_hsh = {and_filter[:filter] => and_filter[:value]}
or_filter_ary << {_and: [or_filter_hsh, and_filter_hsh]}
end
end
}
Which gives:
{:_or => [
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}
]},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Adidas"}
]}
]}
It looks like you want every combination of the given and_filters with the given or_filters. In that case, and assuming you don't care about order (:gender before :brand vs. the other way around) Array#product is your friend:
result = {
_or: and_filters.product(or_filters).map do |a|
{ _and: a.map {|filter:, value:| { filter => value }} }
end
}
# => {
# :_or => [
# {:_and => [{:brand=>"Fila"}, {:gender=>"Hombre"}]},
# {:_and => [{:brand=>"Adidas"}, {:gender => "Hombre"}]}
# ]
# }
See it in action on repl.it: https://repl.it/#jrunning/HorizontalDirectCharmap
Thats what i was looking for
query = {}
query[:_or] = or_filters.map do |or_f|
and_filters_aux = and_filters.dup
and_filters_aux << or_f
{ :_and => and_filters_aux.map{|hsh| {hsh[:filter] => hsh[:value]} } }
end
https://repl.it/repls/ShyLateClients

Mongoid query - Refine Search Implementation

I am working on refine search in Rails4 and Mongoid. This is my search params and form,
I built the query piece by piece:
if params[:procedure_id]
#blood_banks = BloodBank.where(:procedure_id => params[:procedure_id])
end
if params[:location_ids]
#blood_banks = BloodBank.where(:location_id.in => params[:location_ids])
end
...
if condition
end
If i need to search Procedure "Discectomy" in location "Hebbal" then i nned to define the if condition as follows,
if params[:procedure_id].present? && params[:location_id].present?
...
end
Then i need to do for all such combinations(4x3x2x1) of search, for my refine search!!!
Which is the best way to implement the same.
How do you achieve above situation???
I can't right all the possible if conditions, is their any shortcut method!!!
How to achieve below code:
if params[:fees].present?
if params[:fees] == "300"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "500"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => 300, '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "1000"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => 500, '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "1001"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
end
You can do this:
conditions = {}
conditions.merge!(:procedure_id => params[:procedure_id]) if params[:procedure_id]
conditions.merge!(:location_id.in => params[:location_ids]) if params[:location_ids]
...
#blood_banks = BloodBank.where(conditions)
EDIT:
Regarding your last code, since you have no logic here (common idea for all values), you can use case:
condition = case params[:fees]
when "300"
{ '$lte' => 300 }
when "500"
{ '$gte' => 300, '$lte' => 500 }
when "1000"
{ '$gte' => 500, '$lte' => 1000 }
when "1001"
{ '$gte' => 1001 }
else
nil
end
if condition
#doctor_clinic = DoctorClinic.where( :consultation_fees => condition).map(&:doctor_id)
#doctors = Doctor.where({ :id.in => #doctor_clinic })
end

Hash overriding a hash

I got some yml config files with a deep, different structures and I want to extract passwords(that are located on different levels) and store them in a yml file outside a git repo. Let me show an example
config1.yml
a:
b:
c: 1
password: secret
...
d: 2
...
I wish to replace secret with '' and extract a pass to a different yml file that will look like:
config1_passwords.yml
a:
b:
password: secret
Is there any way to merge config1.yml without passwords with config1_passwords.yml to get a correct config structure?
So basically in terms of hashes(parsed ymls) I want to do following:
{ :a => { :b => { :c => 1, :password => '' }, :d => 2 } }
{ :a => { :b => { :password => 'secret' } } }
# =>
{ :a => { :b => { :c => 1, :password => 'secret' }, :d => 2 } }
Thanks for suggestions
EDITED
another example
{ :a => { :b => { :c => 1, :d => { :password1 => '' }, :password2 => '' } } }
{ :a => { :b => { :d => { :password => 'secret' }, :password2 => 'secret2' } } }
# =>
{ :a => { :b => { :c => 1, :d => { :password => 'secret' }, :password2 => 'secret2' } } }
Rails 3 has a deep_merge, which does exactly what you want.
a = { :a => { :b => { :c => 1, :d => { :password1 => '' }, :password2 => '' } } }
b = { :a => { :b => { :d => { :password1 => 'secret' }, :password2 => 'secret2' } } }
a.deep_merge(b)
# -> {:a=> {:b=> {:c=>1, :d=>{:password1=>"secret"}, :password2=>"secret2"}}}
Note: I changed a[:a][:b][:d] to contain :password1 instead of :password.
Don't think this can be done using Ruby one-liners. But a simple recursive function might do
def recurse_merge_password! conf_hash, pw_hash
pw_hash.keys.each do |k|
next unless conf_hash.has_key?(k)
case
when k == :password
conf_hash[:password] = pw_hash[:password]
when conf_hash[k].is_a?(Hash) && pw_hash[k].is_a?(Hash)
recurse_merge_password! conf_hash[k], pw_hash[k]
end
end
end
h1 = { :a => { :b => { :c => 1, :password => '' }, :d => 2 } }
h2 = { :a => { :b => { :password => "secret" } } }
recurse_merge_password! h1, h2
puts h1.inspect
=> {:a=>{:b=>{:c=>1, :password=>"secret"}, :d=>2}}
If you have arrays and other structures that you may need to traverse, it is up to you to improve on this. Note I made it modify the config in place.
It appears you want some sort of Hash deep merge. It's available in ActiveSupport (part of Rails):
# You can omit this require statement if you're running Rails.
require "active_support/core_ext/hash/deep_merge"
a = { a: { b: { c: 1, d: { password1: "" }, password2: "" } } }
b = { a: { b: { d: { password1: "secret" }, password2: "secret2" } } }
a.deep_merge(b)
#=> { a: { b: { c: 1, d: { password1: "secret"}, password2: "secret2" } } }
If you don't want to depend on ActiveSupport, take a look at the implementation.

Ruby how to sort hash of hashes?

I have that deep Hash of hashes:
my_hash = {
:category_1 => {
:solution_1 => { :order => 1 },
:solution_2 => { :order => 2 }
},
:category_2 => {
:solution_3 => { :order => 3 },
:solution_4 => { :order => 4 }
}
}
I want to sort :solution_* hashes under :category_* hashes by key :order. Any suggestions?
(fixed)
Let's say you have the following hash of people to ages:
people = {
:fred => { :name => "Fred", :age => 23 },
:joan => { :name => "Joan", :age => 18 },
:pete => { :name => "Pete", :age => 54 }
}
use sort_by to get where we want to go:
people.sort_by { |k, v| v[:age] }
# => [[:joan, {:name=>"Joan", :age=>18}],
[:fred, {:name=>"Fred", :age=>23}],
[:pete, {:name=>"Pete", :age=>54}]]
Ok, you didn't specify your question, so I'm assuming you want one layer removed. I changed the starting hash a bit to actually see if the sorting works:
my_hash = {
:category_1 => {
:solution_1 => { :order => 2 },
:solution_2 => { :order => 3 }
},
:category_2 => {
:solution_3 => { :order => 4 },
:solution_4 => { :order => 1 }
}
}
Hash[my_hash.inject({}) { |h, (k, v)| h.merge(v) }.sort_by { |k,v| v[:order] }]
#=> {:solution_4=>{:order=>1}, :solution_1=>{:order=>2}, :solution_2=>{:order=>3}, :solution_3=>{:order=>4}}
EDIT:
Taking into account your clarification (and still starting from the modified unsorted hash I posted above):
sorted = my_hash.inject({}) do |h, (k, v)|
h[k] = Hash[v.sort_by { |k1, v1| v1[:order] }]
h
end
#=> {:category_1=>{:solution_1=>{:order=>2}, :solution_2=>{:order=>3}}, :category_2=>{:solution_4=>{:order=>1}, :solution_3=>{:order=>4}}}

Resources