Using variables in an array passed to a def - Rails - ruby-on-rails

I'm using the Google charts API to generate a pie chart in my Rails application. However, I'm having a problem passing local variables to the def in the helper. The def takes a 2D array of [label, value] pairs. It doesn't like it when I try to pass a local variable in as the value. These are calculated ahead of time and are in currency format. Putting the variable in quotes or in #{} doesn't work either.
application_helper.rb
def pie_chart(data, options = {})
options[:width] ||= 250
options[:height] ||= 100
options[:colors] = %w(F5DD7E 0DB2AC FC8D4D FC694D FABA32 704948 968144 C08FBC ADD97E)
dt = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."
options[:divisor] ||= 1
options[:title] ||= "Energy Costs"
while (data.map { |k,v| v }.max / options[:divisor] >= 4096) do
options[:divisor] *= 20
end
opts = {
:cht => "p",
:chd => "e:#{data.map{|k,v|v=v/options[:divisor];dt[v/64..v/64]+dt[v%64..v%64]}}",
:chl => "#{data.map { |k,v| CGI::escape(k)}.join('|')}",
:chs => "#{options[:width]}x#{options[:height]}",
:chco => options[:colors].slice(0, data.length).join(','),
:chf => "bg,s,FFFFFF00",
:chtt => "#{options[:title]}"
}
The def call in the view:
<%= pie_chart( [["Light Cost", #{light_cost}], ["Small Appliance Cost", #{sm_appl_cost}]], :width => 550, :height => 200) %>
How do I pass the local variables?

What's the error you get? You should be able to call it like:
<%=
pie_chart(
[
["Light Cost", light_cost],
["Small Appliance Cost", sm_appl_cost]
],
{ :width => 550, :height => 200 }
)
%>
I added {}'s around the hash just to make it clearer and explicit.
Using #{var} is not the way to do it here because that's for substitution in a string, eg "Here is the value: #{var}".

So that code definitely wants numeric values, but #{} is just going to start a comment unless used inside a string, and you don't want a string there.
And actually, pie_chart as written would appear to take either a 2D array as you say OR a Hash, if the only use of it is with 2-variable .map iterators. So you should be able to use either [["str1", light_cost], ["str2", sm_appl_cost]] or { 'str1'=>light_cost, 'str2'=>sm_appl_cost}.
You do need to make sure that those locals are numeric, though. Try using .to_i or .to_f on them if they aren't.

Related

Searching using a single integer in a hash whose keys are ranges in Ruby

I have this hash in Ruby:
hash = {
0..25 => { low_battery_count: 13 },
26..75 => { average_battery_count: 4 },
76..100 => { good_battery_count: 4 }
}
Now, what is want is a method (preferably a built-in one) in which if I pass a value (integer e.g. 20, 30, etc), it should return me the value against the range in which this integer lies.
I have already solved it using each method but now, I want to optimize it,
even more, to do it without each or map methods to reduce its complexity.
For Example:
method(3) #=> {:low_battery_count=>13}
method(35) #=> {:average_battery_count=>4}
method(90) #=> {:good_battery_count=>4}
You can use find for this hash
BATTERIES = {
0..25 => { low_battery_count: 13 },
26..75 => { average_battery_count: 4 },
76..100 => { good_battery_count: 4 }
}
def get_batteries_count(value)
BATTERIES.find { |range, _| range.include?(value) }&.last
end
get_batteries_count(3) #=> {:low_battery_count=>13}
get_batteries_count(35) #=> {:average_battery_count=>4}
get_batteries_count(90) #=> {:good_battery_count=>4}
get_batteries_count(2000) #=> nil
You need to get exact key in order to fetch a value from the hash. So either way a passed number should be tested against the available keys (ranges).
You might consider introducing an optimisation based on assigning pre-determined ranges to the constants and using a case equality check:
SMALL = 0..25
MEDUIM = 26..75
LARGE = 76..100
def method(n)
case n
when SMALL then data[SMALL]
when MEDUIM then data[MEDUIM]
when LARGE then data[LARGE]
end
end
def data
#data ||= {
SMALL => { low_battery_count: 13 },
MEDUIM => { average_battery_count: 4 },
LARGE =>{ good_battery_count: 4 }
}
end
method(25)
=> {:low_battery_count=>13}
method(44)
=> {:average_battery_count=>4}
method(76)
=> {:good_battery_count=>4}
We can do this using some of built in methods but there is not any direct way to do this
class Hash
def value_at_key_range(range_member)
keys.select{ |range| range.include?(range_member) }.map{ |key| self[key]}
end
end
hash = {0..25=>{:low_battery_count=>13}, 26..75=>{:average_battery_count=>4}, 76..100=>{:good_battery_count=>4}}
p hash.value_at_key_range(10)
output
[{:low_battery_count=>13}]

Create hash from variables in loop

I want to end up with an array of hashes.
I am starting with an array of codes:
#codes = ['123', '456', '789']
I take each of those codes and hit an API with them and it returns values that I parse into variables in a loop, like so:
#codes.each do |x|
#y = x.get_some_data
#brand = #y[brand]
#size = #y[size]
end
Then I want to put this data into an array of hashes
merged_array = []
final_hash = #codes.map{|code| {:code => code, :brand=> #brand, :size=> #size}
merged_array << final_hash
And in a perfect world, end up with hashes that look like this in the merged_array:
{:code => '123', :brand=> 'nike', :size=> 8 }
{:code => '456', :brand=> 'adidas', :size=> 4 }
{:code => '789', :brand=> 'converse', :size=> 10 }
But when I run my script it maps the codes right, but overwrites the #brand, #size variables and just returns the values of the last loop.
Not sure how to get all my variables into the hashes?
In your code example, the variables are all declared as being instance variables, because they're prefixed with #.
However, the variables within the loop are simply working/temporary variables, not instance variables. Additionally, x.get_some_data is probably not working, since x is just the loop variable and contains 456, abc, etc., and not an object with the desired method. Thus, the following code should produce your desired result:
def get_data(codes)
result = []
codes.each do |code|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
result
end
This is a very verbose example; you can put the whole logic in map, if the return value of get_some_data permits it.
A more elegant version would utilize Enumerable#each_with_object (the Array class includes Enumereable):
def get_data(codes)
codes.each_with_object([]) do |code, result|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
end
Thanks, Cary Swoveland, for pointing this out!
This should do the work
#codes.map{|code| {:code => code, :brand => code.get_some_data['brand'], code.get_some_data['size']}}
But, I'm really not sure what String.get_some_data will give you. Eg:- '123'.get_some_data['brand']

How to write this Ruby function using a loop?

Is there a more elegant way to write this in Ruby, maybe using a loop?
def save_related_info
update_column(:sender_company_name, user.preference.company_name)
update_column(:sender_address, user.preference.address)
update_column(:sender_telephone, user.preference.telephone)
update_column(:sender_email, user.preference.email)
update_column(:sender_url, user.preference.url)
update_column(:sender_vat_number, user.preference.vat_number)
update_column(:sender_payment_details, user.preference.payment_details)
end
Thanks for any help.
Any reason you're not using update_attributes to do them all at once?
def save_related_info
update_attributes(
:sender_company_name => user.preference.company_name,
:sender_address => user.preference.address,
:sender_telephone => user.preference.telephone,
:sender_email => user.preference.email,
:sender_url => user.preference.url,
:sender_vat_number => user.preference.vat_number,
:sender_payment_details => user.preference.payment_details
)
end
def save_related_info
%w[company_name address telephone email url vat_number payment_details]
.each{|s| update_column("sender_#{s}".to_sym, user.preference.send(s))}
end
first guess is to put keys in and values in lists and then use loop. something like this:
keys = ['key1', 'key2', 'key3', 'key4']
values = [val1, val2, val3, val4]
keys.each_index do |i|
update_column(keys[i], values[i])
end
Minus in that approach is that the order of elements in values array should fit for order of keys. You could avoid it of using hash instead of arrays. Code will looks like this:
data = { "key1" => val1, "key2" => val2, "key3" => val3 };
data.each do |key, value|
update_column(keys, values)
end

Ruby way to loop and check subsequent values against each other

I have an array that contains dates and values. An example of how it might look:
[
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
Notice that some of the dates repeat. I'm trying to output this in a table format and I only want to show unique dates. So I loop through it with the following code to get my desired output.
prev_date = nil
#reading_schedule.reading_plans.each do |plan|
use_date = nil
if plan.assigned_date != prev_date
use_date = plan.assigned_date
end
prev_date = plan.assigned_date
plan.assigned_date = use_date
end
The resulting table will then look something like this
1/1/2010 aa
bb
1/2/2010 cc
dd
1/3/2010 ee
This work fine but I am new to ruby and was wondering if there was a better way to do this.
Enumerable.group_by is a good starting point:
require 'pp'
asdf = [
{'1/1/2010' => 'aa'},
{'1/1/2010' => 'bb'},
{'1/2/2010' => 'cc'},
{'1/2/2010' => 'dd'},
{'1/3/2010' => 'ee'}
]
pp asdf.group_by { |n| n.keys.first }.map{ |a,b| { a => b.map { |c| c.to_a.last.last } } }
# >> [{"1/1/2010"=>["aa", "bb"]}, {"1/2/2010"=>["cc", "dd"]}, {"1/3/2010"=>["ee"]}]
Which should be a data structure you can bend to your will.
I don't know as though it's better, but you could group the values by date using (e.g.) Enumerable#reduce (requires Ruby >= 1.8.7; before that, you have Enumerable#inject).
arr.reduce({}) { |memo, obj|
obj.each_pair { |key, value|
memo[key] = [] if ! memo.has_key?(key);
memo[key] << value
}
memo
}.sort
=> [["1/1/2010", ["aa", "bb"]], ["1/2/2010", ["cc", "dd"]], ["1/3/2010", ["ee"]]]
You could also use Array#each to similar effect.
This is totally a job for a hash.
Create a hash and use the date as the hashkey and an empty array as the hashvalue.
Then accumulate the values from the original array in the hashvalue array

what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key:
example:
{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==>
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }
Is there a helper method can do this?
using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)
eg:
>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"}
Leventix, thank you for your answer.
The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.
This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).
E.g.:
h = {
:youtube => {
:search => 'daffy', # nested symbol key
'history' => ['goofy', 'mickey'] # nested string key
}
}
Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix
JSON.parse( h.to_json, {:symbolize_names => true} )
=> { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } }
Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix
ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
=> { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }
Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!
Marshal.load( Marshal.dump(h) )
=> { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }
Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.
Cheers
There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.
require 'json'
def json_to_sym_hash(json)
json.gsub!('\'', '"')
parsed = JSON.parse(json)
symbolize_keys(parsed)
end
def symbolize_keys(hash)
hash.inject({}){|new_hash, key_value|
key, value = key_value
value = symbolize_keys(value) if value.is_a?(Hash)
new_hash[key.to_sym] = value
new_hash
}
end
As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.
Recursive method:
require 'json'
def JSON.parse(source, opts = {})
r = JSON.parser.new(source, opts).parse
r = keys_to_symbol(r) if opts[:symbolize_names]
return r
end
def keys_to_symbol(h)
new_hash = {}
h.each do |k,v|
if v.class == String || v.class == Fixnum || v.class == Float
new_hash[k.to_sym] = v
elsif v.class == Hash
new_hash[k.to_sym] = keys_to_symbol(v)
elsif v.class == Array
new_hash[k.to_sym] = keys_to_symbol_array(v)
else
raise ArgumentError, "Type not supported: #{v.class}"
end
end
return new_hash
end
def keys_to_symbol_array(array)
new_array = []
array.each do |i|
if i.class == Hash
new_array << keys_to_symbol(i)
elsif i.class == Array
new_array << keys_to_symbol_array(i)
else
new_array << i
end
end
return new_array
end
Of course, there is a json gem, but that handles only double quotes.
Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:
YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml)
=> {:test=>{"test"=>{":test"=>5}}}
Benefit of this approach it seems like a format that is better suited for REST services...
The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash
require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"
# on my_hash will have the json as a hash
my_hash = my_str.json
# or you can filter and get what you want
vals = my_str.json(:age, :city)
# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
If you think you might need both string and symbol keys:
JSON.parse(json_string).with_indifferent_access

Resources