iTerm2 Configuration

iTerm2 is my default terminal on macOS.

iTerm2 Preferences
  • Navigate to iTerm2 -> Preferences
  • Uncheck Native full screen windows on the General tab
  • Check Show/hide iTerm2 with a system-wide hotkey
  • Set the Hotkey to Cmd + Esc

In addition, here are some software packages that I highly recommend you check out.

Recommended Software

  • Shuttle to organize SSH connections

Goodreads & Other APIs

I've been messing around with the Goodreads API this weekend. This API returns XML output for their endpoints. It would be nice to get JSON from them instead, many have even asked for this, but any data format is sufficient enough in the end.

For the curious, I am currently reading:

Refactoring To Collections by Adam Wathan

"Refactoring to Collections" is a book and video course that teaches you how to apply functional programming principles to write clean, maintainable PHP.

Learn how to use collection pipelines to break down ugly, complex functions into a series of simple transformations, free of loops, complex conditionals, and temporary variables.

Matter by Iain M Banks

In a world renowned within a galaxy full of wonders, a crime within a war. For one brother it means a desperate flight, and a search for the one - maybe two - people who could clear his name. For his brother it means a life lived under constant threat of treachery and murder. And for their sister, it means returning to a place she'd thought abandoned forever.

Aurora by Kim Stanley Robinson

A major new novel from one of science fiction's most powerful voices, AURORA tells the incredible story of our first voyage beyond the solar system.

Brilliantly imagined and beautifully told, it is the work of a writer at the height of his powers.

Avahi Domain Aliases

Avahi Logo

The Avahi project, helmed by Trent Lloyd, is a Bonjour compatible mDNS/DNS-SD service discovery server for Linux systems. Bonjour is a protocol that Apple uses on their devices (i.e. laptops, phones, Apple TVs, etc) to multicast a DNS record and available services on a local area network (LAN). This avoids a need for a local DNS server to be necessary for simple home networks.

If you have avahi installed on a Linux server, you can pull up a list of available services with the avahi-browse command.

$ avahi-browse -art

By default, an Apple device would use its <hostname>.local address, and would list what services are available for that host. Avahi does this as well out of the box, but we can take it a step further. Using the avahi-aliases pip package, you can broadcast multiple other .local domains for the same machine.

$ apt-get install python-avahi
$ pip install git+git://github.com/airtonix/avahi-aliases.git
$ vim /etc/avahi/aliases.d/default

Just add the other domain names you want to respond to on the network in the /etc/avahi/aliases.d/default file. Finally, stop/start the avahi-aliases python script and you'll be able to ping the new domain names on the network.

$ python /usr/local/bin/avahi-alias stop
$ python /usr/local/bin/avahi-alias start

If you want to see if the script is active use the ps -aux command.

$ ps -aux | grep avahi

If you are having trouble resolving *.local hostnames from an Apple computer, you can always try resetting the DNS cache. You could also simply toggle your WIFI to force a reset.

Set iFrame Focus & Support Microsoft Browsers

Okay, if you ever need to refocus on an iframe window...

var iframe = document.getElementById('myframe');

var content = (iframe.contentWindow || iframe.contentDocument);

content.focus();  

If you want to support Internet Explorer or Edge browsers, make sure that the iframe content is in the same domain name as the parent window. Please note that the above code functions correctly on all other browsers when the src of the iframe is from a different domain name.

Grouping Monthly Totals For Charts

On a personal project, I have recently been working through showing a chart with total monthly views for movies and TV episodes over the last 12 months.

When implementing the process of calculating the total views, I came up with a few options in getting the data from the backend to the chart.

TLDR: See Option 3

Option 1 - Database

First impulse would be to use group by on your database relationship and select a custom set of columns. You can use the count() function get you the necessary total views and the date_format() to generate labels for each month.

/**
 * Get the views for the movie.
 */
public function views()  
{
    $lastYear = Carbon::now()
        ->addMonth()
        ->startOfMonth()
        ->subYear();

    return $this->hasMany(MovieView::class)
        ->where('created_at', '>=', $lastYear)
        ->selectRaw(
            'movie_id, '.
            'COUNT(movie_id) as views, '.
            'DATE_FORMAT(created_at, \'%m/%y\') as label, '.
            'YEAR(created_at) as year, '.
            'MONTH(created_at) as month'
        )
        ->groupBy('year', 'month')
        ->orderBy('year', 'desc')
        ->orderBy('month', 'desc');
}

This method obviously has downsides:

  1. The grouped columns are set as though they are attributes of a MovieView model. This is not an ideal option since your models should always stay clean.

  2. You do not have any representation of past months with zero viewings.

Option 2 - Collections

You could move back to loading the actual activity records from the database. Then you can create an additional method on the model to generate a collection of all past 12 month totals.

I also brought in the League's Period package because it allows for ease in working with timespans, and solves the problem of creating those pesky zero total months.

use League\Period\Period;  
// ...
/**
 * Get the views for the movie.
 */
public function views()  
{
    $lastYear = Carbon::now()
        ->addMonth()
        ->startOfMonth()
        ->subYear();

    return $this->hasMany(MovieView::class)
        ->where('created_at', '>=', $lastYear);
}

/**
 * Get the monthly views for the movie.
 */
public function monthlyViews()  
{
    // Group and map views to a collection of totals
    $views = $this->views
        ->groupBy(function($view) {
            return $view->created_at->format('m/y');
        })
        ->map(function ($views) {
            $view = $views->first();

            return collect([
                'id' => $view->created_at->format('m-y-').
                        $view->movie_uuid,
                'movie_id' => $view->movie_uuid,
                'total' => $views->count(),
                'label' => $view->created_at->format('m/y'),
            ]);
        });

    $months = $this->collectPastTwelveMonths();

    // Merge the views per month into all months
    return $months->merge($views);
}

/**
 * Generate zero totals for the past twelve months.
 */
protected function collectPastTwelveMonths()  
{
    $months = collect();

    // Create a Period of the last 12 months
    $nextMonth = Carbon::now()
        ->addMonth()
        ->startOfMonth();
    $lastYear = $nextMonth->copy()
        ->subYear();
    $period = new Period(
        $lastYear, 
        $nextMonth
    );

    // Create zero count records for all months
    foreach ($period->getDatePeriod('1 MONTH') as $month) {
        $months->put(
            $month->format('m/y'), 
            collect([
                'id' => $month->format('m-y-').$this->uuid,
                'movie_uuid' => $this->uuid,
                'total' => 0,
                'label' => $month->format('m/y'),
            ])
        );
    }

    return $months;
}

This is a potentially a good option to go with because it allows you to generate a new collection of totals without making your database models dirty.

Option 3 - Frontend

First impulse, you could simply send all view records as a resource to your frontend. You would have a client's browser calculate the totals from all of the results using the Array.prototype map/reduce functions. However, then you have a variable amount of data being sent in the response. In the case of a personal media server, this would not be a major issue.

However, to reduce data in the response (i.e. 12 or less resource records), you could go with a variation of Option 2 where only the months with totals are sent from the backend and the totals collection is built on the frontend.

/**
 * Get the monthly views for the movie.
 */
public function monthlyViews()  
{
    // Group and map views to a collection of totals
    return $this->views
        ->groupBy(function($view) {
            return $view->created_at->format('m/y');
        })
        ->map(function ($views) {
            $view = $views->first();

            return collect([
                'id' => $view->created_at->format('m-y-').
                        $view->movie_uuid,
                'movie_id' => $view->movie_uuid,
                'total' => $views->count(),
                'label' => $view->created_at->format('m/y'),
            ]);
        });
}

On the frontend, I use a package called moment and a plugin for it called moment-range to generate the zero total months. I use an iterator function on the range to merge the totals coming from the API. Here is a vuex getter that I've written to generate the necessary array for my chart.

monthlyViews: ({ movies, views }) => {  
  let movie = movies.all
    .find(m => m.id === movies.currentID)
  let movieViews = movie.relationships
    .views.data.map(({ id }) => 
      views.movies.find(m => m.id === id))

  // Create a moment.js range of the past 12 months
  let currentMonth = moment().startOf('month')
  let lastYear = currentMonth.clone().subtract(11, 'M')
  let range = moment.range(lastYear, currentMonth)
  let months = []

  // Create records for past 12 months
  range.by('months', function(month) {
    let label = month.format('MM/YY')
    let view = movieViews.find(
      v => v.attributes.label === label
    )
    months.push({
      id: label,
      total: view ? view.attributes.total : 0,
    })
  })

  return months
}

Avoid Repository Dependency Recursion

Here's a quick tip. If you have embraced the repository pattern for your Laravel application, you may have run into a 502 bad gateway. This can happen if you choose to setup the constructors of one or more repositories to mutually direct inject each other. The result is a loop or recursion of the dependencies between the objects being direct injected.

class CurrentRepository  
{
    protected $others;

    public function __construct(OtherRepository $others)
    {
        $this->others = $others;
    }
}

If you have pulled out your hair with this before, here's the tip. Instead, you can app() or app()->make() the repositories you need access to within a protected function and avoid the injection loop altogether.

// Now you can access the other repository within 
// the current repository with $this->others()
protected function others()  
{
    return app(OtherRepository::class);
}

Please note, if your object dependencies are looping, you may want to restructure your code so that the repositories stop depending on each other altogether.