Convert and translate a Ruby hash into a human-readable string - ruby

I have the following Ruby hash:
{"limit"=>250, "days_ago"=>14, "days_ago_filter"=>"lt", "key"=>3}
I'd like to convert it to a human-readable string and translate some of the values as necessary:
Limit: 250 - Days Ago: 14 - Days Ago Filter: Less than - Key: D♯, E♭,
So lt, in this case, actually translates to Less than. and 3 for key translates to D♯, E♭.
I'm almost there with this:
variables.map {|k,v| "#{k.split('_').map(&:capitalize).join(' ')}: #{v}"}.join(' - ')
But translating those values is where I'm hitting a snag.

I'd suggest using hashes for mapping out the possible values, e.g.:
days_ago_filter_map = {
"lt" => "Less than",
# ...other cases here...
}
musical_key_map = {
3 => "D♯, E♭",
# ...other cases here...
}
Then you can switch on the key:
variables.map do |key, value|
label = "#{key.split('_').map(&:capitalize).join(' ')}"
formatted_value = case key
when "days_ago_filter" then days_ago_filter_map.fetch(value)
when "key" then musical_key_map.fetch(value)
else value
end
"#{label}: #{formatted_value}"
end.join(' - ')
Note that if you're missing anything in your maps, the above code will raise KeyNotFound errors. You can set a default in your fetch, e.g.
days_ago_filter_map.fetch(value, "Unknown filter")
musical_key_map.fetch(value, "No notes found for that key")

You can create YAML files too for such kind of mappings :
values_for_replacement = {
"lt" => "Less than",
3 => "D♯, E♭"
}
you can try following :
variables.map {|k,v|
value_to_be_replaced = values_for_replacement[v]
"#{k.humanize}: #{(value_to_be_replaced.present? ? value_to_be_replaced : v)}"}.join(' - ')

Related

How to sort given array list?

list = ["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02", "HM020", "HM021", "HM022", "HM023", "HM024", "HM025", "HM026", "HM027", "HM028", "HM029", "HM03", "HM030", "HM031", "HM032", "HM033", "HM034", "HM035", "HM036", "HM037", "HM038", "HM039", "HM04", "HM040", "HM041", "HM042", "HM043", "HM044", "HM045", "HM046", "HM047", "HM05", "HM06", "HM07", "HM08", "HM09"]
I want the display the results as ["HM00","HM01","HM002"...] but using sort method it is giving the below results
["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02"]
If every element has a number at the end
list.sort_by { |item| item.scan(/\d*$/).first.to_i }
match that number at the end, take the first one (because scan gives you an array of results), convert it to an integer
simpler
list.sort_by { |item| item[/\d*$/].to_i }
[] already takes the first match
There is a more general solution that will work with most strings that contain groups of numbers
number = /([+-]{0,1}\d+)/;
strings = [ '2', '-2', '10', '0010', '010', 'a', '10a', '010a', '0010a', 'b10', 'b2', 'a1b10c20', 'a1b2.2c2' ]
p strings.sort_by { |item| [item.split(number).each_slice(2).map {
|x| x.size == 1 ? [ x[0], '0' ] : [ x[0], x[1] ] }].map {|y| ret = y.inject({r:[],x:[]}) { |s, z| s[:r].push [ z[0], z[1].to_r]; s[:x].push z[1].size.to_s; s }; ret[:r] + ret[:x] }.flatten
}
You can adjust number to match the types of numbers you want to use: integers, floating point, etc.
There is some extra code to sort equal numbers by length so that '10' comes before '010'.

Parsing a string field

I have these Syslog messages:
N 4000000 PROD 15307 23:58:12.13 JOB78035 00000000 $HASP395 GGIVJS27 ENDED\r
NI0000000 PROD 15307 23:58:13.41 STC81508 00000200 $A J78036 /* CA-JOBTRAC JOB RELEASE */\r
I would like to parse these messages into various fields in a Hash, e.g.:
event['recordtype'] #=> "N"
event['routingcode'] #=> "4000000"
event['systemname'] #=> "PROD"
event['datetime'] #=> "15307 23:58:12.13"
event['jobid'] #=> "JOB78035"
event['flag'] #=> "00000000"
event['messageid'] #=> "$HASP395"
event['logmessage'] #=> "$HASP395 GGIVJS27 ENDED\r"
This is the code I have currently:
message = event["message"];
if message.to_s != "" then
if message[2] == " " then
array = message.split(%Q[ ]);
event[%q[recordtype]] = array[0];
event[%q[routingcode]] = array[1];
event[%q[systemname]] = array[2];
event[%q[datetime]] = array[3] + " " +array[4];
event[%q[jobid]] = message[38,8];
event[%q[flags]] = message[47,8];
event[%q[messageid]] = message[57,8];
event[%q[logmessage]] = message[56..-1];
else
array = message.split(%Q[ ]);
event[%q[recordtype]] = array[0][0,2];
event[%q[routingcode]] = array[0][2..-1];
event[%q[systemname]] = array[1];
event[%q[datetime]] = array[2] + " "+array[3];
event[%q[jobid]] = message[38,8];
event[%q[flags]] = message[47,8];
event[%q[messageid]] = message[57,8];
event[%q[logmessage]] = message[56..-1];
end
end
I'm looking to improve the above code. I think I could use a regular expression, but I don't know how to approach it.
You can't use split(' ') or a default split to process your fields because you are dealing with columnar data that has fields that have no whitespace between them, resulting in your array being off. Instead, you have to pick apart each record by columns.
There are many ways to do that but the simplest and probably fastest, is indexing into a string and grabbing n characters:
'foo'[0, 1] # => "f"
'foo'[1, 2] # => "oo"
The first means "starting at index 0 in the string, grab one character." The second means "starting at index 1 in the string, grab two characters."
Alternately, you could tell Ruby to extract by ranges:
'foo'[0 .. 0] # => "f"
'foo'[1 .. 2] # => "oo"
These are documented in the String class.
This makes writing code that's easily understood:
record_type = message[ 0 .. 1 ].rstrip
routing_code = message[ 2 .. 8 ]
system_name = message[ 10 .. 17 ]
Once you have your fields captured add them to a hash:
{
'recordtype' => record_type,
'routingcode' => routing_code,
'systemname' => system_name,
'datetime' => date_time,
'jobid' => job_id,
'flags' => flags,
'messageid' => message_id,
'logmessage' => log_message,
}
While you could use a regular expression there's not much gained using one, it's just another way of doing it. If you were picking data out of free-form text it'd be more useful, but in columnar data it tends to result in visual noise that makes maintenance more difficult. I'd recommend simply determining your columns then cutting the data you need based on those from each line.

refactor large switch-statement

Any suggestions for refactoring this ugly case-switch into something more elegant?
This method (in Ruby) returns a (short or full) description for Belgian provinces, given a zipcode.
def province(zipcode, short = false)
case zipcode
when 1000...1300
short ? 'BXL' : 'Brussel'
when 1300...1500
short ? 'WBR' : 'Waals-Brabant'
when 1500...2000, 3000...3500
short ? 'VBR' : 'Vlaams-Brabant'
when 2000...3000
short ? 'ANT' : 'Antwerpen'
when 3500...4000
short ? 'LIM' : 'Limburg'
when 4000...5000
short ? 'LIE' : 'Luik'
when 5000...6000
short ? 'NAM' : 'Namen'
when 6000...6600, 7000...8000
short ? 'HAI' : 'Henegouwen'
when 6600...7000
short ? 'LUX' : 'Luxemburg'
when 8000...9000
short ? 'WVL' : 'West-Vlaanderen'
when 9000..9999
short ? 'OVL' : 'Oost-Vlaanderen'
else
fail ArgumentError, 'Not a valid zipcode'
end
end
Based on suggestions from MiiinimalLogic i made a second version. It this preferable?
class Provincie
ProvincieNaam = Struct.new(:kort, :lang)
PROVINCIES = {
1000...1300 => ProvincieNaam.new('BXL', 'Brussel'),
1300...1500 => ProvincieNaam.new('WBR', 'Waals-Brabant'),
1500...2000 => ProvincieNaam.new('VBR', 'Vlaams-Brabant'),
2000...3000 => ProvincieNaam.new('ANT', 'Antwerpen'),
3000...3500 => ProvincieNaam.new('VBR', 'Vlaams-Brabant'),
3500...4000 => ProvincieNaam.new('LIM', 'Limburg'),
4000...5000 => ProvincieNaam.new('LIE', 'Luik'),
5000...6000 => ProvincieNaam.new('NAM', 'Namen'),
6000...6600 => ProvincieNaam.new('HAI', 'Henegouwen'),
6600...7000 => ProvincieNaam.new('LUX', 'Luxemburg'),
7000...8000 => ProvincieNaam.new('HAI', 'Henegouwen'),
8000...9000 => ProvincieNaam.new('WVL', 'West-Vlaanderen'),
9000..9999 => ProvincieNaam.new('OVL', 'Oost-Vlaanderen')
}.freeze
def self.lang(postcode)
provincie_naam(postcode).lang
end
def self.kort(postcode)
provincie_naam(postcode).kort
end
def self.provincie_naam(postcode)
PROVINCIES.each { |list, prov| return prov if list.cover?(postcode) }
fail ArgumentError, 'Geen geldige postcode'
end
private_class_method :provincie_naam
end
Personally, I'd specify the zip ranges & Province information in a different data structure a la Map of Range objects/Provinces, then use Ruby's Range methods to check if the result falls in the range with the range's method.
You could consider having just one range lookup, either as is done here or with a map structure, with a secondary lookup (probably in a map) from the short description to the long description.

How to I reference an array member in Ruby?

Given this array in Ruby:
myarray = [name: "John", age: 35]
How do I refer to the age?
I tried myarray[:age] but got an error can't convert Symbol into Integer
Update:
I was trying to simplify my question by extracting what I thought my problem is. I may not understand completely.
I'm experimenting with Dashing and trying to send a number to a meter widget. I've created a variable, 'response_raw' and am trying to send it in the third send event. Here's my code:
SCHEDULER.every '1m', :first_in => 0 do
# Get checks
url = "https://#{CGI::escape user}:#{CGI::escape password}#api.pingdom.com/api/2.0/checks"
`enter code here`response = RestClient.get(url, {"App-Key" => api_key})
response = JSON.parse(response.body, :symbolize_names => true)
if response[:checks]
checks = response[:checks].map { |check|
if check[:status] == 'up'
state = 'up'
last_response_time = "#{check[:lastresponsetime]}ms"
response_raw = check[:lastresponsetime]
else
state = 'down'
last_response_time = "DOWN"
response_raw = 0
end
{ name: check[:name], state: state, lastRepsonseTime: last_response_time, pt: response_raw }
}
else
checks = [name: "pingdom", state: "down", lastRepsonseTime: "-", pt: 0]
end
checks.sort_by { |check| check['name'] }
send_event('pingdom', { checks: checks })
send_event('pingdom-meter', { value: checks[:pt] })
end
In CoffeeScript [name: "John", age: 35] is an array containing single object with two properties (name and age).
Here is how it'll look in plain JavaScript:
myarray = [
{
name: "John",
age: 35
}
];
So, answering your question, to access an age you should take the first element of an array and then reference an age property:
myarray[0].age
or
myarray[0]['age']
But, judging from your question, your're probably using wrong data structure. Why don't you want to use a plain object instead of an array?
person = name: "John", age: 35
console.log "#{person.name}'s age is #{person.age}"
Update
It looks like your question is actually about Ruby and not about CoffeeScript. Though, my answer will remain the same.
To access an age you should take the first element of an array and then reference an age property:
myarray[0][:age]
Since myarray is an array, Ruby expects an integer index, but you're giving it symbol :age instead.
I finally figured it out with Leonid's help. Thank you.
I changed:
send_event('pingdom-meter', { value: checks[:pt] })
to
send_event('pingdom-meter', { value: checks[0][:pt] })

modify active record relation in ruby

I have my #albums which are working fine; pickung data up with albums.json.
What I'd like to do is to split this #albums in three parts.
I was thinking of something like { "ownAlbums" : [ ... ], "friendSubscriptions" : [ ... ], "otherSubscriptions" : [ ... ] } But I got several errors like
syntax error, unexpected tASSOC, expecting kEND #albums["own"] => #albums
when I tried
#albums["own"] => #albums
or
TypeError (Symbol as array index):
when I tried:
#albums[:otherSubscriptions] = 'others'
and so on.
I never tried something like this before but this .json is just a simple array ?
How can I split it in three parts ?
Or do I have to modify the active record to do so ? Because if so, I'd find another way than splitting.
Second Edit
I tried something like this and it's actually working:
#albums = [*#albums]
own = []
cnt = 0
#albums.each do |ownAlbum|
cnt = cnt.to_int
own[cnt] = ownAlbum
cnt=cnt+1
end
subs = Subscription.where(:user_id => #user.user_id)
#albums[0] = own
#albums[1] = subs
But where I have [0] and [1] I'D prefer Strings.
But then I get the error: TypeError (can't convert String into Integer):
How to get around that ?

Resources