Cleaning up submitted emails in Ruby

So there I was the other day, adding a feature where users could invite their friends to join Fluency too. The UI is a textarea where the user is asked to enter the email addresses on separate lines, so that’s pretty simple to grab the emails from:

recipients = params[:contact_emails].split(/\s/)

But because the line separator is actually two characters (newline and carriage return) you end up with empty elements in the returned array:

["someone@example.com","","something@gmail.com"]

Enter Ruby; by passing a block to the array, it can test for empty strings and reject them:

recipients.reject! { |c| c.blank? }

which can be written in a more compact form thus:

recipients.reject!(&:empty?)

So how to check that they are actually emails?

Well, we can monkey patch the String class like so

class String
def looks_like_email
!! self.match(/\A[^@]+@([^@\.]+\.)+[^@\.]+\z/)
end
end

and then use a similar technique by passing a block to the array of cleaned strings to verify that they are indeed emails. The snippet above is a simple test – ensuing that there is a dot and an @ sign with at least one character surrounding them in the right order.

recipients.reject! { |c| not c.looks_like_email }

so for example

["someone@example.com","somethingwrong","something@gmail.com"]
becomes

["someone@example.com","something@gmail.com"]

So finally, what happens if someone enters commas between the items of the list?

Then we end up with them appended to our emails in a way that is very wrong and will wreck everything:

["someone@example.com,","something@gmail.com,"]

To get rid of this, one might think of using something like

recipients = params[:contact_emails].gsub(",",'')

and this would work, but it’s a bit inelegant; as is so often the case, there is an elegant solution built into Ruby, the delete method.

This removes all instances of the characters you pass to it. So for example:

"This is another nice mess you've gotten us into".delete('mess')
=> "Thi i anothr nic you'v gottn u into"

It doesn’t do quite what it looks like it will at first; rather than deleting just the word “mess”, it deletes all the “m”s, all the “e”s, and all the “s”s.

"Thi i anothr nic you'v gottn u into"

So we can simply chain this onto the first line before we call split:

recipients = params[:contact_emails].delete(',').split(/\s/)

Finally, suppose someone enters a list of emails with commas and no spaces? Like this:

something@example.com,somethingelse@example.com

After we delete the commas there will be no white space for the split!

So, let’s just make sure there always are spaces by replacing any commas with a comma and a space.

.gsub(",", ", ")

So the bit of code for our controller ends up like this:

recipients = params[:contact_emails].gsub(",", ", ").delete(',').split(/\s/)
recipients.reject!(&:empty?)
recipients.reject! { |c| not c.looks_like_email }

And it will take a list of email addresses which can either be space separated, comma and space separated, or on new lines and it will return a list of valid-looking emails.

Advertisements