Journal Entry, July 5th to July 12th 2019

July 12, 2019 · 5 min read

Laravel Nova

  1. Mon Jul 8

    • In this example the path has our directory and they're stored in a format like storage/app/public/images/HELL/originalFileName.extension. The image column saves only the name as it already appears.
    • The biggest takeaway from this is the usage $this->model()->path. At a resource level we can get the model columns used because it's defined as $this->model. Because we're on the detail view the path in this case is filled in. We don't need methods that include the models.
    • This is one of the biggest "aha!" moments with Nova where the docs aren't very intuitive nor do they promote this in any meaningful way in the examples I've seen.

      Image::make('Header Image', 'image')
          ->disk('public')
          ->store(function (Request $request, \App\Models\Bucket $model) {
              $path = "images/{$model->path}";
              $file = $request->image->storeAs($path, $request->image->getClientOriginalName(), 'public');
              return [
                  'image' => $request->image->getClientOriginalName(),
              ];
          })
          ->prunable()
          ->preview(function ($image, $disk) {
              return $image ? \Storage::disk($disk)->url("images/{$this->model()->path}/$image") : null;
          })
          ->thumbnail(function ($image, $disk) {
              return $image ? \Storage::disk($disk)->url("images/{$this->model()->path}/$image") : null;
          }),
  2. Tue Jul 9

    • In this example we're creating a read-only path column when the model is first created. Initially I looked to append the column ID but this is not possible without removing the image upload on the create form.
    • The getPathAttribute() essentially creates our path value in the same way from our slug and it's only used during the create model upload.
    • This is sleight of hand at its finest, the real path column is what we're looking for but we're hacking in functionality to work at the create form level, bypassing the need to restrict this to only during edits.

      public function getPathAttribute()
      {
          $path = (array_has($this->attributes, 'path') && $this->attributes['path']) ? $this->attributes['path'] : null;
          // We only care to calculate the path in the same manner as our creating method when the model is first used
          if ($path === null) {
              $slug = (array_has($this->attributes, 'slug') && $this->attributes['slug']) ? static::stripSpecialCharacters($this->attributes['slug']) : null;
              return $slug;
          }
          return $path;
      }
      
      protected static function boot()
      {
          parent::boot();
      
          static::created(function(Bucket $bucket) {
              $bucket->path = static::stripSpecialCharacters($bucket->slug);
              $bucket->save();
          });
      }
  3. Wed Jul 10

    • Redis, specifically Horizon troubleshooting
    • Filter the specific keys to look for:

      > keys horizon:queue*
      1) "horizon:queues:orders:transfer:reserved"
      2) "horizon:queue:orders:transform"
      3) "horizon:queue:orders:transfer"
      4) "horizon:queues:orders:transfer"
    • Get the type of our keys:

      > type "horizon:queues:orders:transfer:reserved"
      zset
      
      > type "horizon:queue:orders:transform"
      hash
      
      > type "horizon:queue:orders:transfer"
      hash
      
      > type "horizon:queues:orders:transfer"
      list
      
      > type "horizon:job:App\\Jobs\\PendingOrderTransformJob"
      hash
      
      > type "horizon:job:App\\Jobs\\PendingOrderTransferJob"
      hash
    • Look at the values for the specific keys:

      > zrange "horizon:queues:orders:transfer:reserved" 0 2 WITHSCORES
      1) "{\"type\":\"job\",\"timeout\":null,\"tags\":[\"App\\\\Model\\\\PendingOrderStatus:1258\"],\"id\":\"2520\",\"data\":{\"command\":\"O:32:\\\"App\\\\Jobs\\\\PendingOrderTransferJob\\\":8:{s:9:\\\"\\u0000*\\u0000status\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":3:{s:5:\\\"class\\\";s:28:\\\"App\\\\Model\\\\PendingOrderStatus\\\";s:2:\\\"id\\\";i:1258;s:10:\\\"connection\\\";s:5:\\\"mysql\\\";}s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";s:13:\\\"redis:horizon\\\";s:5:\\\"queue\\\";s:15:\\\"orders:transfer\\\";s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";N;s:7:\\\"chained\\\";a:0:{}}\",\"commandName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\"},\"displayName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\",\"timeoutAt\":1562789478,\"pushedAt\":\"1562787678.5943\",\"maxTries\":null,\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"attempts\":3}"
      2) "1562788306"
      
      > hgetall "horizon:queue:orders:transfer"
      1) "throughput"
      2) "52"
      3) "runtime"
      4) "5623.0248076923099"
      
      > lrange "horizon:queues:orders:transfer" 0 99
      ...
      47) "{\"type\":\"job\",\"timeout\":null,\"tags\":[\"App\\\\Model\\\\PendingOrderStatus:1280\"],\"id\":\"2564\",\"data\":{\"command\":\"O:32:\\\"App\\\\Jobs\\\\PendingOrderTransferJob\\\":8:{s:9:\\\"\\u0000*\\u0000status\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":3:{s:5:\\\"class\\\";s:28:\\\"App\\\\Model\\\\PendingOrderStatus\\\";s:2:\\\"id\\\";i:1280;s:10:\\\"connection\\\";s:5:\\\"mysql\\\";}s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";s:13:\\\"redis:horizon\\\";s:5:\\\"queue\\\";s:15:\\\"orders:transfer\\\";s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";N;s:7:\\\"chained\\\";a:0:{}}\",\"commandName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\"},\"displayName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\",\"timeoutAt\":1562789480,\"pushedAt\":\"1562787680.4057\",\"maxTries\":null,\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"attempts\":3}"
      
      > hgetall "horizon:job:App\\Jobs\\PendingOrderTransferJob"
      1) "throughput"
      2) "7"
      3) "runtime"
      4) "7738.0300000000007"
    • Dump our entire key list (the -n option specifies the database):

      > redis-cli -n 3 keys "*" > horizon.keys
      
      horizon:snapshot:job:App\\Jobs\\PendingOrderTransformJob
      horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-order-transform
      horizon:job:App\\Jobs\\PendingOrderTransformJob
      horizon:failed_jobs
      horizon:snapshot:job:App\\Jobs\\PendingOrderTransferJob
      horizon:masters
      horizon:job_id
      horizon:queues:orders:transfer:reserved
      horizon:snapshot:queue:orders:transform
      horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-default
      horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-order-transfer
      horizon:supervisors
      horizon:recent_failed_jobs
      horizon:master:orange-business-rules-swap-f6S1
      horizon:job:App\\Jobs\\PendingOrderTransferJob
      horizon:measured_jobs
      horizon:queue:orders:transform
      horizon:measured_queues
      horizon:recent_jobs
      horizon:queue:orders:transfer
      horizon:last_snapshot_at
      horizon:monitor:time-to-clear
      horizon:snapshot:queue:orders:transfer
      horizon:metrics:snapshot
      horizon:queues:orders:transfer
    • Note: The list of keys was manually filtered by removing anything with a key like horizon:id or horizon:failed*.
  4. Thu Jul 11

    • Do not use Redis::throtle() with the retryUntil() method.

      Redis::throttle($key)->allow(1)->every(60)->then(function () {
          $this->execute();
      }, function () {
          // Could not obtain lock...
          return $this->release(10);
      });
      
      /**
       * Determine the time at which the job should timeout.
       *
       * @return \DateTime
       */
      public function retryUntil()
      {
          return now()->addMinutes(60);
      }
    • The retryUntil method is utilized in getJobExpiration. It seems to be more of a timeout per queue or per job class rather than per job instance like I was expecting.
  5. Fri Jul 12

    • Setting a redis override for our custom systemd configuration

      sudo systemctl edit redis-server
    • The command opens nano to an override file in /etc/systemd/system where we paste the following:

      [Service]
      ReadWriteDirectories=-/mnt/orange-business-rules/var/lib/redis
    • Now our custom directory can be saved to within redis. Without this, every time apt would update redis our service definition would be blown away. * Now we have something that will stand whenever redis is updated in the future.

Elixir Koans

  1. Thu Jul 11

    • Maps

      • Map.fetch(@person, :age) returns {:ok, value}
    • MapSets

      • MapSet.size(new_set) returns an integer for the size (think array length in JavaScript)
      • MapSet.member?(@set, 3) returns a boolean
      • MapSet.new(@set, fn x -> 3 * x end) creates a new set using the function results of 3 * (each value in the set).
      • MapSet.to_list(@set) looks for brackets not braces. I confused a list with a tuple.