El objetivo de esta lección será establecer un mecanismo mediante el cual podamos destacar contenido nuevo para los visitantes que regresan al sitio web. Entonces, por ejemplo, considere que un usuario visita el sitio y lee todos los hilos de la página principal y luego se va por el día. Más tarde, un visitante diferente visita el sitio y agrega una nueva respuesta para decir tres hilos diferentes. Cuando el primer usuario regresa, esos tres hilos con nuevas respuestas deben resaltarse de alguna manera. En nuestro caso, tal vez podamos agregar una fuente en negrita a los hilos que tienen contenido nuevo para resaltar al usuario. Veamos cómo podemos implementar esto ahora.
Impulsar contenido nuevo a través de la caché
Todos los modelos en Laravel hacen uso de un campo updated_at en la base de datos. Esto significa que sabremos cuál ha sido la actualización más reciente en un hilo en particular. Lo que eso significa es que si registramos una marca de tiempo de cuando un usuario visita un hilo en particular, entonces podemos comparar esa marca de tiempo con el valor updated_at para ver cuál es más reciente. Si la marca de tiempo que visitó el usuario es más reciente que la marca de tiempo updated_at, no es necesario resaltar el contenido. Por otro lado, si el valor updated_at es más reciente que la marca de tiempo de la caché de la visita más reciente del usuario, eso significa que hay contenido nuevo que debe resaltarse para el usuario.
Registro de visitas de usuarios
Si seguimos la lógica de comparar un valor de tiempo de la caché con lo que está en la base de datos, entonces necesitamos una forma de registrar cuándo el usuario ha visitado una página determinada. Quizás podamos agregar un par de métodos. A continuación se resaltan los métodos read () y wentThreadCacheKey () en el modelo de usuario. VisitThreadCacheKey () es donde ocurre la esencia de esta característica. En él, una cadena única se define como la clave que se utilizará al almacenar un registro de una visita en la caché. Trabajando en conjunto con el método read (), almacenamos una nueva clave en la caché donde es igual a la hora actual según Carbon :: now ().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?php namespace App; use Carbon\Carbon; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', 'email' ]; public function getRouteKeyName() { return 'name'; } public function threads() { return $this->hasMany(Thread::class)->latest(); } public function activity() { return $this->hasMany(Activity::class); } public function read($thread) { cache()->forever( $this->visitedThreadCacheKey($thread), Carbon::now() ); } public function visitedThreadCacheKey($thread) { return sprintf("users.%s.visits.%s", $this->id, $thread->id); } } |
Buscando actualizaciones en el modelo de subproceso
Los métodos que están haciendo el trabajo pesado para esta función ya están definidos en el modelo de usuario. Ahora, podemos agregar un método al modelo Thread que busca nuevas actualizaciones para un usuario. Tenga en cuenta el método hasUpdatesFor () resaltado a continuación en el modelo Thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | <?php namespace App; use App\Events\ThreadReceivedNewReply; use Illuminate\Database\Eloquent\Model; use App\User; class Thread extends Model { use RecordsActivity; protected $guarded = []; protected $with = ['creator', 'channel']; protected $appends = ['isSubscribedTo']; protected static function boot() { parent::boot(); static::addGlobalScope('replyCount', function ($builder) { $builder->withCount('replies'); }); static::deleting(function ($thread) { $thread->replies->each->delete(); }); } public function path() { return '/threads/' . $this->channel->slug . '/' . $this->id; } public function replies() { return $this->hasMany(Reply::class); } public function creator() { return $this->belongsTo(User::class, 'user_id'); } public function channel() { return $this->belongsTo(Channel::class); } public function addReply($reply) { $reply = $this->replies()->create($reply); event(new ThreadReceivedNewReply($reply)); return $reply; } public function scopeFilter($query, $filters) { return $filters->apply($query); } public function subscribe($userId = null) { $this->subscriptions()->create([ 'user_id' => $userId ?: auth()->id() ]); return $this; } public function unsubscribe($userId = null) { $this->subscriptions() ->where('user_id', $userId ?: auth()->id()) ->delete(); } public function subscriptions() { return $this->hasMany(ThreadSubscription::class); } public function getIsSubscribedToAttribute() { return $this->subscriptions() ->where('user_id', auth()->id()) ->exists(); } public function hasUpdatesFor(User $user) { $key = $user->visitedThreadCacheKey($this); return $this->updated_at > cache($key); } } |
Lo que hace el método hasUpdatesFor () por nosotros es verificar en la caché la marca de tiempo de la última vez que el usuario visitó el hilo en cuestión. Entonces estamos haciendo una comparación. Estamos verificando si la marca de tiempo updated_at es mayor que el valor de la marca de tiempo que se encuentra en la caché. Si es mayor que, entonces ha sucedido algo como una nueva respuesta para actualizar este hilo. Eso significa que el usuario necesita ver el nuevo contenido resaltado para que la función devuelva verdadero.
¿Dónde tendrá lugar la grabación real de la visita? Tiene sentido que en ThreadsController, el método show () sea lo que se use para mostrar un hilo a un usuario. Por lo tanto, si un usuario que ha iniciado sesión visita un hilo, este es el método que se lo muestra. A continuación se resalta un fragmento que hace una verificación rápida para ver si el usuario está conectado. Si eso es cierto, entonces llamamos al método read () que definimos en el modelo de Usuario justo arriba. Cuando este método read () se activa, se apoya en el método VisitThreadCacheKey () para almacenar una clave en la caché usando Carbon :: now () como marca de tiempo. ¡Excelente!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | <?php namespace App\Http\Controllers; use App\Filters\ThreadFilters; use App\Thread; use App\Channel; use Illuminate\Http\Request; class ThreadsController extends Controller { public function __construct() { $this->middleware('auth')->except(['index', 'show']); } public function index(Channel $channel, ThreadFilters $filters) { $threads = $this->getThreads($channel, $filters); if (request()->wantsJson()) { return $threads; } return view('threads.index', compact('threads')); } public function create() { return view('threads.create'); } public function store(Request $request) { $this->validate($request, [ 'title' => 'required', 'body' => 'required', 'channel_id' => 'required|exists:channels,id' ]); $thread = Thread::create([ 'user_id' => auth()->id(), 'channel_id' => request('channel_id'), 'title' => request('title'), 'body' => request('body') ]); return redirect($thread->path()) ->with('flash', 'Your thread was published!'); } public function show($channel, Thread $thread) { if (auth()->check()) { auth()->user()->read($thread); } return view('threads.show', [ 'thread' => $thread ]); } public function edit(Thread $thread) { // } public function update(Request $request, Thread $thread) { // } public function destroy($channel, Thread $thread) { $this->authorize('update', $thread); $thread->delete(); if (request()->wantsJson()) { return response([], 204); } return redirect('/threads'); } protected function getThreads(Channel $channel, ThreadFilters $filters) { $threads = Thread::latest()->filter($filters); if ($channel->exists) { $threads->where('channel_id', $channel->id); } $threads = $threads->get(); return $threads; } } |
Agregar lógica en Blade para resaltar contenido
Todo está en su lugar ahora para que esto funcione. Todo lo que tenemos que hacer es actualizar el archivo de vista threads / index.blade.php para resaltar el nuevo contenido. ¿Cómo haremos eso? Bueno, aquí es donde podemos hacer uso del método hasUpdatesFor () que definimos en el modelo Thread. El código resaltado a continuación está verificando si el hilo tiene contenido nuevo para el usuario autenticado que actualmente está conectado al sitio web. Si eso devuelve verdadero, entonces el título del hilo está envuelto en etiquetas <strong> para dar una indicación visual de que hay contenido nuevo listo para que el usuario se ponga al día. Si no devuelve verdadero, entonces el título del hilo se muestra en la página con fuente normal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> @forelse($threads as $thread) <div class="panel panel-default"> <div class="panel-heading"> <div class="level"> <h4 class="flex"> <a href="{{ $thread->path() }}"> @if (auth()->check() && $thread->hasUpdatesFor(auth()->user())) <strong> {{ $thread->title }} </strong> @else {{ $thread->title }} @endif </a> </h4> <a href="{{$thread->path()}}"> <strong>{{$thread->replies_count}} {{ str_plural('reply', $thread->replies_count) }}</strong> </a> </div> </div> <div class="panel-body"> <div class="body"> {{ $thread->body }} </div> </div> </div> @empty <p>There are no results yet</p> @endforelse </div> </div> </div> @endsection |
Ver contenido nuevo destacado en acción
Hemos iniciado sesión como usuario Nikola Tesla y hemos visitado todos los hilos de la página principal del sitio web. Por lo tanto, podemos ver que no se resaltan los hilos a continuación.
Llega Tom y decide agregar una respuesta al "¡Santo Guacamole!" hilo.
Bueno, en este punto, la lógica que hemos construido en el sitio debería poder resaltar el nuevo contenido. En otras palabras, el "¡Santo Guacamole!" El título del hilo ahora debería aparecer en negrita cuando Nikola Tesla regrese al sitio web como visitante recurrente. ¡Parece que esto está funcionando!
Nota: Si tiene problemas para que esto funcione, agregue una llamada a $ this-> touch (); en el método addReply () del modelo Thread. Esto garantizará que la columna updated_at se actualice correctamente al agregar una nueva respuesta.
Construya primero, pruebe después
Este tutorial nos hizo construir primero y probar después. No se preocupe, podemos agregar una prueba para esta nueva función de contenido aquí. La prueba se destaca a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | <?php namespace Tests\Unit; use Illuminate\Foundation\Testing\DatabaseMigrations; use Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; class ThreadTest extends TestCase { use DatabaseMigrations; protected $thread; public function setUp() { parent::setUp(); $this->thread = $thread = factory('App\Thread')->create(); } public function test_a_thread_can_make_a_string_path() { $thread = create('App\Thread'); $this->assertEquals('/threads/' . $thread->channel->slug . '/' . $thread->id, $thread->path()); } public function test_a_thread_has_replies() { $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $this->thread->replies); } public function test_a_thread_has_a_creator() { $this->assertInstanceOf('App\User', $this->thread->creator); } public function test_a_thread_can_add_a_reply() { $this->thread->addReply([ 'body' => 'Chipoltle', 'user_id' => 1 ]); $this->assertCount(1, $this->thread->replies); } public function test_a_thread_belongs_to_a_channel() { $thread = create('App\Thread'); $this->assertInstanceOf('App\Channel', $thread->channel); } public function test_a_thread_can_be_subscribed_to() { $thread = create('App\Thread'); $thread->subscribe($userId = 1); $this->assertEquals( 1, $thread->subscriptions()->where('user_id', $userId)->count() ); } public function test_a_thread_can_be_unsubscribed_from() { $thread = create('App\Thread'); $thread->subscribe($userId = 1); $thread->unsubscribe($userId); $this->assertCount(0, $thread->subscriptions); } public function test_a_thread_can_check_if_the_authenticated_user_has_read_all_replies() { $this->signIn(); $thread = create('App\Thread'); tap(auth()->user(), function ($user) use ($thread) { $this->assertTrue($thread->hasUpdatesFor($user)); $user->read($thread); $this->assertFalse($thread->hasUpdatesFor($user)); }); } } |
Y ejecutar la prueba valida que la funcionalidad esté funcionando según lo diseñado.
vagrant @ homestead: ~ / Code / forumio $ phpunit --filter test_a_thread_can_check_if_the_authenticated_user_has_read_all_replies
PHPUnit 6.5.5 por Sebastian Bergmann y colaboradores.
. 1/1 (100%)
Tiempo: 1,16 segundos, memoria: 8,00 MB
OK (1 prueba, 2 afirmaciones)
Resumen de cómo destacar contenido nuevo para visitantes recurrentes
Para ser justos, esta tarea podría haberse logrado de muchas formas diferentes. Sin embargo, el mismo principio es válido. Si desea destacar contenido nuevo para los visitantes que regresan a su sitio web, primero debe registrar cuándo fue la última vez que verificaron ese contenido en particular. Luego, configura la lógica para ver cuándo se actualizó ese contenido y cuándo el usuario vio el contenido por última vez. Con base en esa lógica, sabrá si debe proporcionar una buena señal visual al usuario de que hay nuevo contenido para ponerse al día.
0 Comentarios