jamesmills / watchable

Enable users to watch various models in your application.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to?

slakbal opened this issue · comments

Hi there,

Still a bit new to polymorphic relationships but based on your examples with a "user" watching "books"... what will the inverse be? For example, how can we retrieve the Books a User is watching? $books = User::watchingBooks()

Thus basically from a User first perspective get the collection of watched books.

Thanks?

This is a great question. I will look at this over the weekend.

Awesome thanks....!! Been breaking my head on this for hours now. I have something that is working but I'm not sure if it is correct because I think it might complicate eager loading of child models:

In User model:

public function watchingBooks()
{
    return $this->morphedByMany(Book::class, 'watchable', 'watch');
}

public function watchingShops()
{
    return $this->morphedByMany(Shop::class, 'watchable', 'watch');
}

I think because of the Watch Model overriding the standard table naming convention I had to add explicitly the 4th parameter.

The above does deliver the correct results like:

$result = auth()->user()->watchingBooks;

But image e.g. I want to eager load the Authors of all the books than I'm watching as per the above statement it becomes hairy.

e.g. get Books that are watched by the User and lazy load the Authors for the view to avoid the n+1 problem.

This could maybe help a little bit: https://github.com/spatie/laravel-tags

In that package it is turned around a bit:

//retrieving models that have any of the given tags
NewsItem::withAnyTags(['tag1', 'tag2']);

//retrieve models that have all of the given tags
NewsItem::withAllTags(['tag1', 'tag2']);

So in the case of the Books example, one could consider then rather to filter as follows:

Books::withWatcher($user_id); //if no user id is given then again your rule of auth()->id() could apply.

this could make eager loading easier:

Books::with('author')->withWatcher($user_id);

Could also consider filtering via Array of keys:

Books::with('author')->withWatchers( [ 15, 16, 18] );

or

$users = [collection of user models];
Books::with('author')->withWatchers( $users );

Hope this helps you. Looking forward to your reply. Thanks!

Hi there, did you maybe find some time to have a look at the above question? TX!

Hi,

I am not 100% sure what you want your end result to be... see if this helps.

Try adding this to your User class to test it.

public function watching()
{
    return $this->hasMany(Watch::class, 'user_id')->with('watchable');
}

public function watchingCollection($class = null)
{
    $query = $this->watching();

    if (!is_null($class)) {
        $query->where('watchable_type', $class);
    }

    $result = $query->with('watchable')->get();

    foreach ($result as $item) {
        if (isset($item->watchable->id)) {
            $watching[] = $item->watchable;
        }
    }

    return collect($watching);
}

And then you can call this with either

auth()->user()->watchingCollection(Order::class)

or

auth()->user()->watchingCollection()

So this will allow a user to follow many different Eloquent Models.

screenshot 2017-11-16 11 02 24

And will actually return a collection of those models directly. So, much like how you can work with the Laravel Notifications, you could build view partials for each.

screenshot 2017-11-16 11 02 32

Or, we could build on the watchingCollection() to give this

screenshot 2017-11-16 12 27 39

So you could display the user something customizable...

<div class="list-group">
    @foreach (auth()->user()->watchingCollection() as $watched_item)
        @include('watchable.partials.' . $watched_item['watched_model'])
    @endforeach
</div>

Maybe 'resources/views/watchable/partials/order.blade.php'

@slakbal did you find what you wanted?

    public function watching()
    {
        return $this->hasMany(Watch::class, 'user_id')
            ->with('watchable')
            ->orderBy('created_at', 'desc');
    }

    public function watchingCollection($class = null)
    {
        $query = $this->watching();

        if (!is_null($class)) {
            $query->where('watchable_type', $class);
        }

        $result = $query->with('watchable')->get();

        /*
         * This is here because we are using hasMany to get all the items the user is watching. This will not
         * respect soft deleted items on the 'with' relationship so we may get Watch objects that have an
         * empty Watchable relationship (because the model that's being watched has been deleted).
         * the
         */
        foreach ($result as $item) {
            if (isset($item->watchable->id)) {
                $watching[] = [
                    'watched_model' => snake_case(class_basename($item->watchable)),
                    'model' => $item->watchable,
                    'watched_at' => $item->created_at,
                ];
            }
        }

        return collect($watching);
    }

@jamesmills sorry for replying so much later. I will give this a try and give you a shout back. thanks!