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.
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
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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With