Converting filesize string to kilobyte equivalent in Rails - ruby-on-rails

My objective is to convert form input, like "100 megabytes" or "1 gigabyte", and converts it to a filesize in kilobytes I can store in the database. Currently, I have this:
def quota_convert
#regex = /([0-9]+) (.*)s/
#sizes = %w{kilobyte megabyte gigabyte}
m = self.quota.match(#regex)
if #sizes.include? m[2]
eval("self.quota = #{m[1]}.#{m[2]}")
end
end
This works, but only if the input is a multiple ("gigabytes", but not "gigabyte") and seems insanely unsafe due to the use of eval. So, functional, but I won't sleep well tonight.
Any guidance?
EDIT: ------
All right. For some reason, the regex with (.*?) isn't working correctly on my setup, but I've worked around it with Rails stuff. Also, I've realized that bytes would work better for me.
def quota_convert
#regex = /^([0-9]+\.?[0-9]*?) (.*)/
#sizes = { 'kilobyte' => 1024, 'megabyte' => 1048576, 'gigabyte' => 1073741824}
m = self.quota.match(#regex)
if #sizes.include? m[2].singularize
self.quota = m[1].to_f*#sizes[m[2].singularize]
end
end
This catches "1 megabyte", "1.5 megabytes", and most other things (I hope). It then makes it the singular version regardless. Then it does the multiplication and spits out magic answers.
Is this legit?
EDIT AGAIN: See answer below. Much cleaner than my nonsense.

You can use Rails ActiveHelper number_to_human_size.

def quota_convert
#regex = /([0-9]+) (.*)s?/
#sizes = "kilobytes megabytes gigabytes"
m = self.quota.match(#regex)
if #sizes.include? m[2]
m[1].to_f.send(m[2])
end
end
Added ? for optional plural in the regex.
Changed #sizes to a string of plurals.
Convert m[1] (the number to a float).
Send the message m[2] directly

why don't you simply create a hash that contains various spellings of the multiplier as the key and the numerical value as the value? No eval necessary and no regexs either!

First of all, changing your regex to #regex = /([0-9]+) (.*?)s?/ will fix the plural issue. The ? says match either 0 or 1 characters for the 's' and it causes .* to match in a non-greedy manner (as few characters as possible).
As for the size, you could have a hash like this:
#hash = { 'kilobyte' => 1, 'megabyte' => 1024, 'gigabyte' => 1024*1024}
and then your calculation is just self.quota = m[1].to_i*#hash[m2]
EDIT: Changed values to base 2

Related

Is there a way to shorten the variable value text in conditionals or in general in ruby?

I'm new to Ruby and I am building a web scraper. I have a variable that is assigned a value if a conditional is true.
The problem is that the value of the variable is really long and I'd like to avoid repeating myself with these long values.
I am using conditionals because the number of data that exists is not a static figure.
#Grab the top 3 comps if they exist
#comp1
if b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[1]/td[13]/span').exists?
comp1 = b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[1]/td[13]/span')
end
#comp2
if b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[2]/td[13]/span').exists?
comp2 = b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[2]/td[13]/span')
end
#comp3
if b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[3]/td[13]/span').exists?
comp3 = b.element(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[3]/td[13]/span')
end
Is there a way to decrease it the length of that such as
if "telement with really long xpath location on the webpage that we are checking to see if it is true ".exists?
x = "That conditional referenced above"
end
Since you're just replacing a single number in that long xpath selector you can use a template string:
elements = (1..3).map do |x|
b.element(
xpath: '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr[%d]/td[13]/span' % x
)
end.select(&:exists?)
See Kernel#sprintf for the options which are pretty much identical to the venerable C sprintf function.
Break up the string, either literally, or logically:
# literally
table_xpath = '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table'
if b.element(:xpath => "#{table_xpath}/tbody/tr[1]/td[13]/span").exists?
#...
end
# logically
table = b.element(xpath: '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table')
if table.element(xpath: "tbody/tr[1]/td[13]/span").exists?
end
break it up as many or as few times as you feel like to make the code read well.
You can directly write WATIR CODE as shown below, you have to use elements instead of element
b.elements(:xpath => '/html/body/form/div[3]/div[6]/table/tbody/tr/td/div[2]/div[3]/div[3]/div/div/div[1]/table/tbody/tr')
.take(3)
.map{|tr|tr.element(xpath: "./td[13]/span")}
But still, the above code is not optimized, you can write the below code Once you located the table, For the below code, I assume the table number is 2.
b.table(index: 2)
.rows
.to_enum
.take(3)
.map{|row| row.cell(index: 13).span}

Ruby/Rails - Converting an integer into a float excluding existing zeros

There must be a simple way to achieve this, I have an DB field containing an integer and I want to reformat it into a float to display.
As an integer my value looks like 6500 and I want it to display as 65.00
Within my model I have attempted to achieve this by creating the following method
def get_payment_amount_as_number
amount = self.payment_amount
return '%.02f' % self.payment_amount.to_f
end
Which results in the following being displayed: 6500.00
What would the best approach be to either strip the initial zeroes or to simply insert a decimal point?
Whilst I imagine this a ruby related question, I am not sure if rails has a handy helper already in place?
Thank you.
You could divide the number by 100:
payment_amount = 6595
'%.02f' % payment_amount.fdiv(100)
#=> "65.95"
'%.02f' % (payment_amount / 100.0)
#=> "65.95"
Or you could convert the number to a string and insert a decimal point:
payment_amount.to_s.rjust(3, '0').insert(-3, '.')
#=> "65.95"
Rails also provides several helpers to format numbers:
number_to_currency(65.95)
#=> "$65.95"
number_to_currency(1000)
#=> "$1,000.00"
And you might want to take a look at the money-rails gem which provides a mapping from cents to money objects.
You do this simply ...
def get_payment_amount_as_number
amount = self.payment_amount / 100
#to convert amount to float
amount.to_f
end
I find another one
amount = self.payment_amount
# => 6500
ans = '%.2f' % (amount/100)
# => "65.00"
int_value = 6500
float_value = float_value = '%.2f' % (int_value / 100.0)
puts int_value: int_value, float_value: float_value
it's all!

Array range being recognised as string - unable to convert to integer

i've written a function within a model to scrape a site and store certain attributes within a separate model (story):
def get_content
request = HTTParty.get("#{url}")
doc = Nokogiri::HTML(request.body)
doc.css("#{anchor}")["#{range}"].each do |entry|
story = self.stories.new
story.title = entry.text
story.url = entry[:href]
story.save
end
This uses the url, anchor, and range attributes of a Sections variable. The range attribute is stored as an array range - i.e. 0..2 or 11..13 - however, I'm being told that it can't convert a string into a variable. I've tried storing range as an integer and as a string, but both fail.
I realise I could input the beginning and end of the range as two separate integers in my db, and put ["#{beginrange}".."#{endrange}"] but this seems a messy way of doing it.
Any other ideas? Many thanks in advance
Hmm if you are sure that the range is always a string like '1..2' ('<Integer >..<Integer>'), you can use the eval method:
In my IRB console:
1.9.3p0 :032 > (eval "1..2").each { |l| puts l }
1
2
=> 1..2
1.9.3p0 :033 > (eval "1..2").inspect
=> "1..2"
1.9.3p0 :034 > (eval "1..2").class
=> Range
In your case:
doc.css("#{anchor}")[eval(range)].each do |entry|
#...
end
But eval is kind of dangerous. If you are sure that the range attribute is a Range as a String (validations and Regex are here to help), you can use eval without risk.
There's a couple things I see wrong.
["#{beginrange}".."#{endrange}"] creates a range of characters, not a range of integers, which Array[] needs:
beginrange = 1
endrange = 2
["#{beginrange}".."#{endrange}"]
=> ["1".."2"]
[beginrange..endrange]
=> [1..2]
But, you're storing the representation of the array range you need as a string. If I had a string representation of a range, I'd use this:
range_value = '1..2'
[Range.new(*range_value.scan(/\d+/).map(&:to_i))]
=> [1..2]
Or, if there was a chance I'd encounter an exclusive-range:
[Range.new(*range_value.scan(/\d+/).map(&:to_i), range_value['...'])]
=> [1..2]
range_value = '1...2'
[Range.new(*range_value.scan(/\d+/).map(&:to_i), range_value['...'])]
=> [1...2]
Those are all good when you can't trust your Range string representation's source, i.e., the value is coming from a form or a file someone else created. If you own the incoming value, or, for convenience, stored it as a string in a database, you can easily recreate the range using eval:
eval('1..2').class
=> Range
eval('1..2')
=> 1..2
eval('1...2')
=> 1...2
People are afraid of eval, because, used unwisely, it is dangerous. That doesn't mean we should avoid using it, instead, we should use it when it's safe.
You could use a regex to check the format of the string, raise an exception if it's not acceptable, then continue:
raise "Invalid range value received" if (!range_value[/\A\d+\s*\.{2,3}\s*\d+\z/])
[eval(range_value)]

Formatting a float to a minimum number of decimal places

I'm storing a decimal in rails and I need to have it display as a currency as such:
11.1230 => "$11.123"
11.1000 => "$11.10"
11.0100 => "$11.01"
11.1234 => "$11.1234"
Any easy way to do this?
def pad_number( number, min_decimals=2 )
s = "%g" % number
decimals = (s[/\.(\d+)/,1] || "").length
s << "." if decimals == 0
s << "0"*[0,min_decimals-decimals].max
end
puts [ 11.123, 11.1, 11.01, 11.1234, 11 ].map{ |n| pad_number(n) }
#=> 11.123
#=> 11.10
#=> 11.01
#=> 11.1234
#=> 11.00
Edit: Looks like this is Rails 3 specific, as Rails 2's number_with_precision method doesn't include the strip_insignificant_zeros option:
You can pass some options to number_to_currency (a standard Rails helper):
number_to_currency(11.1230, :precision => 10, :strip_insignificant_zeros => true)
# => "$11.123"
You need to provide a precision in order for the strip_insignificant_zeros option to work, though, otherwise the underlying number_with_precision method never gets called.
If you want to store as a float, you can use the number_to_currency(value) method in yours views for printing something that looks like $.
Correct me if I'm wrong (as I've rarely dealt with currency) but I think the conventional wisdom is to store dollar values as integers. That way you won't have to deal with funky float math.
So, convert it to three decimal fraction digits and then remove the final one if and only if it's a zero.
s.sub(/0$/, '')

Ruby (Rails) unescape a string -- undo Array.to_s

Have been hacking together a couple of libraries, and had an issue where a string was getting 'double escaped'.
for example:
Fixed example
> x = ['a']
=> ["a"]
> x.to_s
=> "[\"a\"]"
>
Then again to
\"\[\\\"s\\\"\]\"
This was happening while dealing with http headers. I have a header which will be an array, but the http library is doing it's own character escaping on the array.to_s value.
The workaround I found, was to convert the array to a string myself, and then 'undo' the to_s. Like so:
formatted_value = value.to_s
if value.instance_of?(Array)
formatted_value = formatted_value.gsub(/\\/,"") #remove backslash
formatted_value = formatted_value.gsub(/"/,"") #remove single quote
formatted_value = formatted_value.gsub(/\[/,"") #remove [
formatted_value = formatted_value.gsub(/\]/,"") #remove ]
end
value = formatted_value
... There's gotta be a better way ... (without needing to monkey-patch the gems I'm using). (yeah, this break's if my string actually contains those strings.)
Suggestions?
** UPDATE 2 **
Okay. Still having troubles in this neighborhood, but now I think I've figured out the core issue. It's serializing my array to json after a to_s call. At least, that seems to be reproducing what I'm seeing.
['a'].to_s.to_json
I'm calling a method in a gem that is returning the results of a to_s, and then I'm calling to_json on it.
I've edited my answer due to your edited question:
I still can't duplicate your results!
>> x = ['a']
=> ["a"]
>> x.to_s
=> "a"
But when I change the last call to this:
>> x.inspect
=> "[\"a\"]"
So I'll assume that's what you're doing?
it's not necessarily escaping the values - per se. It's storing the string like this:
%{["a"]}
or rather:
'["a"]'
In any case. This should work to un-stringify it:
>> x = ['a']
=> ["a"]
>> y = x.inspect
=> "[\"a\"]"
>> z = Array.class_eval(y)
=> ["a"]
>> x == z
=> true
I'm skeptical about the safe-ness of using class_eval though, be wary of user inputs because it may produce un-intended side effects (and by that I mean code injection attacks) unless you're very sure you know where the original data came from, or what was allowed through to it.

Resources