Passing argument to sql query in rails - ruby-on-rails

I'm trying to pass a value calculated in my controller to a model method and use it in a SELECT query.
I calc a value and want to pass it as an argument to the method below as 'control' and use it in place of the .02 in the sum calculations. The query below works as is, but if I add a third argument, call it 'control' and try to use it in the sum calc it fails. I tried it as I did with the bom and eom values by sticking {:control => control} on to the SELECT statement after the quote and before the parenthesis. The error output shows me that that :control is equal to the value passed in, but it errors on the rest of the instances of :control in the SELECT statement. The DB is PostgreSQL.
def self.rolling_total_month(bom, eom)
select("SUM((usages.usg_amount * materials.mat_voc_with)/2000) AS uc_emit_tons,
SUM(((.02) * usages.usg_amount * materials.mat_voc_with)/2000) AS c_emit_tons,
SUM((.02) * (usages.usg_amount * materials.mat_pounds_per_gal)/2000) AS hap_tons
").
joins("JOIN materials ON usages.material_id = materials.id").
where("usages.material_id <> 65 AND materials.mat_active_flag = TRUE AND materials.mat_report_flag = TRUE AND :bom <= usages.usg_date AND usages.usg_date <= :eom", { :bom => bom, :eom => eom })
end

Related

Subtraction in Rails

I have an object that has an integer type of attribute. I want to subtract 1 from the attribute after a particular action. I tried in the controller:
def subtraction
#find a item and get the value, let's say value is 40
item = Item.where(id: params[:id]).pluck(:value)
# subtract 1 from the value and i thought it would be 40-1
after_subtraction = item.to_i - 1
#update the value
final = item.update(value: after_subtraction)
end
I get:
NoMethodError (undefined method `to_i' for [40]:Array
When I remove to_i, it says - is not a method. Is there any way to update the stored value?
The better way to handle is
item = Item.find_by(id: params[:id]).value
pluck will return you array, which is not necessary in your case here.
Based on the way you constructed the query, it gets the value for all entries that match the where condition, hence what is returned by the .pluck method is an array on which you cannot call .to_i method.
I guess what you want to do is to pluck the value you need from the first entry that matches your query, hence you can refactor as below
def subtraction
#find the first item with id supplied
item = Item.where(id: params[:id]).first
#after subtraction value
val = item.value - 1
#update the value
final = item.update(value: val)
end
As pluck returns an array you can not use to_i for conversion here.
Seeing your code, you can refactor it like this,
def subtraction
# Find the item first
item = Item.find(params[:id])
# Subtract 1 from the value column of the item
item.value -= 1
# Save the modification of the item
item.save!
end
You can't directly convert array in to to_i . please use below method
after_subtraction = item.join.to_i - 1
Yes, Nithin's answer is more valid. you can go with nithin's answer. you don't need to use pluck until you want array of multiple values.

How can .where accept an array without braces?

I'm not sure how is this implemented, when you do something like:
Model.where(["subjects = ?", 1])
Rails allows you to omit the braces:
Model.where("subjects = ?", 1)
I know this is possible with hashes, but how is it possible so you can pass ANY number of arguments (you can have 100 question marks if you want) and for Rails to still interpret this as an array?
In Ruby a method can accept splat arguments.
def foo(*a)
a
end
foo('bar', 'baz')
# => ["bar", "baz"]
The splat gathers up any remaining arguments. You can even use it with regular arguments:
def foo(a, *b)
b
end
foo('bar', 'baz')
# => ["baz"]
You can even do something like:
def foo(*a)
a.length == 1 && a.first.is_a?(Array) ? a.first : a
end
Now calling foo('bar', 'baz') and foo(['bar', 'baz']) have the same return value.
However if what you want is a WHERE condition where the value can be one of many possible values you would write it like so:
Model.where(foo: [1, 2, 3, 5])
Which would create a WHERE models.foo IN (1,2,3,5) clause.
From the Docs
Model.where(array)
If an array is passed, then the first element of the array is treated as a template, and the remaining elements are inserted into the template to generate the condition. Active Record takes care of building the query to avoid injection attacks, and will convert from the ruby type to the database type where needed. Elements are inserted into the string in the order in which they appear.
User.where(["name = ? and email = ?", "Joe", "joe#example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe#example.com';

find_all elements in an array that match a condition?

I've an array of hash entries, and want to filter based on a paramater passed into the function.
If there are three values in the hash, A, B, and C, I want to do something similar to:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
data.find_all{ |d| d[:A].include?params[:A] }
.find_all{ |d| d[:B].include?params[:B] }
.find_all{ |d| d[:C].include?params[:C] }
find all where A == 'a1' AND B='b2'
so for above I get:
{A:'a1', B:'b2', C:'c1'} and {A:'a1', B:'b2', C:'c2'}
* put if / else statements, like params.has_key? 'B' then do something.
* modify my code each time a new type of value is added to the Hash map (say now I have 'D').
Note: The key of the hash is a symbol, but the value is a string and I want to do a "contains" not "equals".
I think of it as SQL Select statement with where zero or more '==' clause
If I understand your question correctly, then I believe that the select method of Array class may be helpful to you.
select takes a block of code which is intended to be a test condition on each element in your array. Any elements within your array which satisfy that test condition will be selected, and the result is an array of those selected elements.
For example:
arr = [ 4, 8, 15, 16, 23, 42 ]
result = arr.select { |element| element > 20 }
puts result # prints out [23, 42]
In your example, you have an array of hashes, which makes it only slightly more complicated than my example with a simple array of numbers. In your example, we have:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
I believe what you want your code to do is something like: Select from my data array all of the hashes where, within the hash, :A equals some value AND :B equals some other value.
Let's say you want to find all of the hashes where :A == 'a1' and :B == 'b2'. You would do that like this:
data.select { |hash_element| hash_element[:A] == 'a1' && hash_element[:B] == 'b2' }
This line returns to you an array with those hashes from your original data array which satisfy the condition we provided in the block - that is, those hashes where :A == 'a1' and :B == 'b2'. Hope that that helps shed some light on your problem!
More information about the select method here:
http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-select
edited - below is an addition to original answer
To follow up on your later question about if/else clauses and the addition of new parameters... the block of code that you pass to select can, of course, be much more complicated than what I've written in the example. You just need to keep this in mind: If the last line of the block of code evaluates to true, then the element will be selected into the result array. Otherwise, it won't be selected.
So, that means you could define a function of your own, and call that function within the condition block passed to select. For example, something like this:
def condition_test(hash_element, key_values)
result = true
key_values.each do |pair|
if hash_element[pair[:key]] != pair[:value]
result = false
end
end
return result
end
# An example of the key-value pairs you might require to satisfy your select condition.
requirements = [ {:key => :A, :value => 'a1'},
{:key => :B, :value => 'b2'} ]
data.select { |hash_element| condition_test(hash_element, requirements) }
This makes your condition block a bit more dynamic, rather than hard-wired for :A == 'a1' and :B == 'b2' like we did in the earlier example. You can tweak requirements on the fly based on the keys and values that you need to look for. You could also modify condition_test to do more than just check to see if the hash value at some key equals some value. You can add in your if/else clauses in condition_test to test for things like the presence of some key, etc.
I think you want to use .values_at(key)
http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-values_atvalues_at method

Need to convert a Boolean from Postgres (== String) to a Ruby Boolean

I'm using Postgres with Rails. There's a query with a subselect which returns a boolean, but Postgres always returns a String like 't' or 'f'. But in the generated JSON I need a real boolean.
This is my query:
SELECT
*,
EXISTS (
SELECT TRUE
FROM measurement
JOIN experiment ON measurement.experiment_id = experiment.id
JOIN location ON experiment.location_id = location.id
WHERE location.map_id = map.id
LIMIT 1
) AS measurements_exist
FROM "map"
It doesn't matter whether I use THEN true or THEN 1 or THEN 'true', I will always get a string. So my JSON response will always look like that:
[
{"id":8, ..., "measurements_exist":"f"},
{"id":9, ..., "measurements_exist":"t"}
]
But it should(!) look like that:
[
{"id":8, ..., "measurements_exist":false},
{"id":9, ..., "measurements_exist":true}
]
Is there any way to get this working right?
Thank you!
THE SOLUTION:
Just give the corresponding model (here: Map) an attribute accessor, which uses value_as_boolean to convert the value. So every time the controller tries to access the value, it uses the attribute accessor method automatically.
The controller code:
class MapsController < ApplicationController
def index
select = ["*"]
select.push(measurements_exist) # This will just insert the string returned by the 'measurements_exist' method
maps = Map.select(select) # Results in 'SELECT *, EXISTS (...) AS measurements_exist FROM "map"'
render json: maps
end
private
def measurements_exist
"EXISTS (
SELECT TRUE
FROM measurement
JOIN experiment ON measurement.experiment_id = experiment.id
JOIN location ON experiment.location_id = location.id
WHERE location.map_id = map.id
LIMIT 1
) AS measurements_exist"
end
end
The model code:
class Map < ActiveRecord::Base
def measurements_exist
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(self[:measurements_exist])
end
end
Resulting JSON:
[
{"id":7, ..., "measurements_exist":false},
{"id":6, ..., "measurements_exist":true}
]
ActiveRecord has a method called ActiveRecord::ConnectionAdapters::Column.value_to_boolean it uses internally to convert any true-like value to a Ruby true value.
You can use it in your code.
When you query model from Rails its boolean fields converted to true/false automatically because DB adapter can determine type of field from schema. If you select custom boolean field from db - adapter doesn't know anything about it, so it returned string 't' or 'f' and you need to convert it manually.
One of the ways to get expected boolean value:
Create the view with provided SQL-query on the DBMS side (e.g. see CREATE VIEW ... statement for PostgreSQL). Views fields have types so boolean fields will be converted in your app automatically. Suppose its called map_with_measurements.
Create model MapWithMeasurement and place it in models/map_with_measurement.rb.
class MapWithMeasurement < ActiveRecord::Base; end
Use MapWithMeasurement.find_all.
You can use wannabe_bool gem.
https://github.com/prodis/wannabe_bool
This gem implements a #to_b method for String, Integer, Symbol and NilClass classes.
class Map < ActiveRecord::Base
def measurements_exist
self[:measurements_exist].to_b
end
end
Here is another solution:
boolean = (value_from_postgres =~ /^t$/i) == 0
converts a value_from_postgres of 't' to boolean true or 'f' to boolean false
$irb
2.2.1 :001 > value_from_postgres = 't'
=> "t"
2.2.1 :002 > (value_from_postgres =~ /^t$/i) == 0
=> true
2.2.1 :003 > value_from_postgres = 'f'
=> "f"
2.2.1 :004 > (value_from_postgres =~ /^t$/i) == 0
=> false
The regular expresion in this line could be modified to match /^true$/i if you are expecting a string "true" or "false". This is more flexible than using a ternary or a gem, because you can write it to convert a match to any regex to a boolean true.
Using a ternary it looks like:
boolean = value_from_postgres.eql?('t') ? true : false

Is it possible to have variable find conditions for both the key and value?

I'm trying to pass in both the field and the value in a find call:
#employee = Employee.find(:all,
:conditions => [ '? = ?', params[:key], params[:value].to_i)
The output is
SELECT * FROM `employees` WHERE ('is_manager' = 1)
Which returns no results, however when I try this directly in mysqsl using the same call without the '' around is_manager, it works fine. How do I convert my params[:key] value to a symbol so that the resulting SQL call looks like:
SELECT * FROM `employees` WHERE (is_manager = 1)
Thanks,
D
If you want to convert a string to symbol(which is what params[:key] produces, all you need to do is
params[:key].to_s.to_sym
2 points:
A word of caution : symbols are
not garbage collected.
Make sure your key is not a
number, if you convert to_s first
then to_sym, your code will work but
you may get a wierd symbol like
this:
:"5"
You could use variable substitution for column name instead of using bind values:
# make sure the key passed is a valid column
if Employee.columns_hash[params[:key]]
Employee.all :conditions => [ "#{params[:key]} = ?", params[:value]]
end
You can further secure the solution by ensuring column name passed belongs to a pre selected set.:
if ["first_name", "last_name"].include? [params[:key]]
Employee.all :conditions => [ "#{params[:key]} = ?", params[:value]]
end
"string".to_sym

Resources