I'm new in Ruby on rails and I would like to fetch records based on a condition, and I'm passing the condition in a string format. Moreover, I will pass the query in multiple OR and AND conditions. However, right now, I'm stuck that how to pass the query in string format in rails
I have attached the screenshot
#data= CustomAttribute.includes(:custom_attribute_values).where(id: 18, company_id: current_user.company_id).first
The above line executed successfully and gave the output
<CustomAttribute id: 18, data_type: "string", label: "Marital status", code: "marital_status", entity_type: "member", company_id: 1, created_at: "2021-03-10 10:16:15", updated_at: "2021-03-10 10:16:27", is_active: true, is_default: false, rank: nil, is_identifier: false>
but when I executed the below line it gave me the error that
#data.custom_attribute_values.where("\""+"value_string"+"\""+"="+"\""+'Single'+"\"").size
ERROR: column "Single" does not exist
the Single is the value which I would like to count
Here is my code for the dynamic query creation
logical_operator = 'OR'
#custom_attribute = CustomAttribute.includes(:custom_attribute_values).where(id: custom_attribute_ids, company_id: current_user.company_id)
query=""
#custom_attribute.each_with_index do |attribute_object, index|
filter_object= filter_params[:filters].find {|x| x['custom_attribute_id']==attribute_object['id']}
if filter_object.present?
query += "("+ '"' +'value_'+attribute_object.data_type + '"' + ' ' + filter_object['operator'] + ' ' + "'" + filter_object['value'].to_s + "'"+ ")"
end
if index != #custom_attribute.length-1
query+=' '+logical_operator+' '
end
if index == #custom_attribute.length-1
query="'" + " ( " + query + " ) " + "'"
end
end
byebug
puts(#custom_attribute.first.custom_attribute_values.where(query).size)
Any time you're doing a lot of escaping and string addition in Ruby you're doing it wrong. If we clean up how you build your SQL:
"\""+"value_string"+"\""+"="+"\""+'Single'+"\""
things will be clearer. First, put space around your operators for readability:
"\"" + "value_string" + "\"" + "=" + "\"" + 'Single' + "\""
Next, don't use double quotes unless you need them for escape codes (such as \n) or interpolation:
'"' + 'value_string' + '"' + '=' + '"' + 'Single' + '"'
Now we see that we're adding several constant strings so there's no need to add them at all, a single string literal will do:
'"value_string" = "Single"'
Standard SQL uses double quotes for identifiers (such as table and column names) and single quotes for strings. So your query is asking for all rows where the value_string column equals the Single column and there's your error.
You want to use single quotes for the string (and %q(...) to quote the whole thing to avoid adding escapes back in):
#data.custom_attribute_values.where(
%q("value_string" = 'Single')
)
Or better, let ActiveRecord build the query:
# With a positional placeholder:
#data.custom_attribute_values.where('value_string = ?', 'Single')
# Or a named placeholder:
#data.custom_attribute_values.where('value_string = :s', s: 'Single')
# Or most idiomatic:
#data.custom_attribute_values.where(value_string: 'Single')
I am creating a checksum for my payment gateway. The checksum should be in this format:
key|txnid|amount|productinfo|firstname|email|||||||||||salt
I need to leave 11 pipes between the email and the salt.
In my controller I created a method:
require 'digest/sha2'
def getChecksum(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
String str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
I called this method in my controller:
hash: getChecksum(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io')
But now I am getting a "wrong number of arguments" error:
wrong number of arguments (1 for 9)
def getChecksum( key, txnid,
amount, productinfo, firstname, email, phone, surl, furl)
I think all the parameters are considered as a single one. Can someone tell me what I am doing wrong here?
You are sending a single hash as the parameter, instead you need to send the values only (without keys). Also no need to declare the type String str, you should review your Ruby fundamentals, I'm guessing you come from another language.
# You are calling this
getChecksum(key: 'key_value', txnid: '4', ...)
# which syntactically equals this, a single hash as a parameter
getChecksum({key: 'key_value', txnid: '4', ...})
# Instead, send the values alone
getChecksum('key_value', '4', ...)
Let's look at how you're writing your code and see if we can make it more Ruby-like and hopefully more production-worthy.
You wrote this:
def getChecksum(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
String str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
First, ick.
Methods and variables in Ruby are snake_case, not camelCase. ItsAReadabilityThing.
We don't define variables using String str =.... Ruby can figure out what type of variable it is 99.99% of the time, so str =... is sufficient.
Think about how you're using fixed-strings and values. '|' is your separator, so define it as such:
SEPARATOR = '|'
And you need eleven of them as a divider, so define that:
DIVIDER = SEPARATOR * 11
I'd write the code more like this:
require 'digest/sha2'
SEPARATOR = '|'
DIVIDER = SEPARATOR * 11 # => "|||||||||||"
def checksum_1(key, txnid, amount, productinfo, firstname, email, phone, surl, furl)
str = [key, txnid, amount, productinfo, firstname, email, phone, surl, furl].join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest(str)
end
But even that can be improved upon.
When we have more than three parameters it's time to refactor and clean up the parameter list. There are many ways to do it, but using a hash is the most common:
def checksum_2(params)
str = params.values_at(
:key, :txnid, :amount, :productinfo, :firstname, :email, :phone, :surl, :furl
).join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest( str )
end
Using values_at is a nice way to extract the values of the keys in a hash. They come out in an array in the same order of the keys.
The problem now is that a parameter could be missing and Ruby won't catch it. Named parameters are the new-hotness for dealing with that, but for older Rubies, or to get an idea how it might be done in other languages, we can look at the keys received, then their associated values, and if things don't look good raise an exception rather than return a bogus value.
To make things easier I'm going to use Active Support's blank? method, found in the Core Extensions:
require 'active_support/core_ext/object/blank'
def checksum_3(params)
required_values = [:key, :txnid, :amount, :productinfo, :firstname, :email, :phone, :surl, :furl]
missing_keys = required_values - params.keys
fail ArgumentError, "Missing keys: #{ missing_keys.map{ |k| ":#{ k }" }.join(', ') }" unless missing_keys.empty?
missing_values = required_values.select{ |k| params[k].blank? }
fail ArgumentError, "Missing values for: #{ missing_values.map{ |k| ":#{ k }" }.join(', ') }" unless missing_values.empty?
str = params.values_at(*required_values).join(SEPARATOR) + DIVIDER + 'salt'
Digest::SHA512.hexdigest(str)
end
Here's how the calls to the various versions of the method look, along with the associated checksum that's returned:
c1 = checksum_1('key_value', '4', '2000', 'first sales', 'John', 'johndepp#gmail.com', '123456789', 'http://someurl.io', 'http://someurl.io')
c2 = checksum_2(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io'
)
c3 = checksum_3(
key: 'key_value',
txnid: '4',
amount: '2000',
productinfo: 'first sales',
firstname: 'John',
email: 'johndepp#gmail.com',
phone: '123456789',
surl: 'http://someurl.io',
furl: 'http://someurl.io'
)
c1 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
c2 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
c3 # => "fcf4e21c6e711808a6984824f09552dccc5c4378b55720c88b3abd4a1904931b8d883beaef51edec705a9d8b0ccd3ba898a0d4c05f75bd41fa3b90f0df7b5c79"
While we still have to pass in the same amount of information, using the hash allows us to see what each parameter is being assigned to, resulting in self-documenting code, which is a very important thing for long-term maintenance. Also, the hash doesn't require a specific order of the parameters, making it easier to write the code calling it; I usually know I need certain things but can't remember what order they're in.
That's all my take on how to write this type of code. See "Ruby 2 Keyword Arguments" for a similar write-up on the same topic.
Here's how you would define the method with keyword arguments:
def getChecksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = key + '|' + txnid + '|' + amount + '|' + productinfo + '|' + firstname + '|' + email + '|' + phone + '|' + surl + '|' + furl+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|'+ '|' + 'salt'
Digest::SHA512.hexdigest( str )
end
Then you can call it with a hash like you are currently.
I think this looks a bit cleaner, though:
def get_checksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = "#{key}|#{txnid}|#{amount}|#{productinfo}|#{firstname}|#{email}|#{phone}|#{surl}|#{furl}||||||||salt"
Digest::SHA512.hexdigest(str)
end
Or perhaps:
def get_checksum(key:, txnid:, amount:, productinfo:, firstname:, email:, phone:, surl:, furl:)
str = [ key, txnid, amount, productinfo, firstname, email, phone, surl, furl ].join("|")
Digest::SHA512.hexdigest("#{str}||||||||salt")
end
You can learn more about keyword arguments, including how to give keywords default values to make them optional, here: https://robots.thoughtbot.com/ruby-2-keyword-arguments
I am making a custom rails route as:
match '/setFavoriteRestaurant/:user_id/:restaurant_id/:campaignSetFav_id/:metro_id/:time_period', to: 'requests#setFavoriteRestaurant', via: 'get'
with controller action:
def setFavoriteRestaurant
setFavorite = "INSERT INTO androidchatterdatabase.users_favorite_restaurants(usersId,restaurantId,campaignIdSetFav,metroId,timePeriod,favoritedDt)
VALUES(" + params[:user_id].to_s + ","
+ params[:restaurant_id].to_s + ","
+ params[:campaignSetFav_id].to_s + ","
+ params[:metro_id].to_s + ","
+ params[:time_period].to_s + ",
NOW());"
ActiveRecord::Base.connection.execute(setFavorite)
end
Yet when testing in the browser with: http://localhost:3000/setFavoriteRestaurant/1/2/3/5/4
it returns an odd error as: undefined method +#' for "2":String
Why is this the case when other methods, setup exactly the same are fine to run?
This has to do with how you broke up the lines. Ruby doesn't know that the VALUES(" + line and the + params[:restaurant_id] are part of the same thing. This is because the VALUES( + line is complete. Move the + to the end of the line so that Ruby will know to expect the next line to be a continuation.
setFavorite = "INSERT INTO androidchatterdatabase.users_favorite_restaurants(usersId,restaurantId,campaignIdSetFav,metroId,timePeriod,favoritedDt)" +
"VALUES(" + params[:user_id].to_s + "," +
params[:restaurant_id].to_s + "," +
params[:campaignSetFav_id].to_s + "," +
params[:metro_id].to_s + "," +
params[:time_period].to_s + ",NOW());"
Also, note that I moved some other things around to avoid new lines and extra spaces.
I'm not sure why you prefer raw SQL here, but you should consider going through Rails. Seems like you're opening yourself up to SQL injection. At the very least, you could have some constraints in the route to match only integers.
Ruby has a couple of ways to quote multi line strings:
1) Here it is with ruby's heredoc syntax:
def some_action
setFavorite = <<-"END_OF_INSERT"
INSERT INTO androidchatterdatabase.users_favorite_restaurants(
usersId,
restaurantId,
campaignIdSetFav,
metroId,
timePeriod,
favoritedDt
)
VALUES(
"#{params[:user_id]}",
"#{params[:restaurant_id]}",
"#{params[:campaignSetFav_id]}",
"#{params[:metro_id]}",
"#{params[:time_period]}",
NOW()
);
END_OF_INSERT
end
Explanation:
setFavorite = <<-"END_OF_INSERT"
- => Terminator does not have to be at the start of the line.
"" => This is a double quoted string--do interpolation.
2) Here it is with %Q{}:
setFavorite = %Q{
INSERT INTO androidchatterdatabase.users_favorite_restaurants(
usersId,
restaurantId,
campaignIdSetFav,
metroId,
timePeriod,
favoritedDt
)
VALUES(
"#{params[:user_id]}",
"#{params[:restaurant_id]}",
"#{params[:campaignSetFav_id]}",
"#{params[:metro_id]}",
"#{params[:time_period]}",
NOW()
);
}
However, your SQL statement is still vulnerable to sql injection attacks. Check your db adapter for how to do parameter substitutions.
I can't set the item summary in set express checkoput. L_NAME0=A caused error
def strUsername = "***"
def strPassword = "***"
def strSignature = "***"
def strCredentials = "USER=" + strUsername + "&PWD=" + strPassword + "&SIGNATURE=" + strSignature
def strNVPSandboxServer = "https://api-3t.sandbox.paypal.com/nvp";
def user = session.userId +","+amt + "," + receiver + "," + address + "," + opt
def successUrl = '***
def cancelUrl = '***'
def strAPIVersion = "56.0"
def strNVP = strCredentials + "&METHOD=SetExpressCheckout&AMT=" + totalamount +"&PAYMENTACTION=Sale&RETURNURL="+ successUrl+"&CANCELURL="+ cancelUrl +"&CURRENCYCODE=SGD&ITEMAMT="+totalamount+"&L_NAME0=OHN&VERSION=" + strAPIVersion
Try updating def strAPIVersion = "56.0" to def strAPIVersion = "76.0" I think the advanced features a not available in the 56 version. I used the default set by Paypal as a test
&L_PAYMENTREQUEST_0_NAME0=10% Decaf Kona Blend Coffee
&L_PAYMENTREQUEST_0_NUMBER0=623083
&L_PAYMENTREQUEST_0_DESC0=Size: 8.8-oz
&L_PAYMENTREQUEST_0_AMT0=9.95
&L_PAYMENTREQUEST_0_QTY0=2
&L_PAYMENTREQUEST_0_NAME1=Coffee Filter bags
&L_PAYMENTREQUEST_0_NUMBER1=623084
&L_PAYMENTREQUEST_0_DESC1=Size: Two 24-piece boxes
&L_PAYMENTREQUEST_0_AMT1=39.70
&L_PAYMENTREQUEST_0_QTY1=2
&PAYMENTREQUEST_0_ITEMAMT=99.30
&PAYMENTREQUEST_0_TAXAMT=2.58
&PAYMENTREQUEST_0_SHIPPINGAMT=3.00
&PAYMENTREQUEST_0_HANDLINGAMT=2.99
&PAYMENTREQUEST_0_SHIPDISCAMT=-3.00
&PAYMENTREQUEST_0_INSURANCEAMT=1.00
&PAYMENTREQUEST_0_AMT=105.87
&ALLOWNOTE=1