How to get rails to return SUM(columnName) attributes with right datatype instead of a string?

前端 未结 4 1038
北海茫月
北海茫月 2020-12-30 17:27

Assume a query of the following form

operatingExpenses = Expense.find(:all,
      {:select=>\"categories.activityType, categories.name heading, sum(amount         


        
相关标签:
4条回答
  • 2020-12-30 17:49

    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:

    • Don't do this; it isn't the way Active record is supposed to work; the total of the details belongs to whatever is rolling them up; it isn't a detail object itself.
    • If you must treat it as a detail, name the field 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.

    0 讨论(0)
  • 2020-12-30 17:59

    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.

    0 讨论(0)
  • 2020-12-30 18:01

    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. :)

    0 讨论(0)
  • 2020-12-30 18:05

    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.

    0 讨论(0)
提交回复
热议问题