tim_xyz
tim_xyz

Reputation: 13491

Generate array of daily avg values from db table (Rails)

Context: Trying to generating an array with 1 element for each created_at day in db table. Each element is the average of the points (integer) column from records with that created_at day. This will later be graphed to display the avg number of points on each day.

Result: I've been successful in doing this, but it feels like an unnecessary amount of code to generate the desired result.

Code:

def daily_avg
    # get all data for current user
    records = current_user.rounds

    # make array of long dates
    long_date_array = records.pluck(:created_at)

    # create array to store short dates
    short_date_array = []

    # remove time of day
    long_date_array.each do |date|
        short_date_array << date.strftime('%Y%m%d')
    end 

    # remove duplicate dates
    short_date_array.uniq!

    # array of avg by date
    array_of_avg_values = []

    # iterate through each day
    short_date_array.each do |date|
        temp_array = []

        # make array of records with this day
        records.each do |record|
            if date === record.created_at.strftime('%Y%m%d')
                temp_array << record.audio_points
            end
        end
        # calc avg by day and append to array_of_avg_values
        array_of_avg_values << temp_array.inject(0.0) { |sum, el| sum + el } / temp_array.size
    end
    render json: array_of_avg_values
end

Question: I think this is a common extraction problem needing to be solved by lots of applications, so I'm wondering if there's a known repeatable pattern for solving something like this?

Or a more optimal way to solve this?

(I'm barely a junior developer so any advice you can share would be appreciated!)

Upvotes: 2

Views: 150

Answers (3)

Maur&#237;cio Linhares
Maur&#237;cio Linhares

Reputation: 40333

Yes, that's a lot of unnecessary stuff when you can just go down to SQL to do it (I'm assuming you have a class called Round in your app):

class Round

  DAILY_AVERAGE_SELECT = "SELECT 
      DATE(rounds.created_at) AS day_date,
      AVG(rounds.audio_points) AS audio_points
    FROM rounds
    WHERE rounds.user_id = ?
    GROUP BY DATE(rounds.created_at) 
"

  def self.daily_average(user_id)
    connection.select_all(sanitize_sql_array([DAILY_AVERAGE_SELECT, user_id]), "daily-average")
  end

end

Doing this straight into the database will be faster (and also include less code) than doing it in ruby as you're doing now.

Upvotes: 2

Малъ Скрылевъ
Малъ Скрылевъ

Reputation: 16507

I advice you to do something like this:

grouped =
records.order(:created_at).group_by do |r|
   r.created_at.strftime('%Y%m%d')
end

At first here you generate proper SQL near to that you wish to get in first approximation, then group result records by created_at field converted to just a date.

points =
grouped.map do |(date, values)|
   [ date, values.reduce(0.0, :audio_points) / values.size ]
end.to_h

# => { "1-1-1970" => 155.0, ... }

Then you remap your grouped hash via array, to calculate average values with audio_points.

Upvotes: 0

Related Questions