cybercog / laravel-love

Add Social Reactions to Laravel Eloquent Models. It lets people express how they feel about the content. Fully customizable Weighted Reaction System & Reaction Type System with Like, Dislike and any other custom emotion types. Do you react?
https://komarev.com/sources/laravel-love
MIT License
1.16k stars 71 forks source link

Expose the datetime of reactions #192

Closed vesper8 closed 1 year ago

vesper8 commented 3 years ago

This is a continuation of an idea started in https://github.com/cybercog/laravel-love/issues/181

I would find it extremely useful to be able to easily retrieve the reaction date when using scopeWhereReactedTo or scopeWhereReactedBy

I have users liking other users and I would like to be able to display to a user who has liked them and when specifically that like has been received

Right now those scopes return the users that have reacted to/by the specfied reaction type.. but I'm not seeing an easy way to also retrieve more details about that reaction, namely the datetime it occurred at

The following lends itself to a n+1 problem.. but this is the workaround I came up with to retrieve the raw reaction from the db table in order to expose the datetime of the reaction itself which is useful in my case

$rawReaction = (new Love)->getRawReaction($user, User::find(request('viaResourceId')), 'LIKE');
<?php

namespace App\Helpers;

use App\Models\LoveReaction;

class Love
{
    public function getRawReaction($reactable, $reacterable, $reactionType)
    {
        $rawReaction = LoveReaction::where('reactant_id', $reactable->love_reactant_id)
        ->where('reacter_id', $reacterable->love_reacter_id)
        ->where('reaction_type_id', config('love.reaction-types')[$reactionType])
        ->first();

        return $rawReaction;
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class LoveReaction extends Model
{
    protected $table = 'love_reactions';
}
antonkomarev commented 1 year ago

I will try to find time to look forward to it.

antonkomarev commented 1 year ago

@vesper8 could you try new scopes in master branch (before the release)? It looks like they already have required feature, or I'm missing something.

$usersReactedToMe = User::query()
    ->whereReactedTo($myUser)
    ->get();

dd($usersReactedToMe->first()->toArray());

Output:

array:6 [
  "id" => 1
  "love_reactant_id" => 2
  "love_reacter_id" => 1
  "name" => "Pearlie Bergstrom"
  "created_at" => "2023-01-05T19:44:27.000000Z"
  "updated_at" => "2023-01-05T19:44:27.000000Z"
]

Update: oh... this might be created_at of the User record...

This happens because Laravel using sub-queries:

select *
from "users"
where exists (
    select *
    from "love_reacters"
    where "users"."love_reacter_id" = "love_reacters"."id"
    and exists (
        select *
        from "love_reactions"
        where "love_reacters"."id" = "love_reactions"."reacter_id"
        and "reactant_id" = ?
    )
)

This kind of magic doesn't allow us to add reaction related data to outer select. It will be possible if we will start using joins, but it may lead us to total package rewrite. The more I look on it — more I think that such features should be done aside.

antonkomarev commented 1 year ago

This query will be more efficient if we will rewrite in joins and will allow to add more joined data:

$usersReactedToMe = DB::select(
    <<<'SQL'
    SELECT
        users.id AS user_id,
        users.name AS user_name,
        users.created_at AS user_created_at,
        love_reactions.rate AS reaction_rate,
        love_reactions.created_at AS reaction_created_at
    FROM users
    JOIN love_reacters
      ON users.love_reacter_id = love_reacters.id
    JOIN love_reactions
      ON love_reacters.id = love_reactions.reacter_id
      AND love_reactions.reactant_id = :reactant_id
    SQL,
    [
        'reactant_id' => $myUser->getLoveReactant()->getId(),
    ]
);

dd($usersReactedToMe);
array:1 [
  0 => {
    +"user_id": 1
    +"user_name": "Tiffany Collins"
    +"user_created_at": "2023-01-05 20:14:16"
    +"reaction_rate": 1
    +"reaction_created_at": "2023-01-05 20:14:16"
  }
]
antonkomarev commented 1 year ago

Easiest way without global changes right now:

$reactions = Reaction::query()
    ->with('reacter.reacterable')
    ->where('reactant_id', $myUser->getLoveReactant()->getId())
    ->get();

dd($reactions->first()->toArray()); // toArray call is just for example to have clean output

Output:

array:8 [
  "id" => "1"
  "reactant_id" => 4
  "reacter_id" => 2
  "reaction_type_id" => 1
  "rate" => 1.0
  "created_at" => "2023-01-05T20:26:18.000000Z"
  "updated_at" => "2023-01-05T20:26:18.000000Z"
  "reacter" => array:5 [
    "id" => "2"
    "type" => "Cog\Tests\Laravel\Love\Stubs\Models\User"
    "created_at" => "2023-01-05T20:26:18.000000Z"
    "updated_at" => "2023-01-05T20:26:18.000000Z"
    "reacterable" => array:6 [
      "id" => 1
      "love_reactant_id" => 1
      "love_reacter_id" => 2
      "name" => "Earlene Mitchell"
      "created_at" => "2023-01-05T20:26:18.000000Z"
      "updated_at" => "2023-01-05T20:26:18.000000Z"
    ]
  ]
]

Then you will have reactions with their meta data, and users who reacted on you in reacter.reacterable.

vesper8 commented 1 year ago

Loving what I'm seeing @antonkomarev : )

While you're digging into this, it would be great to be able to query models that have recently reacted or been reacted to other models.. so using a timestamp when querying. So if I wanted to know the most "liked" model in the last 30 days for example.

->whereReactedToBetween or something like that : )

antonkomarev commented 1 year ago

Good idea @vesper8. The only thing I'm afraid of — bloating app models API with some magic scope methods.