"RuntimeError: string modified" when I try gsub! incoming string inside each - ruby

My method gets strings with some urls. Urls can have ascii only symbols or any another:
"тест non-latin url http://url.cоm and test only-latin url https://url.com"
I want to parse my incoming strings, and then change all matches in the original string as follows:
"тест non-url <b>[NON-LATIN LETTERS: http://url.cоm]</b> and test only-latin url https://url.com"
and pass it to the next method.
I used each with gsub!:
def validate_url(string)
validate_url = string.gsub(/((http|https):\/\/.*?)(\s|$|\n|\Z|\t|\r|<.)/)
validate_url.each do |link|
if link.ascii_only?
string.gsub!(link, "#{link}")
else
string.gsub!(link, "<b>[NON-LATIN LETTERS: #{link}]</b>")
end
end
end
I see RuntimeError: string modified error and don't understand why and how to fix that.

For whatever reason that string refuses to be modified. While we haven't yet figured out why, here's how you can sidestep the issue: don't modify the string in-place, return a new string.
def validate_url(string)
string.gsub(/((http|https):\/\/.*?)(\s|$|\n|\Z|\t|\r|<.)/) do |link|
if link.ascii_only?
"#{link}"
else
"<b>[NON-LATIN LETTERS: #{link}]</b>"
end
end
end
Then
clean = validate_url(string)

Related

Is ruby controller params escaped?

I have a controller with PUT method:
class UtilsController < ActionController::API
def update_user_password
email = params[:email]
password = params[:password]
new_password = params[:new_password]
puts("'#{password}'")
end
end
and use openapi/javascript and Postman to send password nRP63P#$
and in console it logs nRP63P\#$
Is params of controller escaped, and how to get real value? URI.unescape?
Reference is welcome.
Thank you.
Please compare output with p and puts
password = 'nRP63P#$'
p password # will print "nRP63P\#$"
puts password # will print nRP63P#$
You can unescape value with tools of Rack
I think you were looking exactly for this Rack::Utils#unescape method.
So in your case it will be
unescaped_password = Rack::Utils.unescape(params[:password])
Keep in mind that, ruby output by default escapes some special chars # is one of them, cause it is indicator of comment, in order to evaluate further code ruby escapes it. So to verify yourself you need to write it to STDOUT or to a file.
puts unescaped_password
File.open('test.txt', 'w') { |f| f.write(unescaped_password)
and then inspect your result.
Do not use Kernel#pp or Kernel#p or String#inspect cause all of them will print values with special chars escaped by ruby itself and can mislead you.

Ruby Caesar Cipher not changing the word

I'm creating a caesar cipher in Ruby based on the code from Rosetta Code, but my code only prints the original word, rather than the modified word.
Here's my code:
class String
ALPHABET = ("A".."Z").to_a
def caesar_cipher(num)
self.tr(ALPHABET.join, ALPHABET.rotate(num).join)
end
end
word = gets.chomp.to_s
encypted = "#{word}".caesar_cipher(8)
decrypted = "#{word}".caesar_cipher(-8)
puts "Encrypt or Decrypt?"
choice = gets.chomp.to_s.downcase
if choice == "encrypt"
puts encypted
elsif choice == "decrypt"
puts decrypted
else
puts "Invalid option"
end
Any help would be greatly appreciated!
You don't necessarily need to declare a constant ALPHABET but you can if you want. The tr method copies a String object and then it will replace characters in the String with specified characters. As mentioned, you're only saying to check for capital letters and replace those but you're taking a word and making it downcase:
word = gets.chomp.to_s.downcase
You can modify your method like this:
class String
def caeser_cipher(num)
self.tr('a-z',("a".."z").to_a.rotate(num).join)
end
Now, you get the encrypted string (which you can always convert to upcase if you want):
"hello".caeser_cipher(8)
=> "pmttw"
Your ALPHABET array only contains uppercase letters, thus your corresponding method only accounts for uppercase letters. You need to update ALPHABET to contain lowercase letters and then your code will work as expected.

Ruby Regex match method

I was giving this regex /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,}\z/ and I'm to use it in order to verify the validity of an email address.
This is my first Ruby code and I'm not sure how to do it. I was told to use the .match method, which returns MatchData object. But how do I go on verifying that the MatchData object confirms the validity?
I attempted using the following, but it seems that it's accepting any string, even not an email address.
#Register new handler
def register_handler
email_regex = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,}\z/
email = "invalid"
unless email =~ email_regex then
puts("Insert your e-mail address:")
email = gets
end
puts("email received")
end
What is the correct way to do this? Either using .match or the method I attempted above.
The regex matches email addresses:
'invalid' =~ email_regex
=> nil # which boolean value is false
'email#example.com' =~ email_regex
=> 0 # which boolean value is true
however:
"email#example.com\n" =~ email_regex
=> nil
The newline character \n is appended by gets to every input.
That is why, the until loop will run forever regardless of what you will type in the terminal. The matching result will always be nil because of the newline character.
Try using gets.chomp, which will trim the newline character and your code should work.
Try this:
Edited the regex and put () to capture group and the beginning and the end
re = /^([\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,})$/m
str = 'a#b.com
p#qasdf.com
adbadf#bwdsfqaf.com
....
a#bdotcom
aasdf.com
www.yahoo.com'
# Print the match result
str.scan(re) do |match|
puts match.to_s
end
Running sample code

Swap part of a string in Ruby

What's the easiest way in Ruby to interchange a part of a string with another value. Let's say that I have an email, and I want to check it on two domains, but I don't know which one I'll get as an input. The app I'm building should work with #gmail.com and #googlemail.com domains.
Example:
swap_string 'user#gmail.com' # >>user#googlemail.com
swap_string 'user#googlemail.com' # >>user#gmail.com
If you're looking to substitute a part of a string with something else, gsub works quite well.
Link to Gsub docs
It lets you match a part of a string with regex, and then substitute just that part with another string. Naturally, in place of regex, you can just use a specific string.
Example:
"user#gmail.com".gsub(/#gmail/, '#googlemail')
is equal to
user#googlemail.com
In my example I used #gmail and #googlemail instead of just gmail and googlemail. The reason for this is to make sure it's not an account with gmail in the name. It's unlikely, but could happen.
Don't match the .com either, as that can change depending on where the user's email is.
Assuming googlemail.com and gmail.com are the only two possibilities, you can use sub to replace a pattern with given replacement:
def swap_string(str)
if str =~ /gmail.com$/
str.sub("gmail.com","googlemail.com")
else
str.sub("googlemail.com","gmail.com")
end
end
swap_string 'user#gmail.com'
# => "user#googlemail.com"
swap_string 'user#googlemail.com'
# => "user#gmail.com"
You can try with Ruby gsub :
eg:
"user#gmail.com".gsub("gmail.com","googlemail.com");
As per your need of passing a string parameter in a function this should do:
def swap_mails(str)
if str =~ /gmail.com$/
str.sub('gmail.com','googlemail.com');
else
str.sub('googlemail.com','gmail.com');
end
end
swap_mails "vgmail#gmail.com" //vgmail#googlemail.com
swap_mails "vgmail#googlemail.com" ////vgmail#gmail.com
My addition :
def swap_domain str
str[/.+#/] + [ 'gmail.com', 'googlemail.com' ].detect do |d|
d != str.split('#')[1]
end
end
swap_domain 'user#gmail.com'
#=> user#googlemail.com
swap_domain 'user#googlemail.com'
#=> user#gmail.com
And this is bad code, imo.
String has a neat trick up it's sleeve in the form of String#[]:
def swap_string(string, lookups = {})
string.tap do |s|
lookups.each { |find, replace| s[find] = replace and break if s[find] }
end
end
# Example Usage
lookups = {"googlemail.com"=>"gmail.com", "gmail.com"=>"googlemail.com"}
swap_string("user#gmail.com", lookups) # => user#googlemail.com
swap_string("user#googlemail.com", lookups) # => user#gmail.com
Allowing lookups to be passed to your method makes it more reusable but you could just as easily have that hash inside of the method itself.

Ruby Regex not matching

I'm writing a short class to extract email addresses from documents. Here is my code so far:
# Class to scrape documents for email addresses
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
def EmailScraper.scrape(doc)
email_addresses = []
File.open(doc) do |file|
while line = file.gets
temp = line.scan(EmailRegex)
temp.each do |email_address|
puts email_address
emails_addresses << email_address
end
end
end
return email_addresses
end
end
if EmailScraper.scrape("email_tests.txt").empty?
puts "Empty array"
else
puts EmailScraper.scrape("email_tests.txt")
end
My "email_tests.txt" file looks like so:
example#live.com
another_example90#hotmail.com
example3#diginet.ie
When I run this script, all I get is the "Empty array" printout. However, when I fire up irb and type in the regex above, strings of email addresses match it, and the String.scan function returns an array of all the email addresses in each string. Why is this working in irb and not in my script?
Several things (some already mentioned and expanded upon below):
\z matches to the end of the string, which with IO#gets will typically include a \n character. \Z (upper case 'z') matches the end of the string unless the string ends with a \n, in which case it matches just before.
the typo of emails_addresses
using \A and \Z is fine while the entire line is or is not an email address. You say you're seeking to extract addresses from documents, however, so I'd consider using \b at each end to extract emails delimited by word boundaries.
you could use File.foreach()... rather than the clumsy-looking File.open...while...gets thing
I'm not convinced by the Regex - there's a substantial body of work already around:
There's a smarter one here: http://www.regular-expressions.info/email.html (clicking on that odd little in-line icon takes you to a piece-by-piece explanation). It's worth reading the discussion, which points out several potential pitfalls.
Even more mind-bogglingly complex ones may be found here.
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\Z/i # changed \z to \Z
def EmailScraper.scrape(doc)
email_addresses = []
File.foreach(doc) do |line| # less code, same effect
temp = line.scan(EmailRegex)
temp.each do |email_address|
email_addresses << email_address
end
end
email_addresses # "return" isn't needed
end
end
result = EmailScraper.scrape("email_tests.txt") # store it so we don't print them twice if successful
if result.empty?
puts "Empty array"
else
puts result
end
Looks like you're putting the results into emails_addresses, but are returning email_addresses. This would mean that you're always returning the empty array you defined for email_addresses, making the "Empty array" response correct.
You have a typo, try with:
class EmailScraper
EmailRegex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
def EmailScraper.scrape(doc)
email_addresses = []
File.open(doc) do |file|
while line = file.gets
temp = line.scan(EmailRegex)
temp.each do |email_address|
puts email_address
email_addresses << email_address
end
end
end
return email_addresses
end
end
if EmailScraper.scrape("email_tests.txt").empty?
puts "Empty array"
else
puts EmailScraper.scrape("email_tests.txt")
end
You used at the end \z try to use \Z according to http://www.regular-expressions.info/ruby.html it has to be a uppercase Z to match the end of the string.
Otherwise try to use ^ and $ (matching the start and the end of a row) this worked for me here on Regexr
When you read the file, the end of line is making the regex fail. In irb, there probably is no end of line. If that is the case, chomp the lines first.
regex=/\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
line_from_irb = "example#live.com"
line_from_file = line_from_irb +"/n"
p line_from_irb.scan(regex) # => ["example#live.com"]
p line_from_file.scan(regex) # => []

Resources