Summer Sale! All accounts are 50% off this week.

Moe's avatar
Level 6

Eloquent update not working

For one of my tests i try to update the created_at timestamp.

But this update will be completely ignored. I don't know why.

The test

public function it_fetches_a_feed_for_any_user ()
    {
        $user = $this->signIn();

        factory(Ticket::class, 2)->create(['user_id' => $user->id]);

        $user->activities->first()->update(['created_at' => Carbon::now()->subWeek()]);

        $feed = Activity::feed($user);

        $this->assertTrue($feed->keys()->contains(Carbon::now()->format('Y-m-d')));

        $this->assertTrue($feed->keys()->contains(Carbon::now()->subWeek()->format('Y-m-d')));
    }
    

The feed function at my Activity class

public static function feed (User $user, $number_of_activities = 25)
    {
        return static::where('user_id', $user->id)
            ->latest()
            ->with('subject')
            ->take($number_of_activities)
            ->get()
            ->groupBy(function ($activity) {
                return $activity->created_at->format('Y-m-d');
            });
    }

This is also one of my settings in the Activity class

protected $guarded = [];

When i dump and die the activities I see the next result

dd($user->activities)

...
#original: array:6 [
    "user_id" => "1"
    "type" => "created_ticket"
    "subject_id" => "1"
    "subject_type" => "App\Ticket"
    "created_at" => "2019-09-27 13:06:00"
    "updated_at" => "2019-10-04 13:06:00"
  ]
  #changes: array:1 [
    "created_at" => "2019-09-27 13:06:00"
  ]
...

But when I dump and die the actual feed, this is completely gone.

What am I missing?

0 likes
23 replies
Moe's avatar
Level 6

The update is also gone!

Sti3bas's avatar

@moe might be time issue. Do you tried to add Carbon::setTestNow(Carbon::now()); at the top?

Moe's avatar
Level 6

Doesn't work.

dd($user->activities)

results

#attributes: array:6 [
        "user_id" => "1"
        "type" => "created_ticket"
        "subject_id" => "1"
        "subject_type" => "App\Ticket"
        "created_at" => "2019-09-29 12:28:13"
        "updated_at" => "2019-10-06 12:28:13"
      ]
      #original: array:6 [
        "user_id" => "1"
        "type" => "created_ticket"
        "subject_id" => "1"
        "subject_type" => "App\Ticket"
        "created_at" => "2019-09-29 12:28:13"
        "updated_at" => "2019-10-06 12:28:13"
      ]
      #changes: array:1 [
        "created_at" => "2019-09-29 12:28:13"
      ]

whit the fresh method

dd($user->fresh()->activities);

results

#attributes: array:6 [
        "user_id" => "1"
        "type" => "created_ticket"
        "subject_id" => "1"
        "subject_type" => "App\Ticket"
        "created_at" => "2019-10-06 12:29:32"
        "updated_at" => "2019-10-06 12:29:32"
      ]
      #original: array:6 [
        "user_id" => "1"
        "type" => "created_ticket"
        "subject_id" => "1"
        "subject_type" => "App\Ticket"
        "created_at" => "2019-10-06 12:29:32"
        "updated_at" => "2019-10-06 12:29:32"
      ]
      #changes: []

When i run the feed method on the activity class, i get the results like when i "fresh" my model. (Updates gone)

Nakov's avatar

@moe and what does your $feed contains?

Can you print here what do you get when trying

dd($feed->keys());
mstrauss's avatar

@moe

How about using the refresh method to refresh the User and its related models (i.e. `Activity)

public function it_fetches_a_feed_for_any_user ()
    {
        $user = $this->signIn();

        factory(Ticket::class, 2)->create(['user_id' => $user->id]);

        $user->activities->first()->update(['created_at' => Carbon::now()->subWeek()]);

        // add this line
    $user->refresh(); 

        $feed = Activity::feed($user);

        $this->assertTrue($feed->keys()->contains(Carbon::now()->format('Y-m-d')));

        $this->assertTrue($feed->keys()->contains(Carbon::now()->subWeek()->format('Y-m-d')));
    }
Moe's avatar
Level 6

You can find the feed method above.

Illuminate\Support\Collection^ {#1192
  #items: array:1 [
    0 => "2019-10-06"
  ]
}
Nakov's avatar

I saw the method above for sure, just wanted to know what is the result that the query returns.

I mean from your results above:

#original: array:6 [
    "user_id" => "1"
    "type" => "created_ticket"
    "subject_id" => "1"
    "subject_type" => "App\Ticket"
    "created_at" => "2019-09-27 13:06:00"
    "updated_at" => "2019-10-04 13:06:00"
  ]
  #changes: array:1 [
    "created_at" => "2019-09-27 13:06:00"
  ]

It is visible that the created_at field contains the correct date, meaning the subWeek() one. So I tested the same approach on one of my one to many tables with the user and I get the positive result. So the only thing that I can think of asking now is if you can share what configuration do you use for the database on the testing?

For example this is mine in the phpunit.xml:

<server name="APP_ENV" value="testing"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
Moe's avatar
Level 6

same

        <server name="APP_ENV" value="testing"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
Moe's avatar
Level 6

It's a polymorphic relationship. Don't know if that make some difference.

Nakov's avatar

Okay, can you show the code on how do you create activities? Because I believe that the problem lies there.

Here is my commented code of yours:


// You are creating two tickets:
factory(Ticket::class, 2)->create(['user_id' => $user->id]);

// there should be two activities at this point
// what does this return:
dd($user->activities->count());

// You are updating the first activity to be the last week
$user->activities->first()->update(['created_at' => Carbon::now()->subWeek()]);

// getting the feed
$feed = Activity::feed($user);

// $feed->keys() === 0 => "2019-10-06" - the result as you've shown, so one activity is gone, or both are grouped because of the date not being updated above

Try to reproduce the same scenario using tinker in your local database, and make sure that it does what you expect then try to find the bad part in the test maybe :)

Moe's avatar
Level 6

The count returns 2 as expected.

And feed keys also returns the 2 records, but the created date of the first one isn't changed.

The code works on the app, but not in test.

Nakov's avatar

@moe howcome it shows two keys when you are grouping based on a created_at date, and if it is not changed as you say, you should get one, not two?!?

You showed a result above that showed only one item when printing $feed->keys()?

So share the part on how you create activities, because the whole discussion doesn't adds up for me..

Moe's avatar
Level 6

Sorry the keys function returns just one key. The 2 records are below that one key.

Nakov's avatar

@moe what should've been asked long time ago, but let's do it now is which version of the tools you use?

What version of Laravel, PHPUnit and MySQL?

As I don't know what else to say, I really tried on my project exactly the same way as you do, and I got the correct results back.

Moe's avatar
Level 6

PHPUnit 7.5.16 MySQL 5.7.24 Laravel 5.8

This all can be updated if needed!

Nakov's avatar

The only difference is that I am running MySQL 8.0.16 which I don't know why it would make a difference.

So please share the code on how do you create the activities? Or even better the full code from your Activity class.

Moe's avatar
Level 6

This code is based on the demo off @Jeffrey in the video https://laracasts.com/series/lets-build-a-forum-with-laravel/episodes/27

<?php

namespace App\Traits;

use App\Activity;

trait RecordsActivity
{
    protected static function bootRecordsActivity ()
    {
        if (auth()->guest()) return;

        foreach (static::getRecordableEvents() as $event) {
            static::$event(function ($model) use ($event) {
                $model->recordActivity($event);
            });
        }
    }

    protected static function getRecordableEvents ()
    {
        return ['created'];
    }

    public function activity ()
    {
        return $this->morphMany(Activity::class, 'subject');
    }

    protected function recordActivity ($event)
    {
        $this->activity()->create([
            'user_id' => auth()->id(),
            'type' => $this->getActivityType($event),
        ]);
    }

    protected function getActivityType ($event)
    {
        return strtolower($event . '_' . (new \ReflectionClass($this))->getShortName());
    }
}

The code is working, but just not in the unit test.

Nakov's avatar

@moe Well, I've also watched those series and what I believe is you are using them wrong. Because you should never manually update an entry in the activities table, but the Activities are there to track your modifications on the Ticket's itself.

So for example in your case, you should allow the updated event to also be registered:

protected static function getRecordableEvents ()
{
    return ['created', 'updated'];
}

Then from the two tickets that you purchased for the user, you should update one for example (remember the ticket, not the activities), and it does not have to be the created_at field, but any field. It should record an activity that a ticket has been updated. And you test that there is an extra activity recorded.

Because your test is not a real user scenario in my opinion.

munazzil's avatar

In the Activity class use below one,

    public $incrementing = false;
Thrax's avatar

Have you by any chance used the DatabaseTransactions in your test class? This will always rollback the transaction after the test and you will not see any changes.

Moe's avatar
Level 6

Thanks to @munazzil I added an 'id' to my activities table and that solved the problem.

Thank you all!

Please or to participate in this conversation.