Ruby collection out of order - ruby

I have the following:
BB_AREAS = {
:about => {:link => "quem somos", :slug => "quem-somos"},
:expositors => {:link => "expositores",:slug => "expositores"},
:map => {:link => "planta", :slug => "planta"},
:activities => {:link => "atividades",:slug => "atividades"},
:address => {:link => "como chegar",:slug => "como-chegar"},
:support => {:link => "apoio", :slug => "apoio"},
:optin => {:link => "cadastro",:slug => "cadastro"},
:how_expositors => {:link => "como expor",:slug => "como-expor"},
:press => {:link => "imprensa",:slug => "imprensa"},
:contact => {:link => "contato",:slug => "contato"},
}
BB_MENU_AREAS = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:link]}
BB_MENU_AREAS_SLUG = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:slug]}
And in the view I have the following:
=render :partial => '/shared/menu', :collection => BB_MENU_AREAS.map {|link, menu| {:link => link, :menu => menu}}, :spacer_template => '/shared/menu_separator'
I want the menu to render in the same order of BB_AREAS, but it is rendered in an arbitrary order.

Hashes are ordered by insertion order in Ruby 1.9+, otherwise they have an internal order.
IMO this data belongs in an array of actual objects, though; roughly:
class Area
attr_accessor :name, :link, :slug
def initialize(name, link, slug)
#name = name
#link = link
#slug = slig
end
end
BB_AREAS = [
Area.new("About", "quem somos", "quem-somos"),
Area.new("Expositors", "expositores", "expositores"),
# etc.
]
If you actually need to extract individual components in order you may.

Ruby Hashs are orderless in Ruby 1.8 and lower. However, in Ruby 1.9 and higher, Hashes are ordered. There is a backward compatible solution though:
BB_AREAS = [
[:about , {:link => "quem somos", :slug => "quem-somos"}],
[:expositors , {:link => "expositores",:slug => "expositores"}],
[:map , {:link => "planta", :slug => "planta"}],
[:activities , {:link => "atividades",:slug => "atividades"}],
[:address , {:link => "como chegar",:slug => "como-chegar"}],
[:support , {:link => "apoio", :slug => "apoio"}],
[:optin , {:link => "cadastro",:slug => "cadastro"}],
[:how_expositors , {:link => "como expor",:slug => "como-expor"}],
[:press , {:link => "imprensa",:slug => "imprensa"}],
[:contact , {:link => "contato",:slug => "contato"}],
]
BB_MENU_AREAS = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:link]}
BB_MENU_AREAS_SLUG = BB_AREAS.each_with_object({}) { |(k, v), h| h[k] = v[:slug]}
Nothing do change in your view. Additionally, in this data structure, instead of [key], you need to use .assoc(key)[1].

Related

Can't get value from nested pair in Omniauth hash.

I'll try to keep this simple, my previous wording was maybe a bit too verbose:
Here is the example Omniauth hash: https://github.com/mkdynamic/omniauth-facebook
I can access and save some values from this but not others. The field is writable, so I know its just my syntax (beginner, sorry!)
{
:provider => 'facebook',
:uid => '1234567',
:info => {
:nickname => 'jbloggs',
:email => 'joe#bloggs.com',
:name => 'Joe Bloggs',
:first_name => 'Joe',
:last_name => 'Bloggs',
:image => 'http://graph.facebook.com/1234567/picture?type=square',
:urls => { :Facebook => 'http://www.facebook.com/jbloggs' },
:location => 'Palo Alto, California',
:verified => true
},
:credentials => {
:token => 'ABCDEF...', # OAuth 2.0 access_token, which you may wish to store
:expires_at => 1321747205, # when the access token expires (it always will)
:expires => true # this will always be true
},
:extra => {
:raw_info => {
:id => '1234567',
:name => 'Joe Bloggs',
:first_name => 'Joe',
:last_name => 'Bloggs',
:link => 'http://www.facebook.com/jbloggs',
:username => 'jbloggs',
:location => { :id => '123456789', :name => 'Palo Alto, California' },
:gender => 'male',
:email => 'joe#bloggs.com',
:timezone => -8,
:locale => 'en_US',
:verified => true,
:updated_time => '2011-11-11T06:21:03+0000'
}
}
}
I can do this to get gender and save it.
location:auth.extra.raw_info["gender"]
Obviously though I dont want to save gender to location. I want to get "Palo Alto" and save it. But this doesn't work.
location.auth.extra.raw_info["location"]["name"]
What am I doing wrong? When I try it in console, I'm able to get the value.
try this
location.auth.extra.raw_info.location.name
or this
location.auth.extra.raw_info[:location][:name]
Yeah, what you suggested was what I was trying...and it turns out we were right but FB had changed how that hash was set up so it wasn't working. Lesson learned: subscribe FB's notifications next time :)

Create multi-dimensional array or hash in ruby from JSON

I am using Rhomobile and trying to dynamically build a hash for the id and title of the buttons has of the Alert.show_popup, but am not quite getting it. What I want the end result to be, in effect, is:
Alert.show_popup( {
:message => 'Please Select Appropriate Address:',
:title => 'Get Nearby...',
:icon => :info,
:buttons => [
{'id' => '1', 'title' => 'Address 1'},
{'id' => '2', 'title' => 'Address 2'},
{'id' => '3', 'title' => 'Address 3'},
{'id' => '4', 'title' => 'Address 4'}
],
:callback => url_for(:action => :on_addressidentified_popup)
}
)
I've tried a few methods, but none have worked (build a string that looks like a hash and try_convert, etc.). Here was the latest one I tried which seemed close, but yet still far away:
nearbyaddresses = Rho::JSON.parse(#params['body'])
h = {}
nearbyaddresses.each do |location|
h[intIndex] = {}
h[intIndex][:id] = intIndex.to_s
h[intIndex][:title] = location["Address"].to_s
intIndex = intIndex + 1
end
Alert.show_popup( {
:message => 'Please Select Appropriate Address:',
:title => 'Get Nearby...',
:icon => :info,
:buttons => h,
:callback => url_for(:action => :on_addressidentified_popup)
}
)
Any ruby wizards here that can help me out?
How about
nearby_addresses_list = Rho::JSON.parse(#params['body'])
buttons_list = nearby_addresses_list.collect.each_with_index {|address, i|
{'id' => i, 'title' => address} #not sure if you need to dig into this at all.
}
This should leave buttons_list with this value
[{'id' => 0, 'title' => nearby_addresses_list[0]},
{'id' => 1, 'title' => nearby_addresses_list[1]}
{'id' => 2, 'title' => nearby_addresses_list[2]}
{'id' => 3, 'title' => nearby_addresses_list[3]}]
If you want the id's to start with 1, change the body of the collect statement to {'id' => i+1, 'title' => address}
Then just add buttons_list in as the value for the key :buttons.
Alert.show_popup( {
:message => 'Please Select Appropriate Address:',
:title => 'Get Nearby...',
:icon => :info,
:buttons => buttons_list,
:callback => url_for(:action => :on_addressidentified_popup)
})
If you're seeing weirdness between the desired output you mentioned first and the code you said was close, is it perhaps that you used symbols for the keys in your code (:id), and strings in your desired output ("id") ?
Here's how I addressed the issue. Works like a charm...
intIndex = 0
nearbyaddresses = Rho::JSON.parse(#params['body'])
##nearbyAddresses = nearbyaddresses
button_array = []
nearbyaddresses.each do |location|
opt = {'id' => intIndex.to_s, 'title' => location["Address"] }
button_array << opt
intIndex = intIndex + 1
end
Alert.show_popup( {
:message => 'Please Select Appropriate Address:',
:title => 'Get Nearby...',
:icon => :info,
:buttons => button_array,
:callback => url_for(:action => :getselectedaddress)
}
)

How do I correctly call Magento SOAP API with Ruby Savon for Category Product Links?

I am trying to call the catalog_product_link.list API method using Savon. However, I keep receiving the error Error cannot find parameter.
Here is what I am using, though I have tried several variations of the call and still cannot get it to go through correctly:
client = Savon.client(wsdl: 'http://localhost/index.php/api/soap/?wsdl')
response = client.call(:login){message(username: 'user', apiKey: 'key')}
session = response.body[:login_response][:login_return]
#These all do not work
client.call(:call){message(:session => session, :method => 'catalog_product_link.list', :type => 'up_sell', :productId => '166')}
client.call(:call){message(:session => session, :method => 'catalog_product_link.list', :type => 'up_sell', :product => '166')}
client.call(:call){message(:sessionId => session, :resourcePath => 'catalog_product_link.list', :args => {:type => 'up_sell', :product => '166'})}
client.call(:call){message(:sessionId => session, :resourcePath => 'catalog_product_link.list', :args => {:type => 'up_sell', :productId => '166'})}
client.call(:call){message(:sessionId => session, :resourcePath => 'catalog_product_link.list', :arguments => {:type => 'up_sell', :product => '166'})}
Is there a different way to format to get this to work?
UPDATE: If I try removing the type parameter, it gives the error Given invalid link type, so it appears it does not like something about multiple parameters.
response = client.call(:call){message(:session => session, :method => 'catalog_product_link.list', :product => '166')}
I was able to get this to work using Builder:
class ServiceRequest
def initialize(session, type, product)
#session = session
#type = type
#product = product
end
def to_s
builder = Builder::XmlMarkup.new()
builder.instruct!(:xml, encoding: "UTF-8")
builder.tag!(
"env:Envelope",
"xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/",
"xmlns:ns1" => "urn:Magento",
"xmlns:ns2" => "http://xml.apache.org/xml-soap",
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
) do |envelope|
envelope.tag!("env:Body") do |body|
body.tag!("ns1:call") do |call|
builder.sessionId(#session, "xsi:type" => "xsd:string")
builder.resourcePath("catalog_product_link.list", "xsi:type" => "xsd:string")
builder.args("xsi:type" => "ns2:Map") do |args|
args.item do |item|
item.key("type", "xsi:type" => "xsd:string")
item.value(#type, "xsi:type" => "xsd:string")
end
args.item do |item|
item.key("product", "xsi:type" => "xsd:string")
item.value(#product, "xsi:type" => "xsd:string")
end
end
end
end
end
builder.target!
end
end
client.call(:call, xml: ServiceRequest.new(session, 'up_sell', '166').to_s)
Thanks to #rubiii for the direction.

Ruby - multidimensional hash

How do I make this multidimensional? Each search result entry has multiple attributes :attributes => ['sAMAccountName','givenName','SN','mail']. and there can be many entries in the result. This code is good for creating only one entry with multiple attributes.
def self.Find(attribute, loginID)
conn = Net::LDAP.new :host => SERVER,
:port => PORT,
:base => BASE,
:auth => {
:username => 'admin',
:password => 'admin',
:method => :simple
}
if conn.bind
result = HashWithIndifferentAccess.new
conn.search( :base => LDAPBASE,
:filter => Net::LDAP::Filter.eq( attribute, loginID+"*" ),
:attributes => ['sAMAccountName','givenName','SN','mail'],
:return_result => true
).each do |entries|
entries.each do |attribute, values|
values.each do |value|
result[attribute] = value
end
end
end
result
end
end
The actual response I get from ldap is like this -
puts result.to_s
{
"dn"=>"CN=somename\\, somefirstname,OU=Users,DC=site,DC=com",
"sn"=>"somename",
"givenname"=>"somefirstname",
"samaccountname"=>"someuserid",
"mail"=>"someone#somthing.com"
}
Since the search is has * wildcard to find all matching entries. Ldap will return multiple entries in the format above. similar to this -
{
"dn"=>"CN=somename\\, somefirstname1,OU=Users,DC=site,DC=com",
"sn"=>"somename1",
"givenname"=>"somefirstname1",
"samaccountname"=>"someuserid1",
"mail"=>"someone1#somthing.com"
},
{
"dn"=>"CN=somename\\, somefirstname2,OU=Users,DC=site,DC=com",
"sn"=>"somename2",
"givenname"=>"somefirstname2",
"samaccountname"=>"someuserid2",
"mail"=>"someone2#somthing.com"
},
{
"dn"=>"CN=somename\\, somefirstname3,OU=Users,DC=site,DC=com",
"sn"=>"somename3",
"givenname"=>"somefirstname3",
"samaccountname"=>"someuserid3",
"mail"=>"someone3#somthing.com"
},
A[a=>1,b=>11,c=>111]
B[a=>2,b=>22,c=>222]
C[a=>3,b=>33,c=>333]
D[a=>4,b=>44,c=>444]
I'm not sure that I understood the question.
def self.Find(attribute, loginID)
conn = Net::LDAP.new :host => SERVER,
:port => PORT,
:base => BASE,
:auth => {
:username => 'admin',
:password => 'admin',
:method => :simple
}
if conn.bind
conn.search( :base => LDAPBASE,
:filter => Net::LDAP::Filter.eq( attribute, loginID+"*" ),
:attributes => ['sAMAccountName','givenName','SN','mail'],
:return_result => true
).reduce(Array.new) do |acc, el|
#
# Any modification of the entry must be here
#
acc + [el]
end
end
end
end
I think Array.map is good choice too
My assumption:
conn.search return Array of Hash
the form of result you want:
[
{"uid":"123","displayName":"User 123","mail":"123#example.com"},
{"uid":"456","displayName":"User 456","mail":"456#example.com"},
{"uid":"789","displayName":"User 789","mail":"789#example.com"}
]

Getting key value in nested Hash by key array values

Sample hash:
{
"audio" => {
"audio/aac" => ["aac"],
"audio/mpeg" => ["mp3", "mp2"],
"audio/mp4" => ["m4a", "m4b", "m4r", "3gp"],
"audio/ogg" => ["ogg", "oga"],
"audio/flac" => ["flac"],
"audio/speex" => ["spx"],
"audio/x-ms-wma" => ["wma"],
"audio/x-pn-realaudio" => ["rm", "ram"],
"audio/vnd.wave" => ["wav"],
"audio/x-musepack" => ["mpc", "mp+", "mpp"],
"audio/x-aiff" => ["aiff", "aif", "aifc"],
"audio/x-tta" => ["tta"]
},
"video" => {
"video/mp4" => ["mp4"],
"video/mpeg" => ["mpg", "mpeg"],
"video/x-m4v" => ["m4v"],
"video/quicktime" => ["mov"],
"video/x-msvideo" => ["avi"],
"video/x-flv" => ["flv"],
"video/webm" => ["webm"]
}
}
What's the best way given a file extension to get the associated content type (first match is okay)?
Searching for "flac" should return "audio/flac".
Currently I'm using this:
hsh.each_key do |group|
hsh[group].each do |k,v|
return k if v.include?(extension)
end
end
Unraveling that sort of structure is best done when it's created. But, you can loop through the various levels and get something useful from it. If I assign your initial hash to mime_hash I can unravel it using:
Hash[*mime_hash.map{ |av, types| types.map{ |mime_type, extensions| extensions.product([mime_type]) } }.flatten]
or more verbosely:
Hash[
*mime_hash.map{ |av, types|
types.map{ |mime_type, extensions|
extensions.product([mime_type])
}
}.flatten
]
Which will return:
{
"aac" => "audio/aac",
"mp3" => "audio/mpeg",
"mp2" => "audio/mpeg",
"m4a" => "audio/mp4",
"m4b" => "audio/mp4",
"m4r" => "audio/mp4",
"3gp" => "audio/mp4",
"ogg" => "audio/ogg",
"oga" => "audio/ogg",
"flac" => "audio/flac",
"spx" => "audio/speex",
"wma" => "audio/x-ms-wma",
"rm" => "audio/x-pn-realaudio",
"ram" => "audio/x-pn-realaudio",
"wav" => "audio/vnd.wave",
"mpc" => "audio/x-musepack",
"mp+" => "audio/x-musepack",
"mpp" => "audio/x-musepack",
"aiff" => "audio/x-aiff",
"aif" => "audio/x-aiff",
"aifc" => "audio/x-aiff",
"tta" => "audio/x-tta",
"mp4" => "video/mp4",
"mpg" => "video/mpeg",
"mpeg" => "video/mpeg",
"m4v" => "video/x-m4v",
"mov" => "video/quicktime",
"avi" => "video/x-msvideo",
"flv" => "video/x-flv",
"webm" => "video/webm"
}
As you've already realized the data structure you have is horrible to search in the fashion you want. Assuming you're going to be searching the same data over and over what you should do is create an index for it.
There are many ways of doing this but the simplest is probably just to flatten the hash and invert it so that your keys become values and vice-versa. That way you can simply search it by calling content_types['flac']
A section of the example hash might end up like this:
{
"aac" => "audio/aac",
"mp3" => "audio/mpeg",
"mp2" => "audio/mpeg",
"m4a" => "audio/mp4",
"m4b" => "audio/mp4",
"m4r" => "audio/mp4",
"3gp" => "audio/mp4",
"flac" => "audio/flac"
}
Try using rassoc()
Definition:
Searches through the hash comparing obj with the value using ==. Returns the first key-value pair (two-element array) that matches. See also Array#rassoc.
a = {1=> "one", 2 => "two", 3 => "three", "ii" => "two"}
a.rassoc("two") #=> [2, "two"]
a.rassoc("four") #=> nil

Resources