I have a form where I put in hashes with regular expression values. My problem is that they gets messed up when travelling from my view, through my controller and into MongoDB with Mongoid. How do I preserve the regex'es?
Input examples:
{:regex1 => "^Something \(#\d*\)$"}
{:regex2 => "\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z"}
My formtastic view form looks like this:
= semantic_form_for resource, :html => {:class => "form-vertical"} do |r|
= r.inputs do
= r.input :value, :as => :text
= r.actions do
= r.action :submit
My controller create action takes in the params and handles it like this:
class EmailTypesController < InheritedResources::Base
def create
puts params[:email_type][:value] # => {:regex1 => "^Something \(#\d*\)$"} and
# {:regex2 => "\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z"}
puts params[:email_type][:value].inspect # => "{:regex1 => \"^Something \\(#\\d*\\)$\"}" and
# "{:regex2 => \"\\A[\\w+\\-.]+@[a-z\\d\\-.]+\\.[a-z]+\\z\"}"
params[:email_type][:value] = convert_to_hash(params[:email_type][:value])
puts params[:email_type][:value] # => {"regex1"=>"^Something (#d*)$"} and
# {"regex2"=>"A[w+-.]+@[a-zd-.]+.[a-z]+z"}
create! do |success, failure|
success.html {
redirect_to resource
}
failure.html {
render :action => :new
}
end
end
def convert_to_hash(string)
if string.match(/(.*?)=>(.*)\n*/)
string = eval(string)
else
string = string_to_hash(string)
end
end
def string_to_hash(string)
values = string.split("\r\n")
output = {}
values.each do |v|
val = v.split("=")
output[val[0].to_sym] = val[1]
end
output
end
end
Firing up the console and inspecting the values put in through Mongoid:
Loading development environment (Rails 3.2.12)
1.9.3p385 :001 > EmailType.all.each do |email_type|
1.9.3p385 :002 > puts email_type.value
1.9.3p385 :003?> end
{"regex1"=>"^Something (#d*)$"}
{"regex2"=>"A[w+-.]+@[a-zd-.]+.[a-z]+z"}
=> true
1.9.3p385 :004 >
The problem lies in ruby's evaluation of strings, which ignores useless escapes:
puts "^Something \(#\d*\)$".inspect
=>"^Something (#d*)$"
That is to say the eval simply ignores the backslash. Note that typically in ruby regexes aren't created using strings but through their own regex literal, so that
/^Something \(#\d*\)$/.inspect
=>"/^Something \\(#\\d*\\)$/"
Notice the double backslash instead of single. This means that eval has to receive two backslashes instead of one in the string, as it has to be eval'd into a single backslash character.
A quick and easy way to do this is to simply run a sub ob the string before the convert_to_hash call:
# A little confusing due to escapes, but single backslashes are replaced with double.
# The second parameter is confusing, but it appears that String#sub requires a few
# extra escapes due to backslashes also being used to backreference.
# i.e. \n is replaced with the nth regex group, so to replace something with the string
# "\n" yet another escape for backslash is required, so "\\n" is replaced with "\n".
# Therefore the input of 8 blackslashes is eval'd to a string of 4 backslashes, which
# sub interprets as 2 backslashes.
params[:email_type][:value].gsub!('\\', '\\\\\\\\')
this shouldn't be a problem unless you are using backslashes in the hash keys at some point, in which case more advanced matching would be needed to extract only the regex's and perform the substitution on them.