Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

convert string to ActiveSupport::Duration

How to convert string to ActiveSupport::Duration?

in rails console this code works

Date.today + 1.month (or 22.days)

but this not work

Date.today + '1.month' 

it says TypeError: expected numeric

'1.month' comes from db record.

like image 907
Guru Avatar asked Dec 29 '15 07:12

Guru


4 Answers

If you can choose the string format, then you should use ActiveSupport::Duration.parse, using the ISO8601 duration format: https://api.rubyonrails.org/classes/ActiveSupport/Duration.html#method-c-parse

Example:

ActiveSupport::Duration.parse('P1M')
=> 1 month
like image 157
Gauthier Delacroix Avatar answered Sep 27 '22 20:09

Gauthier Delacroix


Yes, eval does the trick. But you should be sure the code in the database always is safe. If '22.days', '1.month' and so on come from user input, using eval is a huge security hole.

In this case try to use some kind of natural language parsers, like https://github.com/hpoydar/chronic_duration

like image 32
dimuch Avatar answered Sep 27 '22 20:09

dimuch


As others have pointed out, using eval on your string creates a security vulnerability.

Instead, you can convert your string to an ActiveSupport::Duration using .to_i on the first part of your string to convert it to an Integer, and then .send the second part of your string to the integer to convert it to a Duration. Like this:

parts = '1.month'.split('.')
parts.first.to_i.send(parts.last)

For convenience, you could extend the String class by adding this file config/initializers/string.rb:

# Helper extension to String class
class String
  # Convert strings like "3.days" into Duration objects (via Integer)
  def to_duration
    super unless self =~ /\A\d+\.\w+\z/
    parts = split('.')

    allowed = %w(minutes hours days months years) # whitelist for .send
    super unless allowed.include? parts.last

    parts.first.to_i.send(parts.last)
  end
end

Then in your app, you can call .to_duration on strings.

like image 35
Brent Avatar answered Sep 25 '22 20:09

Brent


In Rails month or 'months' are Integer methods.

So when you use:

1.month

You are applying Integer#month to 1 (which is an integer).

However '1.month' is just a string. You can write anything between quotes and they are treated a String and not evaluated in any way unless you specifically ask for it.

eval('1.month') is one such instance where you specifically ask for your String to get evaluated and hence you get the desired result. However this can be dangerous as you do not have any check over your input String.

You can find a lot of references as to why eval is nasty.

In case you are doing this because your input is a String instead of Integer, you can always convert it to Integer using to_i. Example:

time = "1"
time.to_i.month 
#=> 2592000

This will work even for Integer values:

time = 1
time.to_i.month 
=> 2592000
like image 24
shivam Avatar answered Sep 27 '22 20:09

shivam