Gsub for non-numeric characters is removing numeric characters as well - ruby-on-rails

Code:
puts "params[:phone] is " + params[:phone]
ph = params[:phone].gsub!(/\D/,'')
Rails.logger.info("ph is now " + ph + "\r\n")
Rails console:
params[:phone] is 808XXXXXXX <-- (REDACTED FOR PRIVACY)
Completed 500 Internal Server Error in 2383ms
TypeError (no implicit conversion of nil into String):
app/controllers/api_controller.rb:400:in `+'
That means that 'ph' is nil.
Wut?
That same gsub!(/\D/,'') part has been working fine for nearly a year, stripping hyphens, spaces, parentheses, etc.
Now it wants to completely strip everything.
I don't get it.
EDIT:
When I use this:
ph = params[:phone].gsub(/\D/,'')
I get the result that I expect, both with and without digits (eg. "8084445555" or "808-444-5555" I get the result "8084445555")
But I still want to know why. gsub! is going to replace params[:phone], I get that, but it should not be stripping non-numerics, and it is.

Here is the answer:
gsub will return the original string if it doesn't match anything. On the other hand, gsub! will return nil in such case.
If both methods match, they will return the string with the substitutions in place and there is nothing special about it, except that gsub! will modify the receiver object as you should already know.
Here are some examples that illustrate the facts. Pay special attention to the subjects and the returned values. You can try the following in irb if you want.
phone_number = "888-555-0110" #=> "888-555-0110"
stripped_pn = phone_number.gsub(/\D/, '') #=> "8885550110"
phone_number #=> "888-555-0110"
stripped_pn.gsub(/\D/, '') #=> "8885550110"
Here are the same examples with gsub!:
phone_number = "888-555-0110" #=> "888-555-0110"
stripped_pn = phone_number.gsub!(/\D/, '') #=> "8885550110"
phone_number #=> "8885550110"
stripped_pn.gsub!(/\D/, '') #=> nil

Related

Split a database query string using regex in ruby

I have a query string which I want to separate out
created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30' AND updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30' AND user_id = 5 AND status = 'closed'
Like this
created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30'
updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30'
user_id = 5
status = 'closed'
This is just an example string, I want to separate the query string dynamically. I know can't just split with AND because of the pattern like BETWEEN .. AND
You might be able to do this with regex but here's a parser that may work for your use case. It can surely be improved but it should work.
require 'time'
def parse(sql)
arr = []
split = sql.split(' ')
date_counter = 0
split.each_with_index do |s, i|
date_counter = 2 if s == 'BETWEEN'
time = Time.parse(s.strip) rescue nil
date_counter -= 1 if time
arr << i+1 if date_counter == 1
end
arr.select(&:even?).each do |index|
split.insert(index + 2, 'SPLIT_ME')
end
split = split.join(' ').split('SPLIT_ME').map{|l| l.strip.gsub(/(AND)$/, '')}
split.map do |line|
line[/^AND/] ? line.split('AND') : line
end.flatten.select{|l| !l.empty?}.map(&:strip)
end
This is not really a regex, but more a simple parser.
This works by matching a regex from the start of the string until it encounters a whitespace followed by either and or between followed by a whitespace character. The result is removed from the where_cause and saved in statement.
If the start of the string now starts with a whitespace followed by between followed by a whitespace. It is added to statement and removed from where_cause with anything after that, allowing 1 and. Matching stops if the end of the string is reached or another and is encountered.
If point 2 didn't match check if the string starts with a whitespace followed by and followed by a whitespace. If this is the case remove this from where_cause.
Finally add statement to the statements array if it isn't an empty string.
All matching is done case insensitive.
where_cause = "created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30' AND updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30' AND user_id = 5 AND status = 'closed'"
statements = []
until where_cause.empty?
statement = where_cause.slice!(/\A.*?(?=[\s](and|between)[\s]|\z)/mi)
if where_cause.match? /\A[\s]between[\s]/i
between = /\A[\s]between[\s].*?[\s]and[\s].*?(?=[\s]and[\s]|\z)/mi
statement << where_cause.slice!(between)
elsif where_cause.match? /\A[\s]and[\s]/i
where_cause.slice!(/\A[\s]and[\s]/i)
end
statements << statement unless statement.empty?
end
pp statements
# ["created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30'",
# "updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30'",
# "user_id = 5",
# "status = 'closed'"]
Note: Ruby uses \A to match the start of the string and \z to match the end of a string instead of the usual ^ and $, which match the beginning and ending of a line respectively. See the regexp anchor documentation.
You can replace every [\s] with \s if you like. I've added them in to make the regex more readable.
Keep in mind that this solution isn't perfect, but might give you an idea how to solve the issue. The reason I say this is because it doesn't account for the words and/between in column name or string context.
The following where cause:
where_cause = "name = 'Tarzan AND Jane'"
Will output:
#=> ["name = 'Tarzan", "Jane'"]
This solution also assumes correctly structured SQL queries. The following queries don't result in what you might think:
where_cause = "created_at = BETWEEN AND"
# TypeError: no implicit conversion of nil into String
# ^ does match /\A[\s]between[\s]/i, but not the #slice! argument
where_cause = "id = BETWEEN 1 AND 2 BETWEEN 1 AND 3"
#=> ["id = BETWEEN 1 AND 2 BETWEEN 1", "3"]
I'm not certain if I understand the question, particularly in view of the previous answers, but if you simply wish to extract the indicated substrings from your string, and all column names begin with lowercase letters, you could write the following (where str holds the string given in the question):
str.split(/ +AND +(?=[a-z])/)
#=> ["created_at BETWEEN '2018-01-01T00:00:00+05:30' AND '2019-01-01T00:00:00+05:30'",
# "updated_at BETWEEN '2018-05-01T00:00:00+05:30' AND '2019-05-01T00:00:00+05:30'",
# "user_id = 5",
# "status = 'closed'"]
The regular expression reads, "match one or more spaces, followed by 'AND', followed by one or more spaces, followed by a positive lookahead that contains a lowercase letter". Being in a positive lookahead, the lowercase letter is not part of the match that is returned.

How to resolve this error erb is throwing when passing a currency formatted string

I'm using a module within my Rails App to perform some actions and render a html file and save it to S3. So far so good, apart from the fact that I need to pass a currency variable to to be rendered and erb is throwing this error:
undefined method `/' for "3,395,000":String
Here's my code:
options = {
...
price: Money.new(#case.cash_price / 100.to_i, "DKK").format.to_s.html_safe,
...
}
And here's my module:
def generate_html(options)
require 'erb'
erb_file = "templates/banners/widesky.html.erb"
erb_str = File.read(erb_file)
...
#price = options[:price]
...
renderer = ERB.new(erb_str)
result = renderer.result(binding)
FileUtils.mkdir_p('temp') unless File.directory?('temp')
File.open('temp/index.html', 'w') do |f|
f.write(result)
end
'temp/index.html'
end
And I tried formatting the currency in different ways, but I always get the same error. Any ideas why?
EDIT
#case.cash_price originally is an Integer. I want to convert it to a string with commas (hence using Money to format it). The problem seems to be that erb doesn't like the formatted result and throw the above error.
If for some reason you cannot use any gem/helper, let's reinvent the wheel!
def to_currency(price_in_cents, currency=nil, decimal_separator = '.', thousand_separator = ',')
price_in_cents.to_s.rjust(3,'0').reverse.insert(2,decimal_separator).gsub(/(\d{3})(?=\d)/, '\1'+thousand_separator).reverse+(currency ? " #{currency}" : '')
end
puts to_currency(123456789, 'DKK')
puts to_currency(123456, '€', ',', ' ')
puts to_currency(1)
It outputs :
1,234,567.89 DKK
1 234,56 €
0.01
Note that price_in_cents should be either a String that looks like an Integer ("123456789") or an Integer (123456789), but not a preformatted String ("123,456.78") or a Float (1.23).
Finally, the resulting String is as unsafe as price_in_cents :
to_currency("unsafe_codejs")
=> "unsafe_code.js"
You don't have to specify html_safe on the result anyway, because nothing would be escaped in "1,234,567.89 DKK".
Original answer :
If cash_price is a String with commas, you need to remove the commas first, then convert it to a float, then divide by 100, and then convert the result to an Integer.
cash_price.to_s is to avoid getting errors if cash_price does come as a Numeric.
price: Money.new((#case.cash_price.to_s.delete(',').to_f/100).to_i, "DKK").format.to_s.html_safe
#case.cash_price is a string so you can't perform any mathematical operations on it. You would need to convert the value to an integer (3395000) rather than a comma delimited string as you currently have ('3,395,000').
A side note, 100.to_i is redundant as 100 is already an integer, unless you wanted to convert the equation to an integer, which would need brackets (#case.cash_price / 100).to_i.

How to check if a string contain valid hash

i encounter a problem when i want to validate the input string if it contain a valid hash before execute eval on it, for example:
"{:key=>true}" if i run eval it return a correct hash, but if i run eval on this string "{:key=>true" i get syntax error of expected token:
(eval):1: syntax error, unexpected end-of-input, expecting '}' {:redirect=>true ^
i did tried some basic validation but no luck so far.
so basically what i want to know is how to validate a that a string contain correct hash format.
You can't tell without actually parsing it as Ruby, and (assuming you trust the string), the simplest way of parsing it is to simply do the eval call, and handle the exception:
begin
hash = eval(string)
rescue SyntaxError
# It's not valid
end
This is precisely what exceptions are for, instead of littering your code with checks for whether operations will succeed, you just perform the operations, and handle errors that occur.
To validate the string you can use Kernel#eval + checking the type:
def valid_hash?(string)
eval(string).is_a?(Hash)
rescue SyntaxError
false
end
Usage:
string = "{:key=>true}"
valid_hash?(string)
#=> true
string = "I am not hash"
valid_hash?(string)
#=> false
I had a similar problem, but I don't like the eval solution, because it's not safe.
I used the JSON gem and modified the string to match the JSON syntax.
Assuming you only have symbol keys.
'key: value' to '"key": value'
':key => value' to '"key": value'
string1 = "{:key_1=>true,key_2:false}"
string2 = "{:key=>true}"
string3 = "no hash"
def valid_hash?(string)
begin
string = string.gsub(/(\w+):\s*([^},])/, '"\1":\2')
#=> "{:key_1=>true,\"key_2\":false}"
string = string.gsub(/:(\w+)\s*=>/, '"\1":')
#=> "{\"key_1\":true,\"key_2\":false}"
my_hash = JSON.parse(string, {symbolize_names: true})
#=> {:key_1=>true, :key_2=>false}
my_hash.is_a? Hash # or do whatever you want with your Hash
rescue JSON::ParserError
false
end
end
valid_hash? string1
#=> true
valid_hash? string2
#=> true
valid_hash? string3
#=> false

How to convert a find_by_sql hstore string to a hash in Ruby on Rails

This seems ludicrously simple but I cannot figure out how to convert a hash-string to a hash.
When I do a Answer.find_by_sql I get a string like this
deepthought = "\"answertolife\"=>\"42\""
But I cannot figure out how to turn that into a hash.
I have tried:
pry(main)> Hash[deepthought]
ArgumentError: odd number of arguments for Hash
pry(main)> JSON.parse deepthought
JSON::ParserError: 757: unexpected token at '"answertolife"=>"42"'
pry(main)> deepthought.to_json
=> "\"\\\"answertolife\\\"=>\\\"42\\\"\""
I saw How do I convert a String object into a Hash object?, but I still cannot figure it out.
Try this
eval("{ #{deepthought} }")
It wraps the deepthought string with curly brace { }, and then use eval
A bit late but if you need to convert a multiple entries this works great.
def hstore_to_hash(hstore)
values = {}
hstore.gsub(/"/, '').split(",").each do |hstore_entry|
each_element = hstore_entry.split("=>")
values[each_element[0]] = each_element[1]
end
values
end
Rails4 supports hstore out of the box so I'd probably handle the string casting the same way Rails4 does it. If you look inside the Rails4 PostgreSQL-specific casting code, you'll find string_to_hstore:
def string_to_hstore(string)
if string.nil?
nil
elsif String === string
Hash[string.scan(HstorePair).map { |k, v|
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
[k, v]
}]
else
string
end
end
and a little lower down in the same file, you'll find HstorePair:
HstorePair = begin
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
/(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
end
Stash that somewhere convenient (probably somewhere in lib/) and send your hstore strings through that string_to_hstore to unpack them into Hashes.
This seems to work but feels dirty.
JSON.parse "{ #{deepthought} }".gsub('=>', ':')

ruby on rails, replace last character if it is a * sign

I have a string and I need to check whether the last character of that string is *, and if it is, I need to remove it.
if stringvariable.include? "*"
newstring = stringvariable.gsub(/[*]/, '')
end
The above does not search if the '*' symbol is the LAST character of the string.
How do i check if the last character is '*'?
Thanks for any suggestion
Use the $ anchor to only match the end of line:
"sample*".gsub(/\*$/, '')
If there's the possibility of there being more than one * on the end of the string (and you want to replace them all) use:
"sample**".gsub(/\*+$/, '')
You can also use chomp (see it on API Dock), which removes the trailing record separator character(s) by default, but can also take an argument, and then it will remove the end of the string only if it matches the specified character(s).
"hello".chomp #=> "hello"
"hello\n".chomp #=> "hello"
"hello\r\n".chomp #=> "hello"
"hello\n\r".chomp #=> "hello\n"
"hello\r".chomp #=> "hello"
"hello \n there".chomp #=> "hello \n there"
"hello".chomp("llo") #=> "he"
"hello*".chomp("*") #=> "hello"
String has an end_with? method
stringvariable.chop! if stringvariable.end_with? '*'
You can do the following which will remove the offending character, if present. Otherwise it will do nothing:
your_string.sub(/\*$/, '')
If you want to remove more than one occurrence of the character, you can do:
your_string.sub(/\*+$/, '')
Of course, if you want to modify the string in-place, use sub! instead of sub
Cheers,
Aaron
You can either use a regex or just splice the string:
if string_variable[-1] == '*'
new_string = string_variable.gsub(/[\*]/, '') # note the escaped *
end
That only works in Ruby 1.9.x...
Otherwise you'll need to use a regex:
if string_variable =~ /\*$/
new_string = string_variable.gsub(/[\*]/, '') # note the escaped *
end
But you don't even need the if:
new_string = string_variable.gsub(/\*$/, '')

Resources