window.openerがnullになってしまう

背景と課題

  • 元ウィンドウから、target="_blank" 属性を利用して新ウィンドウを開く
  • 新ウィンドウの window.opener.location.reload(true); という処理で元ウィンドウをリロードする

という処理がある日動かなくなり、次のようなエラーがConsoleに出ていた。

Uncaught TypeError: Cannot read properties of null (reading 'location')

解決策

元ウィンドウのリンクに、rel="opener" 属性を追加する。ただし、リンク先は自身が管理しているページだけにすること。

どうやらブラウザのセキュリティ仕様のせいで、仕様が変わったみたい。

<a href="/foobar.html" target="_blank" rel="opener" >新たにウィンドウを開く</a>

Laravelのキューで利用するインターフェイスやトレイトについて

Laravelで非同期Jobのサンプルを見ると、色々と登場人物が出てきてよくわからないので整理する。 サンプルで出てくるコードはLaravel7あたりです。

<?php

namespace App\Jobs;

use App\AudioProcessor;
use App\Podcast;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $podcast;

    /**
     * 新しいジョブインスタンスの生成
     *
     * @param  Podcast  $podcast
     * @return void
     */
    public function __construct(Podcast $podcast)
    {
        $this->podcast = $podcast;
    }

    /**
     * ジョブの実行
     *
     * @param  AudioProcessor  $processor
     * @return void
     */
    public function handle(AudioProcessor $processor)
    {
        // アップロード済みポッドキャストの処理…
    }
}

ShouldQueueインターフェイス=非同期実行

インターフェイスとしては何も定義していないが、非同期実行のために必要。

// src/Illuminate/Bus/Dispatcher.php
    public function dispatch($command)
    {
        return $this->queueResolver && $this->commandShouldBeQueued($command)
                        ? $this->dispatchToQueue($command)
                        : $this->dispatchNow($command);
    }

Dispatchable トレイト

Dispatchable トレイトを使用することで、ジョブクラスから直接ジョブをディスパッチ(送信)する便利なメソッドが提供されます。具体的には、ジョブクラスに Dispatchable トレイトを使用すると、そのジョブクラスのインスタンスを作成せずに dispatch メソッドを直接呼び出すことができます。

dispatch(new ExampleJob());

こうではなく。

ExampleJob::dispatch();

こう書ける。

Chat-GPTより。

実装を見ると、dispatch(), dispatchIf(), dispatchUnless(), broadcast() があるが、dispatch() しか知らない。

InteractsWithQueue

キューにプッシュされるジョブがキューとやりとりするためのメソッドを提供します。具体的には、このトレイトはジョブが自分自身をキューから削除したり、リリースしたり、タイムアウトを設定したりするための機能を持っています。

Chat-GPTより。

Queueable

ジョブやリスナーにキュー関連の操作やプロパティを追加することができます。

Chat-GPTより。

キューの指定(onQueue())だったり、実行の遅延(delay)だったりの機能提供。

SerializesModels

キューにプッシュされる際に、Eloquentモデルを適切にシリアライズするため。

その他

WithoutOverlapping:

同じジョブが同時に複数回実行されるのを防ぐためのトレイトです。例えば、特定のジョブが長時間かかる場合や、次の実行が前の実行が完了する前に開始される可能性がある場合に使用します。
WithTimestamps (Laravel 8.x 以降):

ジョブがキューに追加されたタイムスタンプと、ジョブが最後に実行されたタイムスタンプを追跡します。
ShouldBeUnique (Laravel 8.x 以降):

ジョブが一度に1つだけキューにあることを保証します。これは、データベースの整合性を保つためや、同じ操作が複数回実行されるのを防ぐために役立ちます。

Chat-GPTなんでも答えてくれるな。すごい。

LaravelのCollectionで複数のキーをもとに重複を排除する

公式でも記載されていますが、分かりづらいのでメモ。 下記の例では、brand + type の組み合わせに対して最初のアイテムだけを含む新しいコレクションを作成します。

$collection = collect([
    ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
    ['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'],
    ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
    ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
    ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]);

$unique = $collection->unique(function ($item) {
    return $item['brand'].$item['type'];
});

$unique->values()->all();

/*
[
    ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
    ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
    ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
    ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]
*/

Railsのレールに乗ることの重要性

Railsのレールに乗ることの重要性を再認識し、新しいアプリではMySQLを採用することに決めました。以前作成したレガシーのRailsアプリを再構築するためにMongoDBをデータベースとして採用しようとしましたが、以下の理由により、MySQLを選択することにしました。

  • MongoDBではActiveRecordEnumを使うことができなかった。代わりに enumerize を利用する必要がある。
  • MongoDBでRansackの利用ができなかった。Ransackを利用するためのGem(ransack-mongoid)も今年でアーカイブされていていた。
  • MongoDB&Railsに関する情報は全体的に古く、不足しているように感じました。

MongoDB自体は、今回作ろうと思っているプロダクトの相性は悪くないと思ったけど、Railsのレールに乗ることが重要であることを改めて認識しました。

aws-sdk-railsでSQSをActive Jobのアダプターとして使っていたら、DBのPoolを消費しまくっていた件

aws-sdk-railsでSQSをActive Jobのアダプターとして使っていたら、

ActiveRecord::ConnectionTimeoutError (could not obtain a connection from the pool within 5.000 seconds (waited 5.000 seconds); all pooled connections were in use):

というように、DBのプールを使い切ってしまった。

これを治すには、 bundle exec aws_sqs_active_job --queue default --threads=2 のように --threads を指定する必要がある。

原因は、この aws_sqs_active_jobデフォルト設定 にある。

{
  threads: 2*Concurrent.processor_count,
  max_messages: 10,
  shutdown_timeout: 15,
  backpressure: 10
}

CPUプロセッサ数の2倍が設定されているため、12コア24スレッドのRyzen 9 5900Xを利用していた私は、24並列でプログラムを動かし、DBのプールを食いつぶしていたという訳です。

目標はSMARTGoalに設定する

プロジェクトで目標を設定する際、次の頭文字をとってSMARTなGoalにすることを心がける。

  • Specific(具体的な)
  • Measurable(測定可能な)
  • Achievable(達成可能な)
  • Result oriented(結果志向な)
  • Time bound(時間内にできる)