Assume a query of the following form
operatingExpenses = Expense.find(:all,
{:select=>\"categories.activityType, categories.name heading, sum(amount
Active record is trying to return "Expense" objects built from the results of your query. But expense objects don't have a totalAmount field, so this is unrequested "bonus data" as far as ActiveRecord can tell. It coud just throw it out, but instead it keeps it for you, with the most forgiving type assumptions it can.
My suggestions:
amount
so ActiveRecord knows what to do with it.If you must have the total as its own field, add an accessor in your Expense model like so:
def total_amount
totalAmount.to_f
end
and use it like so: operatingExpenses[1].total_amount
in your code.
Because with :select
Rails gives you no way of telling it (or the driver) to what kind of native type to bind the result set's fields (once it's all retrieved -- although most of not all drivers will know, internally, what the column SQL types are) -- so it's all recorded as strings and the SQL type information is generally thrown away. This applies to all fields all the time. You have to perform the conversion manually (to_f
), just like Rails' ActiveRecord.instantiate
method has to, internally, when you use plain ActiveRecord.find
without :select
or :joins
(and it has to populate a non-string attribute.)
You may confirm this by looking at your database driver's select_raw
method.
Cheers, V.
For custom queries that require basically a whole custom SQL statement (your find above doesn't exactly abstract much from you) I like to set up a quick little new model that represents the new information. i.e.
class OperatingExpenseReportDatum
attr_accessor :type, :heading, :total
def initialize(row)
# set values from row, like
@total = row["total"].to_f
end
end
and then write a helper method into the model, something like:
class Expense < AR::Base
...
def self.operating_expenses
rows = connection.select_all "SQL STATEMENT HERE"
rows.map { |row| OperatingExpenseReportDatum.new(row) }
end
end
Then your report generation is all nice:
#controller
@expenses = Expense.operating_expenses
#view
<% @expenses.each do |expense| %>
<%= expense.type %>: <%= expense.total %>
<% end %>
Or something similar. :)
Just do find_by_sql
and apply the SUM
on a decimal column :
operating_expenses = Expense.find_by_sql ['SELECT cat.activityType, cat.name heading, SUM(exp.amount) AS amount FROM expenses exp JOIN expense_categories cat ON exp.category_id = cat.id GROUP BY cat.id, cat.activityType, cat.name, exp.id ORDER BY cat.activityType, amount DESC']
Under the 'amount' column of operating_expenses you'll got your total automatically converted to decimal.
N.B. : cat.id and exp.id are required to respect SQL standard.