Journal Entry, October 4th to October 11th 2019

October 11, 2019 · 2 min read

Elixir

  1. Thu Oct 10

    • Compiling elixir language EPUB
    • How to generate EPUB documentation for Elixir v1.6

      • Note: You must remove your phx_new mix archive (mix archive.uninstall phx_new-1.4.10) or conflicts happen during the build process
      git clone https://github.com/phoenixframework/phoenix.git
      cd phoenix
      git checkout v1.4 (or next completed)
      mix deps.get
      mix docs --formatter epub
      git clone https://github.com/elixir-lang/elixir-lang.github.com.git
      git clone https://github.com/elixir-lang/elixir.git
      cd elixir
      
      nano Makefile
      #==> Documentation tasks
      # DOCS_FORMAT = html
      DOCS_FORMAT = epub
      
      make docs
      
      cp doc/elixir/Elixir.epub

Elixir Koans

  1. Tue Oct 8

    • IEx testing a file

      • import_file("lib/knockin/tasks/stack_overflow_query.ex")
      • data = %Knockin.Tasks.StackOverflowQuery{}
      • Knockin.Tasks.StackOverflowQuery.get_base_url
    • Sort order

      • number < atom < reference < function < port < pid < tuple < map < list < bitstring
    • Collections

      • Lists - Simple collection of values
      • Tuples - Lists stored contiguously. Expensive as modifications require copying the entire tuple
      • Keyword lists - a special list of two-element tuples whose first element is an atom; they share performance with lists
      • Maps - the “go-to” key-value store. Unlike keyword lists, they allow keys of any type and are un-ordered.
    • Enum - Set of algorithms for enumerating over enumerables

      • all? - Returns true if supplied callback applies to all values
      • any? - Returns true if supplied callback applies to a single value
      • chunk_every
      • chunk_by
      • map_every
      • each
      • map
      • min
      • max
      • filter
      • reduce
      • sort
      • uniq
      • uniq_by
    • Pattern matching - allows us to match simple values, data structures, and even functions

      • Match operator (=) - Turns into an equation, the left side has to match the right
      • Pin operator (^) - With the match operator, variable assignment happens if included on the left. The pin operator prevents rebinding
      • Any equation or assignment seems to set a pattern or match
    • Control structures

      • if and unless - if looks for truthy, unless is the inverse and defaults to false
      • case - match against multiple patterns. _ required in the event there is no match (like default in other languages but required, unlike others). Allows for guard clauses
      • cond - Match conditions instead of values, akin to else if from other languages
      • with - (Need to look this up)

Laravel Nova

  1. Tue Oct 8

    • You need to run nova:publish as Nova updates via composer. It's easier, if using composer, to run the commands on update.
    • https://twitter.com/freekmurze/status/1169233283269451776 is what happens when this generally breaks but the docs don't point this out in a painfully obvious way.
    • Make a blog post on Chrome overrides and looking at Nova in Vue devtools.

Journal Entry, September 27th to October 4th 2019

October 4, 2019 · 3 min read

Elixir Koans

  1. Wed Oct 2

    • Used asdf over docker image
    • Docker had issues installing the filesystem dependency?
    • Looks like docker works after asdf. My guess is it needed the deps directory as if docker isn't sharing the directory normally.
    • Structs

      • Is %Person{} right for the first answer? I looked at the code, not the text to answer it.
    • Sigils
    • Pattern Matching

      • Very difficult to reason about this example
      assert_raise MatchError, fn ->
          [a, b] = [1, 2, 3]
      end
      koan "The pattern can make assertions about what it expects" do
          assert match?([1, _second, _third], [1, 2, 3])
      end
      • It helps to remember that it's looking for the pattern that gets pulled out, not a left = right scenario
      • I clearly don't understand this part and it's one of the more powerful aspects of the language. I don't even know where to begin to get a deeper understanding of how to use it.
    • Functions

      • Understand the default in this case was 5
      koan "Functions can have default argument values" do
          assert repeat_again("Hello ") == "Hello Hello Hello Hello Hello "
          assert repeat_again("Hello ", 2) == "Hello Hello "
      end
      • Understand the function was building output, look for things like [] or {}
      koan "You can build anonymous functions out of any elixir expression by prefixing it with &" do
          three_times = &[&1, &1, &1]
          assert three_times.("foo") == ["foo", "foo", "foo"]
      end
      • Understand the flow of FP, take the first result 10 then pipe it to the second result, square 100. Then divide by 1
      koan "You can pass functions around as arguments. Place an '&' before the name and state the arity" do
          assert times_five_and_then(2, &square/1) == 100
      end
    • Enums

      • Seems that anything with ? becomes a boolean check or it's just how it's used in all of these examples based on how they define functions
      koan "Elements can have a lot in common" do
          assert Enum.all?([1, 2, 3], &less_than_five?/1) == true
          assert Enum.all?([4, 6, 8], &less_than_five?/1) == false
      end
      • Take the first 3, not take the literal instance of 3
      koan "You three there, follow me!" do
          assert Enum.take([1, 2, 3, 4, 5], 3) == [1, 2, 3]
      end
      • I have a feeling this syntax is important
      koan "Zip-up in pairs!" do
          letters = [:a, :b, :c]
          numbers = [1, 2, 3]
          assert Enum.zip(letters, numbers) == [a: 1, b: 2, c: 3]
      end
      • Unsure why 4 doesn't work here. Does it return the first element?
      koan "When you want to find that one pesky element" do
          assert Enum.find([1, 2, 3, 4], &even?/1) == 2
      end
  2. Thu Oct 3

    • Processes

      koan "You can ask a process to introduce itself" do
          information = Process.info(self())
      
          assert information[:status] == :running
      end
      • It's hard to really understand where this would be useful except to spawn processes or to cleanup/log on exit
    • Tasks
    • Agents

      • It's also hard to understand the use cases for pretty much all the above, processes, tasks, and agents. There seems to be a bit of overlap between them.
    • GenServers

      • This is perhaps one of the more extensive and fun as the response is in the declaration (like most of these) but it's more about hunting and finding the response you expect.
      • One of the best examples of pattern matching, or anything shows as less emphasis in VSCode's syntax highlighting
      koan "Let's use the remaining functions in the external API" do
          Laptop.start_link("EL!73")
          {_, response} = Laptop.unlock("EL!73")
          assert response == "Laptop unlocked!"
      
          Laptop.change_password("EL!73", "Elixir")
          {_, response} = Laptop.unlock("EL!73")
          assert response == "Incorrect password!"
      
          {_, response} = Laptop.owner_name()
          assert response == "Jack Sparrow"
      end
    • Protocols

      • Seem like implementation details and helpful for defining contracts and concrete definitions. It's a little unclear what the preference for using this is.
    • Comprehensions

      • A little harder to understand this is multiple lists not coercing to one long string. I tried "Hello World Apple Pie" at first.
      koan "A generator specifies how to extract values from a collection" do
          collection = [["Hello","World"], ["Apple", "Pie"]]
          assert (for [a, b] <- collection, do: "#{a} #{b}") == ["Hello World", "Apple Pie"]
      end
      • Turns out this one is unlike the one before it where my initial instincts were closer to correct.
      koan "You can use multiple generators at once" do
          assert (for x <- ["little", "big"], y <- ["dogs", "cats"], do: "#{x} #{y}") == ["little dogs", "little cats", "big dogs", "big cats"]
      end

Journal Entry, September 20th to September 27th 2019

September 27, 2019 · 1 min read

Laravel Nova

  1. Fri Sep 27

    • Customize download file name using record information.

      /**
       * Get the actions available for the resource.
       *
       * @param  \Illuminate\Http\Request  $request
       * @return array
       */
      public function actions(Request $request)
      {
          $fileName = $this->getDownloadFileName($request);
          return [
              (new DownloadExcel)->withFilename($fileName)
                                  ->withWriterType(\Maatwebsite\Excel\Excel::CSV)
                                  ->withName('Download CSV')
                                  ->withHeadings()
                                  ->only('code')
                                  ->onlyOnIndex()
                                  ->withoutConfirmation(),
          ];
      }
      
      /**
       * Get the filename for the DownloadExcel action.
       *
       * @param  \Illuminate\Http\Request  $request
       * @return string
       */
      private function getDownloadFileName(Request $request)
      {
          $result = 'codes-' . time() . '.csv';
          try {
              $batch = $request->findModelQuery()->getParent();
              $result = \Str::slug($batch->codeType->name) . "-{$batch->id}-codes.csv";
          } catch (\Exception $exception) {
          }
          return $result;
      }

Journal Entry, August 9th to August 16th 2019

August 16, 2019 · 1 min read

Vue

  1. Mon Aug 12 2019

    • In a Vue component with a data prop titled selectedBucket, if you emit an event named selectedBucketChanged you'll see the event emitted twice. I believe it's because there are life cycle events for any prop that are emitted for reactivity that I just happened to trample on.
    • It would be useful to determine if this is in fact true or an incorrect guess.

Journal Entry, July 26th to August 2nd 2019

August 2, 2019 · 1 min read

Laravel

  1. Tue Jul 30

    • Laravel streaming upload from one filesystem (local/legacy) to another (spaces).

      $resource = \Storage::disk('legacy')->readStream($source);
      $status = \Storage::disk($this->disk)->put($destination, $resource, 'public');
      $sourceSize = \Storage::disk('legacy')->size($source);
      $destinationSize = \Storage::disk($this->disk)->size($destination);
  2. Wed Jul 31

    • It seems the disk cloud doesn't really coerce down to spaces or s3.
    • Required running the command using the disk reference itself: ./artisan convert:material-assets -d spaces --no-progress --reset

Vue

  1. Thu Aug 1

    • Chrome overrides to enable Vue devtools.

      • Open Chrome DevTools (cmd-opt-I)
      • Click Sources tab.
      • Click >> and select Overrides.
      • Click + Select folder for overrides.
      • Navigate to a directory to store your page overrides.
      • Chrome prompts with something like "DevTools requests full access to (directory)..." , click Allow.
      • Now that your overrides folder is setup, click Page under the sources tab to show the files for the page.
      • Navigate to the file you wish to save.
      • Right click and select Save for overrides. This places the file in a site/folder location.
      • Click the {} to pretty print the javascript.
      • Type Ctrl-F to show the find box.
      • Type in devtools and hit enter.
      • You should see an object named H or something unique to each compiled instance. This object could be named absolutely anything.
      • You should see an entry marked something like:
         productionTip: !1, 
         devtools: !1,
         performance: !1,
      • Change the devtools line from !1 to 1 for true.
      • Type Ctrl-S to save the changes.
      • Reload the page and look for the Vue tab. It may take a few reloads for everything to initialize properly.
      • Inspect your production Vue instance like a boss.

Journal Entry, July 12th to July 19th 2019

July 19, 2019 · 1 min read

MySQL

  1. Mon Jul 15 2019

    • Json field queries

      SELECT
          *, `payload`->"$[0].eCommerceOrderId" AS `e_commerce_order_id`
      FROM
          pendingOrderStatus
      WHERE
          `status` = 'sent';
      SELECT
          id, `order`->>'$.e_commerce_order_id' AS `e_commerce_order_id`
      FROM
          order_log_transforms
      WHERE
          `order`->>'$.e_commerce_order_id' IS NOT NULL;
  2. Wed Jul 17 2019

    • Importing from the source table in one database to the destination in another database.

      INSERT INTO `materials-staging`.`files` (`id`, `path`, `type`, `hash`, `material_id`, `active`, `created_at`, `updated_at`, `deleted_at`)
      SELECT
          *
      FROM
          `materials_staging`.`mat_files`;

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.

Journal Entry, June 28th to July 5th 2019

July 5, 2019 · 1 min read

Laravel

  1. Tue Jul 2

    • Actions in Laravel pass in a collection of models. On the index screen this could be hundreds but on the detail screen it is always exactly 1, the parent model.
    • This wasn't quite as easy to grok without attaching a debugger but this makes complete sense now.

      /**
       * Perform the action on the given models.
       *
       * @param  \Laravel\Nova\Fields\ActionFields  $fields
       * @param  \Illuminate\Support\Collection  $models
       * @return mixed
       */
      public function handle(ActionFields $fields, Collection $models)
      {
          $count = 1;
          foreach ($models as $model) {
              $count = $this->generateCodes($model->id, $model->code_type_id, $fields->number);
          }
          return Action::message("$count codes were successfully created.");
      }

Phoenix Framework

  1. Sat Jun 29

    • Initial commit
    • Changes to .gitignore for VSCode integration and to remove /config/*.secret.exs. It's unclear if this was present in the earlier version of Phoenix and I weirdly need to add it. It's also possible that I just followed along with the up and running guide.
    • Added editorconfig from .editorconfig for Elixir projects · GitHub

Journal Entry, June 21st to June 28th 2019

June 28, 2019 · 1 min read

Laravel

  1. Wed June 26

    • How Laravel prevents your scheduled jobs from overlapping - Diving Laravel

      • The obr-sage-sync scheduler was broken in production due to network outages affecting Digital Ocean droplets. The sync:customers command would not execute via the scheduler because withoutOverlap() detected the command was still executing.
      • In this instance multiple copies were hung so I rebooted the machine and took a bit hunting this down. As a companion, whenever you kill any command as part of the scheduler, always clear the cache. It's a simple command that'll almost always save time trying to uncover the problem again.

Phoenix Framework

  1. Fri Jun 28

    • Started over with Phoenix 1.4.8 using asdf global packages for the following:

      elixir 1.9.0 (set by /Users/jbrayton/.tool-versions)
      erlang 22.0.4 (set by /Users/jbrayton/.tool-versions)
      nodejs 12.5.0 (set by /Users/jbrayton/.tool-versions)
    • Followed Using ASDF with Elixir and Phoenix but ran into some issues with the homebrew install method.
    • Essentially, I changed the shim for node to:

      #!/usr/bin/env bash
      # asdf-plugin: nodejs 10.15.3
      # asdf-plugin: nodejs 8.11.4
      # asdf-plugin: nodejs 12.5.0
      exec $(brew --prefix asdf)/bin/asdf exec "npm" "$@"
    • The path is really /usr/local/opt/asdf/bin/asdf and seems to work for the other packages. It could be that I was trying to do a local install before a global.

Journal Entry, June 7th to June 14th 2019

June 14, 2019 · 1 min read

Laravel

  1. Mon Jun 10

    • Returning files sorted by creation time.

      $files = collect(\File::files($realDirectory))
          ->filter(function(\SplFileInfo $file) {
              return strpos($file->getFileName(), "{$this->fileName}");
          })->sortBy(function (\SplFileInfo $file) {
              return $file->getCTime();
          }, SORT_NUMERIC)->map(function (\SplFileInfo $file) {
              return [
                  'base' => $file->getBasename(),
                  'name' => $file->getFilename(),
                  'created' => $file->getCTime(),
                  'modified' => $file->getMTime(),
                  'size' => $file->getSize(),
              ];
          });
  2. Fri June 14