I\'m trying to run a query in a very quick and dirty way in Rails, without putting the rest of the model in place. I know this is bad practice but I just need a quick resul
This is because find_by_sql
returns a model, not data. If you want to do a direct fetch of the data in question, use something like this:
ShippingZonePrice.connection.select_value(query)
There are a number of direct-access utility methods available through connection
that can fetch single values, a singular array, rows of arrays, or rows of hashes. Look at the documentation for ActiveRecord::ConnectionAdapters::DatabaseStatements
.
As when writing an SQL directly, you should be very careful to not create SQL injection bugs. This is why it is usually best to encapsulate this method somewhere safe. Example:
class ShippingZonePrice < ActiveRecord::Base
def self.price_for_item(item)
self.connection.select_value(
self.sanitize_sql(
%Q[
SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id=?
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1
],
item.id
)
)
end
end
If not for you saying that you tried and failed accessing [0]
i'ld say you want to put
@item.shipping_price.first.price # I guess BSeven just forgot the .first. in his solution
into the view...strange
So, I had a hacky solution for this, but it works great. Create a table that has the same output as your function and reference it, then just call a function that does a find_by_sql to populate the model.
Create a dummy table:
CREATE TABLE report.compliance_year (
id BIGSERIAL,
year TIMESTAMP,
compliance NUMERIC(20,2),
fund_id INT);
Then, create a model that uses the empty table:
class Visualization::ComplianceByYear < ActiveRecord::Base
self.table_name = 'report.compliance_year'
def compliance_by_year(fund_id)
Visualization::ComplianceByYear.find_by_sql(["
SELECT year, compliance, fund_id
FROM report.usp_compliance_year(ARRAY[?])", fund_id])
end
end
In your controller, you can populate it:
def visualizations
@compliancebyyear = Visualization::ComplianceByYear.new()
@compliancefunds = @compliancebyyear.compliance_by_year(current_group.id)
binding.pry
end
Then, you can see it populate with what you need:
[1] pry(#<Thing::ThingCustomController>)> @compliancefunds
[
[0] #<Visualization::ComplianceByYear:0x00000008f78458> {
:year => Mon, 31 Dec 2012 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 1
},
[1] #<Visualization::ComplianceByYear:0x0000000a616a70> {
:year => Tue, 31 Dec 2013 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
},
[2] #<Visualization::ComplianceByYear:0x0000000a6162c8> {
:year => Wed, 31 Dec 2014 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
}
]
Since you are defining an instance method, I think it should return the price
if it exists or nil
Try something like this:
def shipping_price
ShippingZonePrice.find_by_sql(
"SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id = '#{self.id}'
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1").first.try(:price)
end
Then this should work for you:
@item.shipping_price
The first.try(:price)
part is needed because find_by_sql
may return an empty array. If you tried to do something like first.price
on an empty array, you would get an exception along the lines of NoMethodError: undefined method 'price' for nil:NilClass
.
@item.shipping_price.first.price
or
@item.shipping_price[0].price
Thanks Atastor for pointing that out!
When you use AS price
in find_by_sql
, price
becomes a property of the result.