How to query jsonb fields and values containing single quote in Rails? - ruby-on-rails

I'm having a Rails app running with PostgreSQL. There is a table called Programs as the following:
Program(id: integer, name: string, properties: jsonb)
Let say we have a program called "Football Club" where properties are as the following:
{
"properties" =>
{
"What's your gender?" => "I'm male.",
"Is your health good?" => "Yes"
}
}
My query is like this:
# this field and value are OK with the query
field = Is your health good?
value = Yes
Program.where("properties ->> '#{field}') = '#{value}'")
# this field and value causes error with the query since they contain quote (')
field = What's your gender?
value = I'm male.
Program.where("properties ->> '#{field}') = '#{value}'")
I know that the issue causes by how I interpolate string. Yet, I could not find a solution.
Really appreciate for your help.

Related

How can I cast a enum string value to column type?

I need to create a method that dynamically filters a model by a column. It needs to receive the column that I want to filter by (called attr_name), an operator as a string, and the value as a string.
I need to first cast the string value to the database column type so I can then do the sql query.
scope :filtered_by_attribute, (lambda do |attr_name, operator, value|
comparing_value = Customer.attribute_types[attr_name].cast(value)
casting_error = !value.nil? && comparing_value.nil?
raise I18n.t('api.errors.unaplicable_customer_scope') if casting_error
sql_query = sanitize_sql("#{attr_name} #{operator} ?")
where(sql_query, comparing_value)
end)
The problem above is when it comes to enums. Enums are integers on the db but when I perform the casting it will return the same string value since for rails it is a string. Then, in the where query, it explodes since in the database it's comparing an integer column with a string.
Do you know how I cast a string value to match the type of a column in the database?
Thank you!
The cast method casts a value from user input when it is assigned to an instance. In the case of enum when you assign a string value it remains a string value. It is converted to an integer only when it is persisted in DB.
class Order < ActiveRecord::Base
enum status: {confirmed: 1, cancelled: 2}
end
# this is where the `cast` method is called
#order.status = "cancelled"
# still a string since the `cast` method didn't do anything.
#order.status # => "cancelled"
What you really need is the serialize method. It casts a value from a ruby type to a type that the database knows how to understand.
Order.attribute_types["status"].serialize("cancelled") # => 2

Rails- when doing a multiple condition search, how to eliminate the column with nil value?

A car has three attributes, "model", "location", "status". I want to search cars according these three conditions. First, I thought I could use
Car.where(model: params[:car][:model], location: params[:car][:location], status: params[:car][:status])
However, I do not know how to eliminate the column with nil value. For example, if I do not input model which means params[:car][:model] = nil, it would actually do select * from car where model = null and status = ? and location = ?
However, what I want is select * from car where location= ? and status = ?. How could I make this happen?
First you want to slice out the parameters you want:
# `.fetch(:car, {})` prevents a nil error if the key is not present in the params.
params.fetch(:car, {}).permit(:location, :status, :model)
# or if `params[:car]` is required.
params.require(:car).permit(:location, :status, :model)
Then use Hash#compact which returns a new hash with nil values removed:
irb(main):008:0> {"location"=>1, "status"=>nil, "model"=>"Ford Model T"}.compact
=> {"location"=>1, "model"=>"Ford Model T"}
Altogether:
Car.where(params.fetch(:car, {}).slice(:location, :status, :model).compact)

Cannot get exact value on parameter

I have sample parameter below:
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"xxxxxxxxxx",
"post" => {
"product_attributes" => {
"name"=>"Ruby",
"product_dtls_attributes" => {
"0"=>{"price"=>"12,333.00"},
"1"=>{"price"=>"111,111.00"}
},
},
"content"=>"Some contents here."
}
Now, the scenario is, I cannot get the price exact value in model.
Instead of:
price = 12,333.00
price = 111,111.00
I get:
price = 12.00
price = 11.00
And now here is what I did in my code:
before_validation(on: :create) do
puts "price = #{self.price}" # I also tried self.price.to_s, but didn't work.
end
UPDATE:
(I am trying do to here is to get the full value and strip the comma).
before_validation(on: :create) do
puts "price = #{self.price.delete(',').to_f}" # I also tried self.price.to_s, but didn't work.
end
Note:
column price is float
The question is, how can I get the exact value of params price.
Thanks!
Looking at the 'price' parameter you provided:
"price"=>"12,333.00"
The problem is with the comma.
For example:
irb(main):003:0> "12,333.00".to_i
=> 12
But you can fix that:
Example:
irb(main):011:0> "12,333.00".tr(",", "_").to_i
=> 12333
The key point is replacing the comma with an underscore. The reason is that 12_333 is the same integer as 12333 (the underscores are ignored). You could just remove the comma with tr(",", "") as well. In this case, you could replace tr with gsub and have the same effect.
By the way, are you aware that your validation method is not doing anything besides printing? Anyway, a before_validation method is not the right approach here because the number will already have been incorrectly converted when the code reaches this point. Instead, you can override the setter on the model:
class MyModel
def price=(new_price)
if new_price.is_a?(String)
new_price = new_price.tr(",", "")
end
super(new_price)
end
end
You can do it like this too:
2.1.1 :002 > "12,333.00".gsub(',', '').to_f
=> 12333.0
This will replace the comma and if you have any decimal value then too it will interpret it:
2.1.1 :003 > "12,333.56".gsub(',', '').to_f
=> 12333.56
The solution I made is to handle it on controller. Iterate the hash then save it. Then it get the proper value which I want to get and save the proper value.
Iterate the following hash and save.
"post" => {
"product_attributes" => {
"name"=>"Ruby",
"product_dtls_attributes" => {
"0"=>{"price"=>"12,333.00"},
"1"=>{"price"=>"111,111.00"}
},
},
"content"=>"Some contents here."
I can't get the full value of price in model because of comma separator. This comma separator and decimal points + decimal places is made by gem.
Price is float, but your data contains a non-numeric character (comma, ","). When the field is converted to a float, parsing likely stops at this character and returns just 12.
I had expected an error to be thrown, though.
I suggest you remove the comma before putting it into the database.

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

matching string then returning results using regexp

Is there a way to match the following string so I get user name and 50?
hey "user name":/users/50
I may also have more than one instance of this in a string.
can you try the following
string = 'hey "user name":/users/50'
matches = string.scan /"(?<name>[A-Za-z ]+)":\/users\/(?<user_id>\d+)/
matches will be an array containing arrays with 2 elements where the first element is the name and the 2nd element is the user_id
>> matches # [['user name', 50]]

Resources