Ruby : Wrap Double Quotes With Single Quotes - ruby-on-rails

I've searched everywhere in SO and no lucks. Basically, i have this kind of array :
["a", "b", "c"]
I need to wrap this double quotes with single quotes to be like this :
['"a"', '"b"', '"c"']
The main reason for why i need to wrap this array element is because i need to query jsonb object in my database (PostgreSQL). The sample of query is like this :
data -> '#{af.column}' #> any(array#{#arr}::jsonb[])
To query jsonb object i need to put single quotes for each element.
UPDATE
Why i need to do this ? Its because, i need to combine multiple query into an array. Below is my example codes :
#conditions = args[:conditions] unless !args[:conditions].present?
#tables = ["assigned_contact"]
#query = self.joins({:assigned_contact => :call_campaign_answers})
.where(#conditions.join(" AND "))
.where("(data->>'age')::int between ? and ?", args[:min_age].to_i, args[:max_age].to_i)
.where("data -> 'gender' #> any(array[?]::jsonb[])", args[:gender].map(&:to_json))
.where("call_campaign_answers.answer IN (?)", args[:answers]).size
Where args[:conditions] are my query that i need to do wrape double quotes in single quotes.
If theres any other simple / method available, please let me know.

Assuming
ary = %w[a b c]
#=> ["a", "b", "c"]
Then
new_ary = ary.map {|el| %Q["#{el}"] }
produces exactly your desired output:
new_ary == ['"a"', '"b"', '"c"']
#=> true

The problem I see is you're not returning a single JSON array, but multiple independent arrays which could be a syntax error. Use a singular JSON value:
.where("data -> 'gender' #> any(array[?]::jsonb[])", args[:gender].to_json)

Related

Is there any function to get an array value from string except `eval`?

I have a string params, whose value is "1" or "['1','2','3','4']". By using eval method, I can get the result 1 or [1,2,3,4], but I need the result [1] or [1,2,3,4].
params[:city_id] = eval(params[:city_id])
scope :city, -> (params) { params[:city_id].present? ? where(city_id: (params[:city_id].is_a?(String) ? eval(params[:city_id]) : params[:city_id])) : all }
Here i don't want eval.
scope :city, -> (params) { params[:city_id].present? ? where(city_id: params[:city_id]) : all }
params[:city_id] #should be array values e.g [1] or [1,2,3,4] instead of string
Your strings look very close to JSON, so probably the safest thing you can do is parse the string as JSON. In fact:
JSON.parse("1") => 1
JSON.parse('["1","2","3","4"]') => ["1","2","3","4"]
Now your array uses single quotes. So I would suggest you to do:
Array(JSON.parse(string.gsub("'", '"'))).map(&:to_i)
So, replace the single quotes with doubles, parse as JSON, make sure it's wrapped in an array and convert possible strings in the array to integers.
I've left a comment for what would be my preferred approach: it's unusual to get your params through as you are, and the ideal approach would be to address this. Using eval is definitely a no go - there are some big security concerns to doing so (e.g. imagine someone submitting "City.delete_all" as the param).
As a solution to your immediate problem, you can do this using a regex, scanning for digits:
str = "['1','2','3','4']"
str.scan(/\d+/)
# => ["1", "2", "3"]
str = '1'
str.scan(/\d+/)
# => ["1"]
# In your case:
params[:city_id].scan(/\d+/)
In very simple terms, this looks through the given string for any digits that are in there. Here's a simple Regex101 with results / an explanation: https://regex101.com/r/41yw9C/1.
Rails should take care of converting the fields in your subsequent query (where(city_id: params[:city_id])), though if you explictly want an array of integers, you can append the following (thanks #SergioTulentsev):
params[:city_id].scan(/\d+/).map(&:to_i)
# or in a single loop, though slightly less readable:
[].tap { |result| str.scan(/\d+/) { |match| result << match.to_i } }
# => [1, 2, 3, 4]
Hope that's useful, let me know how you get on or if you have any questions.

How to remove the outer array but still keep the inner elements in their arrays in a Rails SQL string?

I have this array:
array = [["1","one"], ["2","two"], ["3","three"]]
How do I remove the outer square brackets, removing one dimension from the array, so it looks like this:
array = ["1","one"], ["2","two"], ["3","three"]
I know if I wanted to flatten the entire array, so I get one large array, I could use flatten. However, I just want to get rid of the outer dimension, while keeping the inner elements within their respective arrays.
Essentially I am trying to get the query:
Phone.where(["brand_name in (?) AND os in (?) AND price_category in (?)", ["Nokia"], ["Blackberry", "Android"], ["1", "2"]])
But instead, this is what I am getting. Notice one more set of array brackets around the corresponding column values.
Phone.where(["brand_name in (?) AND os in (?) AND price_category in (?)", [["Nokia"], ["Blackberry", "Android"], ["1", "2"]]])
This is the method:
def self.checkbox_search(params_hash)
params_array = ["brand_name", "os", "price_category"]
column_array = []
key_array = []
params_hash.each do |k,v|
if params_array.include?(k)
column_array << v
key_array << k + ' in (?)'
end
end
joined_keys = key_array.join(" AND ") + ", " + "#{column_array}"
Phone.where([#{joined_keys}])
end
I am grabbing the params hash, and putting it in checkbox_search, which goes through the hash and puts the key values in key_array, and puts their values in column_array, if they meet specified criteria of key includes params_array. Then I join the entire string together in joined_keys, then put the results of joined_keys inside Phone.where() string
You're just assembling the arrays the wrong way:
Phone.where([ key_array.join(" AND ") ] + column_array)
That appends the column_array values. If you inline them then they'll be pushed down in terms of nesting. Note that #{...} has no place here, that's used for string interpolation and it will mess up things badly.
Technically the second version is equivalent to the first due to how it's parsed and assigned:
x = [1,2],[3,4]
# => [[1, 2], [3, 4]]
x
# => [[1, 2], [3, 4]]
That notation's normally used for situations like this:
x,y = [1,2],[3,4]
# => [[1, 2], [3, 4]]
x
# => [1, 2]
y
# => [3, 4]
There's no "outer dimension" you can remove. Either you have an array of arrays, or you have a singular array that's flat.
Reading your comment about the call to Phone you can try
array = [["1","one"], ["2","two"], ["3","three"]]
Phone.where(brand_name: array.map(&:last))
Your solution is very creative but you're greatly overcomplicating a simple task.
def self.checkbox_search(params_hash)
where(params_hash.slice(:brand_name, :os, :price_category))
end
If you only want certain keys from a hash you can use Hash#slice or for a params hash you can use ActionController::Parameters#permit.
There is absolutely no need to construct a SQL string manually. In ActiveRecord you can create WHERE ... AND ... conditions by:
Person.where(name: 'Max', awesome: true)
# or
Person.where(name: 'Max').where(awesome: true)
Passing an array as the value for a key creates a WHERE ... IN ...:
Person.where(name: ['Max', 'the12'])
See:
http://guides.rubyonrails.org/active_record_querying.html

Ruby: How to access object created by send without name? [duplicate]

This question already has answers here:
How to dynamically create a local variable?
(4 answers)
Closed 6 years ago.
What I'm trying to do in Ruby is to create an object with a name that comes from a string (in an array for instance). Then I want to use that object in further code.
So, for example:
array = ["a", "b", "c"]
array.each do |x|
send x + '_data'
x + '_data' = []
x + '_data' << "foo"
end
The above, of course, does not work.
I've racked my brain, the docs, and SO all morning on this. Your help is appreciated!
Any ideas?
Thanks!
Cheers,
Kyle
EDIT for clarity:
Ok, my understanding of send was incorrect.
For each string in the array, I want to create an array.
So the loop above would create three arrays: a_data,b_data,c_data
Then, I want to populate each array with "foo".
So a_data[0] => "foo"
Thanks!
Double edit:
Here's my slightly altered actual code with fuller explanation of what I'm doing:
I have a big json file of thousands of tweets (not just the text, but the full json from twitter api).
I then have an array of hashes based with topics and associated keywords -- e.g. "cooking" -> "utensils", "oven", "microwave".
I want to loop through the array of topic hashes and see if any of the topic keywords match words in the tweet text.
If there's a match, I want to add that tweet to a new array.
# topics is an array of hashes. Each hash contains title and list of keywords
topics.each do |topic|
# create an array with the topic's name to store matches
(topic[:title] + '_tweets') = []
topic[:keywords].each do |kw|
# loop through array of hashes (parsed json) to check for keyword matches in strings
tweets.each do |tweet|
text = tweet["text"]
# if string contains keyword, add to the topic's array
if text.include? kw
(topic[:title] + '_tweets') << tweet
end
end
end
Thanks for y'all's help guys!
Why not create a Hash to keep the data you need?
array = ["a", "b", "c"]
data = {}
array.each do |x|
key = x + '_data'
data[key] ||= []
data[key] << "foo"
end
Also, note data[key] ||= [] trick. It means "look into data[key]. If it is nil, initialize it with empty array". It is idiomatic way to initialize something once.
You can declare data as Hash.new([]). Then you won't need data[key] ||= [] at all, because Hash.new([]) will create a hash that returns an empty array if the value associated with the given key has not been set.
This is much more flexible than using variable variables from PHP
But if you REALLY need something like this, you can do the following:
array = ["a", "b", "c"]
array.each do |x|
instance_variable_set '#' + x + '_data', []
instance_variable_get('#' + x + '_data') << "foo"
end
p #a_data # ["foo"]
Here we create an instance variable in the context of current object instance. Its name MUST begin with #.

ruby: wrap each element of an array in additional quotes

I have a following string :
a = "001;Barbara;122"
I split in into array of strings:
names = a.split(";")
names = ["001", "Barbara", "122"]
What should I do to have each element wrapped additionally in '' quotes?
The result should be
names = ["'001'", "'Barbara'", "'122'"]
I know it sounds strange but I need it for database query in ruby on rails. For some reason I cannot access database record if my name is in "" quotes. I do have mk1==0006 in the database but rails does not want to access it somehow. However, it does access 1222.
sql = "SELECT mk1, mk2, pk1, pk2, pk3, value_string, value_number FROM infos WHERE mk1 in (0006) AND value_string ='männlich';"
recs = ClinicdbInfo.find_by_sql(sql)
=> []
sql = "SELECT mk1, mk2, pk1, pk2, pk3, value_string, value_number FROM infos WHERE mk1 in (1222) AND value_string ='männlich';"
recs = ClinicdbInfo.find_by_sql(sql)
=> [#<Info mk1: "1222", mk2: "", pk1: "Information allgemein", pk2: "Geschlecht", pk3: "Wert", value_string: "männlich", value_number: nil>]
So, I just need to wrap every element of names into additional ''-quotes.
names.map{ |e| "'" + e + "'" }
=> ["'001'", "'Barbara'", "'122'"]
or
names.map{ |e| "'#{e}'" }
=> ["'001'", "'Barbara'", "'122'"]
You should not concatenate parameters to sql string manually; you should instead pass parameters into find_by_sql method. Example:
sql = "SELECT mk1, mk2, pk1, pk2, pk3, value_string, value_number FROM infos WHERE mk1 in (?) AND value_string = ?"
recs = ClinicdbInfo.find_by_sql [sql, 1222, "männlich"]
This way the necessary type conversions and escaping to prevent against sql injection will be handled by Rails.
I agree with #jesenko that you should not construct your SQL queries and let AR do the type conversion and escape input against SQL injection attacts. However, there are other use cases when you'd want this. For example, when you want to insert an array of strings into your js. I prefer using the following syntax for those rare cases:
names.map &:inspect # => ["\"001\"", "\"Barbara\"", "\"122\""]
If you are print this in your views, you should mark it as html safe:
names.map(&:inspect).html_safe

In Ruby, can I pass each element of an array individually to a method that accepts *args?

Given a method that returns an array and another accepts an arbitrary number of arguments, is there a way to call the second method with each element of the array as an argument?
For example:
def arr
["a", "b", "c"]
end
def bar(*args)
args.each {|a| puts a}
end
I want to call
bar "a", "b" , "c"
Of course this is a simplified example, in reality arr could return an array of any size (say if it's an ActiveRecord find, and I want to pass all the results to bar), hence my problem.
You can do this:
my_array = ['a', 'b', 'c']
bar(*my_array)
This will flatten out the array into it's individual elements and pass them to the method as separate arguments. You could do this to any kind of method, not only ones that accept *args.
So in your case:
bar *arr
Use * also when you give an array as an argument:
bar(*arr)

Resources