Why does gsub fail for this pair of string and hash? - ruby-on-rails

I am trying to replace parts of the string by picking certain words and then replacing them with values corresponding to them, stored in a hash. The example I'm working with is
s = '{{content}}, {{expected}}, {{description}}'
h = {"contents"=>"new int[]{1, 2, 3, 4}", "expected"=>"new java.util.ArrayList<Integer>(){{add(4);add(3);add(2);add(1);}}", "description"=>"example"}
result = s.gsub(/\{\{(.*?)\}\}/) {|x| h[x]}
Basically trying to replace all keys inside double curly braces with their values but this does not seem to work and result remains ', , '. Not sure if the issue is with the way the hash is created or if the regex is written incorrectly.
Any help figuring this out is greatly appreciated. Thanks

s = '{{contents}}, {{expected}}, {{description}}'
result = s.gsub(/\{\{(.*?)\}\}/)
irb(main):013:0> result.as_json
=> ["{{contents}}", "{{expected}}", "{{description}}"]
Your key was like "{{contents}}" so when you access the h[x] the x would have the value with the curly braces. You need to run the following.
irb(main):014:0> result = s.gsub(/\{\{(.*?)\}\}/) {|x| h[x.delete('{}')]}
=> "new int[]{1, 2, 3, 4}, new java.util.ArrayList<Integer>(){{add(4);add(3);add(2);add(1);}}, example"
Also the first key should be contents rather than content

Related

convert my string to comma based elements

I am working on a legacy Rails project that relies on Ruby version 1.8
I have a string looks like this:
my_str = "a,b,c"
I would like to convert it to
value_list = "('a','b','c')"
so that I can directly use it in my SQL statement like:
"SELECT * from my_table WHERE value IN #{value_list}"
I tried:
my_str.split(",")
but it returns "abc" :(
How to convert it to what I need?
To split the string you can just do
my_str.split(",")
=> ["a", "b", "c"]
The easiest way to use that in a query, is using where as follows:
Post.where(value: my_str.split(","))
This will just work as expected. But, I understand you want to be able to build the SQL-string yourself, so then you need to do something like
quoted_values_str = my_str.split(",").map{|x| "'#{x}'"}.join(",")
=> "'a','b','c'"
sql = ""SELECT * from my_table WHERE value IN (#{quoted_values_str})"
Note that this is a naive approach: normally you should also escape quotes if they should be contained inside your strings, and makes you vulnerable for sql injection. Using where will handle all those edge cases correctly for you.
Under no circumstances should you reinvent the wheel for this. Rails has built-in methods for constructing SQL strings, and you should use them. In this case, you want sanitize_sql_for_assignment (aliased to sanitize_sql):
my_str = "a,b,c"
conditions = sanitize_sql(["value IN (?)", my_str.split(",")])
# => value IN ('a','b','c')
query = "SELECT * from my_table WHERE #{conditions}"
This will give you the result you want while also protecting you from SQL injection attacks (and other errors related to badly formed SQL).
The correct usage may depend what version of Rails you're using, but this method exists as far back as Rails 2.0 so it will definitely work even with a legacy app; just consult the docs for the version of Rails you're using.
value_list = "('#{my_str.split(",").join("','")}')"
But this is a very bad way to query. You better use:
Model.where(value: my_str.split(","))
The string can be manipulated directly; there is no need to convert it to an array, modify the array then join the elements.
str = "a,b,c"
"(%s)" % str.gsub(/([^,]+)/, "'\\1'")
#=> "('a','b','c')"
The regular expression reads, "match one or more characters other than commas and save to capture group 1. \\1 retrieves the contents of capture group 1 in the formation of gsub's replacement string.
couple of use cases:
def full_name
[last_name, first_name].join(' ')
end
or
def address_line
[address[:country], address[:city], address[:street], address[:zip]].join(', ')
end

Two way lookup of id vs name ruby

I have a hash in which id is the key and name is the value. Both id and value are unique.
Something like this:
h[1] = "ABC"
h[3] = "DEF"
So, if I am given the key of 1, I can easily return a value "ABC".
I need to do a reverse lookup as well, which means if I am given a value of "DEF", I should return 3.
Also, instead of a single value or single key to do the lookup,
I may be provided with an array of values or array of keys instead.
Should I implement two hashes, one for each, or is there any other way in ruby or rails to achieve that?
Edit: This question is not related to finding a key by its value in a hash. It is related to doing a two way lookup not in O(n) time with a better method other than creating two separate hashes.
You can use Hash#invert as below,
reversed_h = h.invert
reversed_h['DEF']
# => 3
You can get your key this way:
hash.key(value) => key
Hash#key
h = { 1 => 'ABC', 3 => 'DEF' }
puts h.key('DEF')
#=> 3

How to delete key/value pair if the same value is found in another key/value pair

I created a hash and initialized five key/value pairs:
my_hash = {}
my_hash[:some_key] = "hello world"
my_hash[:some_key2] = "apple"
my_hash[:some_key3] = "pear"
my_hash[:some_key4] = "hello world"
my_hash[:some_key5] = "pear"
> my_hash
=> {:some_key=>"hello world", :some_key2=>"apple", :some_key3=>"pear", :some_key4=>"hello world", :some_key5=>"pear"}
I want each key/value pair to be unique by its value. If another key/value pair has a matching value, then delete one of these matching key/value pairs (I don't care which key/value pair is ultimately deleted).
So for my example, I want to delete these two key/value pairs because there is already a key/value pair that has that specified value.
my_hash[:some_key4] = "hello world"
my_hash[:some_key5] = "pear"
To reiterate, it would be fine to delete the other matching key/value pairs instead. Ultimately, each key/value pair has to be unique by its value:
my_hash[:some_key] = "hello world"
my_hash[:some_key3] = "pear"
The returned hash should be:
> my_hash
=> {:some_key=>"hello world", :some_key2=>"apple", :some_key3=>"pear"}
How can I do this? I know about delete_if but I don't know how to look through the rest of the collection and see if the value exists in any of the other key/value pairs.
You can use Array#uniq and transformations to/from array.
my_hash = {
:some_key=>"hello world",
:some_key2=>"apple",
:some_key3=>"pear",
:some_key4=>"hello world",
:some_key5=>"pear",
}
my_hash.to_a # => [[:some_key, "hello world"], [:some_key2, "apple"], [:some_key3, "pear"], [:some_key4, "hello world"], [:some_key5, "pear"]]
my_hash.to_a.uniq(&:last) # => [[:some_key, "hello world"], [:some_key2, "apple"], [:some_key3, "pear"]]
my_hash.to_a.uniq(&:last).to_h # => {:some_key=>"hello world", :some_key2=>"apple", :some_key3=>"pear"}
Try this, perhaps:
my_hash.invert.invert
Not as concise as David's answer, but this will get the job done:
my_hash.delete_if{|k,v| my_hash.values.find_all{|x| x == v}.length > 1}
Similar to the answer David Aldridge gave, but will keep the first key that has the value
my_hash.reduce({}){|memo,(k,v)| memo[v] ||= k; memo}.invert
As I alluded to in a comment on this question: I asked this question so that I could later apply it to a specific situation in rails: When I called #some_model.errors.messages, I was getting back key/value pairs where the value, or the message, was repeated. I needed to find a way to have unique errors.messages by the values. It was an odd situation, I think due to nested_fields within the form.
Anyways: here was the solution, thanks to help from the other answers:
#some_model.errors.messages.each do |attr, msg|
#some_model.errors.delete(attr) if #some_model.errors.messages.values.flatten.find_all{|x| "#{[x]}" == "#{msg}"}.length > 1
end
And with that, I knew that all the messages in the errors hash were unique.

Escaping apostrophe in Ruby

I am trying to escape a single quote in a ruby string. I am using this string as a insert query to push data into the postgresql.
The query that will be generated looks something like this:-
str = insert into table field1,field2 values 'Gaurav's', 'Scooter'
I tried escaping it with
str.gsub("/'/",\\\\')
But this didn't work.
The error that I always get is:
Syntax Error Near s:
I guess i would need a regex to escape single quote inside the two single quotes not sure though.
How do I fix this? Thank you.
You should used prepared statements. Prepared statements help increase your speed. The query is parsed once by the DB. They also help you avoid having to do manual escaping as you are trying to do.
f1_val = "Gaurav's"
f2_val = "Scooter"
# conn is your connection object
conn.prepare('givethisqueryaname', "INSERT INTO table field1,field2 VALUES ($1,$2)")
conn.exec_prepared('givethisqueryaname',[f1_val, f2_val])
If you are given the field names, and field values as a string, then you can do this.
fieldStr = "field1,field2"
valuesStr = "Gaurav's, Scooter"
arr = valuesStr.split(",")
conn.prepare('insert_x', "INSERT INTO table #{fieldStr} VALUES ($1,$2)")
conn.exec_prepared('insert_x', arr)
This works if the single quote you want to escape always is in front of a "s"
1.9.3p125 :020 > str = "insert into table field1,field2 values 'Gaurav's', 'Scooter'"
=> "insert into table field1,field2 values 'Gaurav's', 'Scooter'"
1.9.3p125 :021 > str.gsub("'s","-s")
=> "insert into table field1,field2 values 'Gaurav-s', 'Scooter'"
You can user the difference between single quotes(') and double quotes(").

Setting string equal to match found by Rails regex

I'm trying to pull the user_id from a foursquare URL, like this one:
https://foursquare.com/user/99999999
The following regex pulls exactly what I need (a series of numbers that terminate with the end of the line):
\d+$
However, I'm not sure how to set a string equal to the matched characters. I'm aware of sub and gsub, but those methods substitute a matched string for something else.
I'm looking for a way to specifically pull the section of a string that matches my regex (if it exists)
I like to use the return of match():
Anything wrapped in a capture () in the regex, gets assigned to the match result array
"https://foursquare.com/user/99999999".match(/(\d+)\z/)[1] #=> "99999999"
>> "https://foursquare.com/user/99999999"[/(\d+)\z/, 1]
=> "99999999"
>> "https://foursquare.com/user/99999999" =~ /(\d+)\z/
=> 28
>> $1
=> "99999999"
>> "https://foursquare.com/user/99999999".split('/').last
=> "99999999"
There are many ways. I personally like String#[] though

Resources