Array of Sql values in Ruby - ruby-on-rails

in my Rails project I am trying to use the following query in a search form_tag
Student.joins(:courses).where(#params.joins(','), #values)
where params and values are dynamically constructed arrays since there are some optional parameters in the search. An example from my code:
if params[:date_begin] != ''
#params.push " courses.date_begin >= ? "
#values.push params[:date_begin]
end
The problem is the #values array is being considered as one argument and raises this error:
wrong number of bind variables (1 for 2)
How do I tell it to consider the array elements separately?

What about build query this way:
scope = Student.joins(:courses)
if params[:date_begin].present?
scope = scope.where(" courses.date_begin >= ? ", params[:date_begin])
end
scope

You need to splat array
Student.joins(:courses).where(#params.joins(','), *#values)

You can unpack the array like so:
Student.joins(:courses).where(#params.join('AND'), *#values)
(Note you also need to change joins to join and the comma to AND).

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

Rails find_by_sql - Use "?" literally rather than as bind variable?

In the below, I want the first ? to be used literally, only the second ? should be used as the bind variable marker:
Foo.find_by_sql ["select IFNULL(col,'?') from foos where id = ?",1]
This errors:
wrong number of bind variables (1 for 2)
How would I escape the first ? so it is treated literally?
ActiveRecord isn't smart enough to ignore placeholders in string literals so it thinks that the ? in '?' is a placeholder rather than part of a string. The easiest way around this is to use named placeholders:
Foo.find_by_sql ["select IFNULL(col, '?') from foos where id = :id", :id => 1]
When ActiveRecord sees a Hash in the array's second element it will look for named placeholders (which use Ruby symbol notation) rather than positional ? placeholders. I tend to lean towards named placeholders period as they're more readable and more robust.
Simply pass '?' as a param:
Foo.find_by_sql ["select IFNULL(col,?) from foos where id = ?",'?',1]

how to use dynamic variable for symbols in ruby where statements

I dont how to accomplish this problem.
I faced with this problem 3 times and each time I put it in my todo list but even tho I tried to find a solution I couldnt.
For examples,
I m trying to create a query with dynamic variables of this example;
User.search(first_name_start: 'K')
there are 3 arguments in this example;
1)first_name - My model attribute
2)start - Query type (start/end/cont )
3)'3' - value
I was able to create dynamic ActiveRecord using static symbols but how am I suppose to make dynamic input
Thanks in advance
EDIT: ADDITIONAL INFORMATION
let me show you a some kind of pseudo-code
varArray.each_with_index |x,index|
queryString=varArray[i]+"_"+filterArray=[i] #lets say varArray[i], this will be first_name, an actual model attribute/a column in my db
#and filterArray like /start/end/with a filter type
#and finally valArray a string value like 'geo' or 'paul'
User.where(queryString valArray[i]).result
I tried to use send(variable) but that didnt help me either, so i dont how should i proceed,
This is one of a few cases where new fancy Ruby 1.9 syntax for defining hashes doesn't cut it. You have to use the traditional hashrocket (=>) that allows you to specify not only symbols, but any arbitrary values as hash keys:
column = "#{first_name}_size_#{query_type}".to_sym
User.where( column => value )
AFAIK, ActiveRecord is able to accept strings instead of symbols as column names, so you don't even need to call to_sym.

Rails getting an error '/' undefined method

Hi I am find the average
This what I am trying to do
I have as set of variables which I am taking from db based upon user_id and company_id the variables are And I cannot added then and there because I need to display individual parameter in my show page and I also wanted to display their average
So I am trying to do as below
#r1=company_rating.collect(&:r1)
#r2=company_rating.collect(&:r2)
#r3=company_rating.collect(&:r3)
#r4=company_rating.collect(&:r4)
So I am doing it like
arr = [#r1,#r2,#r3,®r4]
#totalaverage= arr.sum.compact /arr.size
My array sample looks like [10,20,30,nil],[nil,nil,nil,nil],[30,40,50,60]
And If I have array all Nil then it should show be Nil
But I am getting an error undefined method `/' for # and Why I am doing compact is because I have sum of the nil values in that
So please help how do this.
First of all, you define arr as an array of arrays. #r1, #r2 etc. are all arrays and what [#r1, #r2, ...] does is just mixing them up in another array. You probably want to merge them, not include them in another array:
arr = #r1 + #r2 + #r3 + #r4
Second, you should call arr.compact first, then sum the contents up. Also, I'm not really sure about the sum method. I'd use reduce(&:+) instead. So, to answer your question, '/' fails because compact returns an Array, and you're trying to divide an Array to a number. This looks better:
arr = #r1 + #r2 + #r3 + #r4
#totalaverage = arr.compact.reduce(&:+) / arr.size
What Array#reduce(&:+) does is to apply the + operator between array members and return the value (not an array).
EDIT: arr.sum does work if you're using Ruby on Rails. Otherwise use arr.reduce(&:+) instead.
Do as below,which should work :
#totalaverage= arr.flat_map(&:compact).inject(:+) /arr.size.to_f
Actually #totalaverage is an array of array. Where each internal element(array) of #totalaverage can have nil values also(as you shown). So you need to remove those nil entries if any from the internal array of #totalaverage. And arr.map(&:compact) will do the same job.
Simplest way to do this if you're using Rails:
#totalaverage = a.flatten.compact.sum.to_f / a.flatten.compact.size if a.flatten.compact.present?
This will assign to #totalaverage the result or nil in case all the values are nil.

Modifying the returned value of find_by_sql

So I am pulling my hair over this issue / gotcha. Basically I used find_by_sql to fetch data from my database. I did this because the query has lots of columns and table joins and I think using ActiveRecord and associations will slow it down.
I managed to pull the data and now I wanted to modify returned values. I did this by looping through the result ,for example.
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
end
What I found out is that project['mycolumn'] was not changed at all.
So my question:
Does find_by_sql return an array Hashes?
Is it possible to modify the value of one of the attributes of hash as stated above?
Here is the code : http://pastie.org/4213454 . If you can have a look at summarize_roles2() that's where the action is taking place.
Thank you. Im using Rails 2.1.1 and Ruby 1.8. I can't really upgrade because of legacy codes.
Just change the method above to access the values, print value of project and you can clearly check the object property.
The results will be returned as an array with columns requested encapsulated as attributes of the model you call this method from.If you call Product.find_by_sql then the results will be returned in a Product object with the attributes you specified in the SQL query.
If you call a complicated SQL query which spans multiple tables the columns specified by the SELECT will be attributes of the model, whether or not they are columns of the corresponding table.
Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
> [#<Post:0x36bff9c #attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
Source: http://api.rubyonrails.org/v2.3.8/
Have you tried
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
project.save
end

Resources