Ruby Builder Gem - dynamically set node name - ruby

I'm currently using the Builder gem for Ruby to generate XML representations for resources in my application. The XML representation has multiple child nodes that are always structure the same, but the top-level node has a different name, depending on the value of a boolean property of the resource. Is there any way I can generate builder nodes dynamically? Something like this (tried this already, doesn't work):
if resource.attr
top_level_node = :ForFlowBased
else
top_level_node = :ForNonSeamlessOffload
end
builder = Builder::XmlMarkup.new
builder.send(top_level_node). do |top_level_node|
....
end
That code will generate a node <send:ForFlowBased>. Similarly if I call builder.(top_level_node), the xml generated is <call:ForFlowBased>. I'm looking to dynamically send builder the method I want to invoke on it, without adding send or call to the XML as well.

Do this:
builder.tag!(top_level_node) do |top_level_node|
end

Related

Extracting value from complex hash in Ruby

I am using an API (zillow) which returns a complex hash. A sample result is
{"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=>"http://www.zillow.com/static/xsd/SearchResults.xsd http://www.zillowstatic.com/vstatic/5985ee4/static/xsd/SearchResults.xsd",
"xmlns:SearchResults"=>"http://www.zillow.com/static/xsd/SearchResults.xsd", "request"=>[{"address"=>["305 Vinton St"], "citystatezip"=>["Melrose, MA 02176"]}],
"message"=>[{"text"=>["Request successfully processed"], "code"=>["0"]}],
"response"=>[{"results"=>[{"result"=>[{"zpid"=>["56291382"], "links"=>[{"homedetails"=>["http://www.zillow.com/homedetails/305-Vinton-St-Melrose-MA-02176/56291382_zpid/"],
"graphsanddata"=>["http://www.zillow.com/homedetails/305-Vinton-St-Melrose-MA-02176/56291382_zpid/#charts-and-data"], "mapthishome"=>["http://www.zillow.com/homes/56291382_zpid/"],
"comparables"=>["http://www.zillow.com/homes/comps/56291382_zpid/"]}], "address"=>[{"street"=>["305 Vinton St"], "zipcode"=>["02176"], "city"=>["Melrose"], "state"=>["MA"], "latitude"=>["42.466805"],
"longitude"=>["-71.072515"]}], "zestimate"=>[{"amount"=>[{"currency"=>"USD", "content"=>"562170"}], "last-updated"=>["06/01/2014"], "oneWeekChange"=>[{"deprecated"=>"true"}], "valueChange"=>[{"duration"=>"30", "currency"=>"USD", "content"=>"42749"}], "valuationRange"=>[{"low"=>[{"currency"=>"USD",
"content"=>"534062"}], "high"=>[{"currency"=>"USD", "content"=>"590278"}]}], "percentile"=>["0"]}], "localRealEstate"=>[{"region"=>[{"id"=>"23017", "type"=>"city",
"name"=>"Melrose", "links"=>[{"overview"=>["http://www.zillow.com/local-info/MA-Melrose/r_23017/"], "forSaleByOwner"=>["http://www.zillow.com/melrose-ma/fsbo/"],
"forSale"=>["http://www.zillow.com/melrose-ma/"]}]}]}]}]}]}]}
I can extract a specific value using the following:
result = result.to_hash
p result["response"][0]["results"][0]["result"][0]["zestimate"][0]["amount"][0]["content"]
It seems odd to have to specify the index of each element in this fashion. Is there a simpler way to obtain a named value?
It looks like this should be parsed into XML. According to the Zillow API Docs, it returns XML by default. Apparently, "to_hash" was able to turn this into a hash (albeit, a very ugly one), but you are really trying to swim upstream by using it this way. I would recommend using it as intended (xml) at the start, and then maybe parsing it into an easier to use format (like a JSON/Hash structure) later.
Nokogiri is GREAT at parsing XML! You can use the xpath syntax for grabbing elements from the dom, or even css selectors.
For example, to get an array of the "content" in every result:
response = #get xml response from zillow
results = Nokogiri::XML(response).remove_namespaces!
#using css
content_array = results.css("result content")
#same thing using xpath:
content_array = results.xpath("//result//content")
If you just want the content from the first result, you can do this as a shortcut:
content = results.at_css("result content").content
Since it is indeed XML dumped into a JSON, you could use JSONPath to query the JSON

Run a Liquid filter from a Jekyll plugin

Liquid has two filters named newline_to_br and escape.
I'm working on a Jekyll plugin that needs to run a string through those filters. Rather than install a separate gem which does this, or write my own code for it, is there any way to call those filters directly from inside of the plugin?
Those filters can become available with the line include Liquid::StandardFilters.
For example:
class PlaintextConverter
include Liquid::StandardFilters
def convert(content)
content = escape(content)
content = newline_to_br(content)
content
end
end
For a full list of functions that become available in this way, you can view the source of standardfilters.rb

Avoid repeated calls to an API in Jekyll Ruby plugin

I have written a Jekyll plugin to display the number of pageviews on a page by calling the Google Analytics API using the garb gem. The only trouble with my approach is that it makes a call to the API for each page, slowing down build time and also potentially hitting the user call limits on the API.
It would be possible to return all the data in a single call and store it locally, and then look up the pageview count from each page, but my Jekyll/Ruby-fu isn't up to scratch. I do not know how to write the plugin to run once to get all the data and store it locally where my current function could then access it, rather than calling the API page by page.
Basically my code is written as a liquid block that can be put into my page layout:
class GoogleAnalytics < Liquid::Block
def initialize(tag_name, markup, tokens)
super # options that appear in block (between tag and endtag)
#options = markup # optional optionss passed in by opening tag
end
def render(context)
path = super
# Read in credentials and authenticate
cred = YAML.load_file("/home/cboettig/.garb_auth.yaml")
Garb::Session.api_key = cred[:api_key]
token = Garb::Session.login(cred[:username], cred[:password])
profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == cred[:ua]}
# place query, customize to modify results
data = Exits.results(profile,
:filters => {:page_path.eql => path},
:start_date => Chronic.parse("2011-01-01"))
data.first.pageviews
end
Full version of my plugin is here
How can I move all the calls to the API to some other function and make sure jekyll runs that once at the start, and then adjust the tag above to read that local data?
EDIT Looks like this can be done with a Generator and writing the data to a file. See example on this branch Now I just need to figure out how to subset the results: https://github.com/Sija/garb/issues/22
To store the data, I had to:
Write a Generator class (see Jekyll wiki plugins) to call the API.
Convert data to a hash (for easy lookup by path, see 5):
result = Hash[data.collect{|row| [row.page_path, [row.exits, row.pageviews]]}]
Write the data hash to a JSON file.
Read in the data from the file in my existing Liquid block class.
Note that the block tag works from the _includes dir, while the generator works from the root directory.
Match the page path, easy once the data is converted to a hash:
result[path][1]
Code for the full plugin, showing how to create the generator and write files, etc, here
And thanks to Sija on GitHub for help on this.

How do I embed multiple documents in MongoDB using Ruby API?

I'm trying to insert a document that has multiple embedded documents but I have been unable to determine the structure for such a document.
I'm using Mongoid in most places but need to perform a batch document insert.
I've tried the following:
def build_records_array(records)
records.collect do |record|
record.raw_attributes["identifier"] = record.identifiers.collect { |identifier| identifier.raw_attributes }
record.raw_attributes
end
end # self.build_records_array
However the identifiers don't show up as embedded documents when I call insert. I just get a bunch of garbage in my parent document.
What is the proper structure for embedded documents?
So, I just had a typo. I wasn't thinking about Mongoid when looking at my problem. After playing around with the Mongo Driver to retrieve records Mongoid had created I discovered that I had everything right but the attribute name.
def build_records_array(records)
records.collect do |record|
record.raw_attributes["identifiers"] = record.identifiers.collect { |identifier| identifier.raw_attributes }
record.raw_attributes
end
end # build_records_array

Accessing SOAP Service with soap4r not able to access contents of returned objects

So I need to access this service from my rails app. I'm using soap4r to read the WSDL and dynamically generate methods for accessing the service.
From what I've read, I should be able to chain methods to access the nested XML nodes, but I can't get it to work. I tried using the wsdl2ruby command and read through the generated code. From what I can tell, the soap library is not generating these accessor methods. I'm pretty new to ruby, so I don't know if I'm just missing something?
I know when I inspect the element, I can see the data I want. I just can't get to it.
For instance if I use the following code:
require "soap/wsdlDriver"
wsdl = "http://frontdoor.ctn5.org/CablecastWS/CablecastWS.asmx?WSDL"
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
response = driver.getChannels('nill')
puts response.inspect
I get the following output:
ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}binding
ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}operation
ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}body
ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}address
#<SOAP::Mapping::Object:0x80b96394 {http://www.trms.com/CablecastWS/}GetChannelsResult=#<SOAP::Mapping::Object:0x80b96178 {http://www.trms.com/CablecastWS/}Channel=[#<SOAP::Mapping::Object:0x80b95f5c {http://www.trms.com/CablecastWS/}ChannelID="1" {http://www.trms.com/CablecastWS/}Name="CTN 5">, #<SOAP::Mapping::Object:0x80b9519c {http://www.trms.com/CablecastWS/}ChannelID="2" {http://www.trms.com/CablecastWS/}Name="PPAC 2">, #<SOAP::Mapping::Object:0x80b94620 {http://www.trms.com/CablecastWS/}ChannelID="14" {http://www.trms.com/CablecastWS/}Name="Test Channel">]>>
So the data is definitely there!
Here is the code generated by wsdl2ruby for the method being used above:
# {http://www.trms.com/CablecastWS/}GetChannels
class GetChannels
def initialize
end
end
# {http://www.trms.com/CablecastWS/}GetChannelsResponse
# getChannelsResult - ArrayOfChannel
class GetChannelsResponse
attr_accessor :getChannelsResult
def initialize(getChannelsResult = nil)
#getChannelsResult = getChannelsResult
end
end
Sorry for the long post, I figured the more info the more likely someone can point me in the right direction.
Thanks
-ray
Answer
require "soap/wsdlDriver"
wsdl = "http://frontdoor.ctn5.org/CablecastWS/CablecastWS.asmx?WSDL"
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
response = driver.getChannels('nill')
for item in response.getChannelsResult.channel
puts item.name
puts item.channelID
end
How I got the Answer
You can figure out the methods of response via
response.methods
This will get you a long list of methods that are hard to sort through, so I like to subtract out the generic methods. Ruby lets you subtract arrays.
response.methods - Object.new.methods
Using this technique, I found the getChannelsResult method for response. I repeated the process
resonse.getChannelsResult.methods - Object.new.methods
I found the channel method for its result. Again!
response.getChannelsResult.channel.methods - Object.new.methods
This returned a bunch of methods including: sort, min, max etc. So I guessed Array. A simple confirmation was in order
response.getChannelsResult.channel.class
Sure enough it returned Array. To make life simple, I just worked with the first item of the array to get its methods
response.getChannelsResult.channel.first.methods - Object.new.methods
Whoalla, I found two more methods "name" and "channelID"

Resources