Fixing a bug

Jan 31, 22

On another side project that I have built, we measure user’s steps and pit them against each other for supreme glory.

Unfortunately I have spotted a bug that needs to be fixed, lest any user’s glory be diminished 😇

As part of the dashboard, we display the users weekly step average that should run from Monday -> Sunday. Therefore on a Monday, we would expect the steps to be reset to 0, and after submitting the steps for the day, the average should be the submitted total for Monday only.

e.g

10_000 steps / 1 day == 10_000 steps

However upon submitting the steps today, I noticed that the total was still 0 😱. Not a great user experience when bragging rights are on the line!

Debug mode: Activated

In order to diagnose the bug - lets start by looking at where the suspect code is defined.

scope :weekly_average, -> {
  where('date > ?', DateTime.current.beginning_of_week).average(:steps).to_i }

As we can see from the above - we are finding all records where the date is after the beginning of the week, and then taking an average of the steps and converting it to an integer to display to the user in the view.

It seems to be looking ok.

Now let’s take a look at the generated SQL

>> User.first.step_counts.weekly_average
User Load (0.7ms)
SELECT "users".* FROM "users"
ORDER BY "users"."id"
ASC LIMIT $1  [["LIMIT", 1]]

StepCount Average (5.8ms)
SELECT AVG("step_counts"."steps")
FROM "step_counts"
WHERE "step_counts"."user_id" = $1
AND (date > '2022-01-31 00:00:00')  [["user_id", 1]]

=> 0

huh - thats weird, I have created a record today (31 Jan 2022), so I would expect to see a little more than 0

lets check the generated record on our local machine

>> StepCount.last
StepCount Load (0.8ms)
SELECT "step_counts".*
FROM "step_counts"
ORDER BY "step_counts"."id"
DESC LIMIT $1  [["LIMIT", 1]]

=> #<StepCount:0x0000000148058018
id: 15,
steps: 10000,
user_id: 1,
date: Mon, 31 Jan 2022,
created_at: Mon, 31 Jan 2022 22:21:06.003467000 UTC +00:00,
updated_at: Mon, 31 Jan 2022 22:21:06.003467000 UTC +00:00>

Ja, the record has definitely been created - und no issues with the integrity of the data, looks allll good.

Puts on thinking cap

I think I see the issue…

When we create the step_count, we manually timestamp the date field as in the future we want the users to be able to select the day when they completed the steps.

If my intuition is correct, the full date on the record will be exactly midnight on the day the record was created.

For reference, we are talking about this field in the above snippet: date: Mon, 31 Jan 2022

If we interrogate the record for the full timestamp:

>> StepCount.last.date.to_time
StepCount Load (0.8ms)
SELECT "step_counts".*
FROM "step_counts"
ORDER BY "step_counts"."id"
DESC LIMIT $1  [["LIMIT", 1]]

=> 2022-01-31 00:00:00 +0000

Aha! As we can see - the timestamp is exactly midnight.

So, going back to the first snippet we can expose the subtle bug

scope :weekly_average, -> {
  where('date > ?', DateTime.current.beginning_of_week).average(:steps).to_i }

We are checking that the date is after the beginning of the week, but it is actually the exact same time!

Therefore all we need to do to resolve is add one of these badboys =

scope :weekly_average, -> {
  where('date >= ?', DateTime.current.beginning_of_week).average(:steps).to_i }

Und now let’s check our fix

>> User.first.step_counts.weekly_average
User Load (0.9ms)
SELECT "users".*
FROM "users"
ORDER BY "users"."id"
ASC LIMIT $1  [["LIMIT", 1]]

StepCount Average (5.8ms)
SELECT AVG("step_counts"."steps")
FROM "step_counts"
WHERE "step_counts"."user_id" = $1
AND (date >= '2022-01-31 00:00:00') [["user_id", 1]]

=> 10000

Huzzah!

At this point I would recommend adding a regression test to our test suite, however as this is a fun little app, we have minimal tests (sorry mum)

Anyway - that’s enough fun for tonight. Goodbye