OmniAICreator/Qiita-1.07M huggingface dataset:
【Rails＆OpenAI API】「オススメの～」を教えてくれるChatGPT風アプリを作ってみた。

# はじめに
流行りのChatGPTを使って何かそれっぽいアプリを作ってみたいと思ったので、APIであるOpenAI APIを使って作ってみたという感じです。

いつもの通り脳筋ですので、もっとこうした方が良い等あれば遠慮なくご意見ください！
railsは6.1.7
rubyは3.0.~台を利用しています

# 手順
1.APIの登録
2.controllerやviewの記述

## 1-1.APIの発行
正直が私がAPIの登録方法を説明するよりも詳しい方がたくさんいらっしゃるので、詳しく説明してくださっている記事を紹介します↓↓

https://programming-zero.net/openai-api-key/

:::note warn
紹介させていただいた記事はAPIの取得の手順のみです。
利用料金のシステム等、少しややこしい＆記事執筆時と料金体系が変化している可能性があるので、この記事だけでなくご自身で詳しく調べた上でAPIキーを取得してください。
:::

## 1-2.railsにAPIの登録
APIを使えるようにするために`gem "ruby-openai"`
APIの外部流出を防ぐために`gem "dotenv-rails"`を使用します。

```ruby:gemfile
gem "dotenv-rails"
gem "ruby-openai"
```
`bundle install`をお忘れなく

appディレクトリ直下に`.env`ファイルを作成し、以下の記述をしてください
```ruby:.env
OPENAI_API_KEY=ご自身のAPIキー
```
`.gitignore`には以下を追記
```:.gitignore
.env
```
## 2.controllerやviewの記述

::: note info
chatsコントローラーのindex.html.erbにてやり取りする想定です。
:::

`rails g controller chats`で作成しておいてください

```ruby:routes.rb
Rails.application.routes.draw do
 resources :chats
 root 'chats#index'
end
```

```ruby:chats_controller.rb

class ChatsController < ApplicationController
  def index
    if params[:question].present?
        client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
        response = client.chat(
          parameters: {
              model: "gpt-3.5-turbo",
              messages: [{ role: "user", content: params[:question] }],
          })
    
        @answer = response.dig("choices", 0, "message", "content")
    end
  end
end
```

```erb:views/chats/index.html.erb
<div class="question">
  <%= form_with(url: chats_path, method: :get) do |f| %>
    <%= f.text_field :question, size: 50 %>
    <%= f.submit "質問する" %>
  <% end %>
</div>

<div class="answer">
  <%= @answer %>
</div>
```
記述に関しては以上です。

## 3.「オススメの～」というような質問に答えて欲しい時

コントローラー内に記述した
` messages: [{ role: "user", content: params[:question] }]`の`content`の中身がミソです。


`params[:question]`はフォームに記入された内容が入るので、キーワードに合ったオススメの映画をAIに答えて欲しいのであれば

```ruby:
messages: [{ role: "user", content: "次のキーワードに該当する映画を５つ紹介してください。#{params[:question]}" }]
```
というようにすれば返信してくれるという訳です！

::: note info
ChatGPT同様に英語で質問した方が返答の精度はもちろん高くなるので、色々試してみてください！
:::

**Lets'！プロンプトエンジニアリング！**

# お世話になった記事
https://github.com/alexrudall/ruby-openai

https://programming-zero.net/openai-api-key/

https://zenn.dev/kingdom0927/articles/7b2a1814bafe87

ありがとうございました。
SwiftでPaizaに挑戦する（備忘録）

## 標準入力の受け取り方(C級)
以下のように1行目に整数を空白区切りで2つ
2行目に整数を空白区切りで3つ受け取る場合

N M
A B C
```
let input = readLine()!.split(separator: " ").map { Int($0)! }
let n = input[0]
let m = input[1]

let costs = readLine()!.split(separator: " ").map { Int($0)! }
let a = costs[0]
let b = costs[1]
let c = costs[2]
```

※問題を解いていき次第追加していきます。
コマンドプロンプトからMicrosoft Edgeを立ち上げるには

コマンドプロンプトからMicrosoft Edgeを立ち上げるには、以下の手順をご参考ください。

**1. コマンドプロンプトを開く**

Windowsキーを押し、cmdと入力し、Enterキーを押してコマンドプロンプトを起動します。

**2. Microsoft Edgeの実行ファイルへのパスを指定して、Edgeを立ち上げます。**
通常、Edgeの実行ファイルは以下のディレクトリにあります。

```
C:\Program Files (x86)\Microsoft\Edge\Application\
```

うまく実行できない場合は、以下のようにしてディレクトリを変更してから実行してみてください。
```
##cdコマンド + 対象のパス→ディレクトリを移動できます##

C:>cd C:\Program Files (x86)\Microsoft\Edge\Application\
```

**3. コマンドプロンプトで、次のコマンドを入力し、Enterキーを押します。**
```
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
```
これで、Microsoft Edgeが立ち上がります。

**4. 特定のURLを開く場合**

以下のようにコマンドを入力してください。
※この例では、https://www.example.com というURLを開きます。必要に応じてURLを変更してください。
```
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" https://www.example.com
```

以上です。余裕があればchromeにも挑戦します。
diffusersでスケジューラを読み込んで画像生成する方法

# はじめに

これは[huggingface/diffusers](https://github.com/huggingface/diffusers)モジュールを利用して画像生成する際に、スケジューラ(Euler等)をを読み込む方法です。

Diffusersではすでに何種類か用意されているので、それを使ってパイプラインを生成する

今回はそれの備忘録

# コード

```python
import datetime
import torch
from diffusers import (
    DiffusionPipeline,
    DDIMScheduler,
    DDPMScheduler,
    DEISMultistepScheduler,
    DPMSolverMultistepScheduler,
    DPMSolverSinglestepScheduler,
    EulerAncestralDiscreteScheduler,
    EulerDiscreteScheduler,
    HeunDiscreteScheduler,
    KDPM2AncestralDiscreteScheduler,
    KDPM2DiscreteScheduler,
    UniPCMultistepScheduler,
)

MODEL_ID=''
SCHEDULER = 'EulerAncestralDiscrete'
CACHE_DIR = './cache'
DEVICE = 'cuda'

SCHEDULERS_LIST = {
    'DDIM' : DDIMScheduler,
    'DDPM' : DDPMScheduler,
    'DEISMultistep' : DEISMultistepScheduler,
    'DPMSolverMultistep' : DPMSolverMultistepScheduler,
    'DPMSolverSinglestep' : DPMSolverSinglestepScheduler,
    'EulerAncestralDiscrete' : EulerAncestralDiscreteScheduler,
    'EulerDiscrete' : EulerDiscreteScheduler,
    'HeunDiscrete' : HeunDiscreteScheduler,
    'KDPM2AncestralDiscrete' : KDPM2AncestralDiscreteScheduler,
    'KDPM2Discrete' : KDPM2DiscreteScheduler,
    'UniPCMultistep' : UniPCMultistepScheduler
}

# Create Pipeline
pipe = DiffusionPipeline.from_pretrained(
    pretrained_model_name_or_path=MODEL_ID,
    torch_dtype=torch.float16,
    cache_dir=CACHE_DIR,
)

# Add Scheduler
pipe.scheduler = SCHEDULERS_LIST[SCHEDULER].from_pretrained(
    pretrained_model_name_or_path=MODEL_ID,
    torch_dtype=torch.float16,
    cache_dir=CACHE_DIR,
    subfolder='scheduler'
)

pipe = pipe.to(DEVICE)

# Generate Image
image = pipe(
    prompt='',
    height=000,
    width=000,
    num_inference_steps=000,
    guidance_scale=000,
    negative_prompt='',
).images[0]

# Save Image
image.save("images/" + str(datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')) + ".png")
```

上記の下記がスケジューラ読み込み部分です

```python
pipe.scheduler = SCHEDULERS_LIST[SCHEDULER].from_pretrained(
    pretrained_model_name_or_path=MODEL_ID,
    torch_dtype=torch.float16,
    cache_dir=CACHE_DIR,
    subfolder='scheduler'
)
```

`SCHEDULERS_LIST[SCHEDULER]` の部分がクラス名の選択になります。
Azureにおける、東日本リージョンと西日本リージョンの違い

**最初に**
Azureにおいて、東日本リージョン（Japan East）と西日本リージョン（Japan West）は、物理的に異なる場所にあるデータセンター群であり、それぞれ独立したリージョンとして提供されています。以下に、それぞれのリージョンの違いを簡潔に説明します。

<hr>

**地理的位置**
 東日本リージョンは、東京都を中心としたエリアに位置しています。一方、西日本リージョンは、大阪府を中心としたエリアに位置しています。

<hr>

**レイテンシー（遅延）**
顧客の物理的な位置やその他のインフラストラクチャによって、東日本リージョンと西日本リージョンの間でネットワークレイテンシーが異なる場合があります。通常、顧客は、物理的に近いリージョンを選択することで、より低いネットワークレイテンシーを得られます。

<hr>

**サービスの可用性**
Azureのすべてのサービスが両リージョンで利用可能とは限らず、サービスの提供状況はリージョンによって異なる場合があります。利用したいサービスが指定のリージョンで利用可能かどうかを確認することが重要です。

とくにリージョン別の利用可能な製品は異なるため、以下のサイトから随時確認ください。

「リージョン別の利用可能な製品」
https://azure.microsoft.com/ja-jp/explore/global-infrastructure/products-by-region/?products=all

<hr>

**冗長性とディザスタリカバリ**
東日本リージョンと西日本リージョンは、地理的に離れた場所にあるため、災害や障害が発生した場合に、データとアプリケーションを別のリージョンにバックアップし、冗長性とディザスタリカバリを実現することができます。

個人的な見解ですが、とくにこだわりがなければ東日本リージョンを選べばいいと思います。
というのも、可用性ゾーンが3つあること、加えて「DISASTER RECOVERY」において東日本リージョンには「Azure Site Recovery を使用したゾーン DR」があるためです。

<hr>

※　注意点ですがリージョンに関する情報は日々更新されると考え、最新情報は下記のサイトなどから取得するようお願いします。

「Azure の地域」
https://azure.microsoft.com/ja-jp/explore/global-infrastructure/geographies/#overview
【Shell】行数を指定して内容を取得する方法

## 1行目のみ取得する

ファイルの1行目のみ取得するには以下のように実行します。

```zsh
head -n 1 対象ファイルパス
```

## 指定した行のみ取得する

指定した行のみ取得するには以下のようにします。

```zsh
sed -n 行数p 対象ファイルパス
```

`./sample.txt`の5行目を取得する場合には以下のように実行します。

```zsh
sed -n 5p ./sample.txt
```
laravel ファサードで使えるメソッド調べる方法

# 概要

- laravelのファサードでどんなメソッドが使えるのかを調べる方法をまとめる。

# 方法

- 下記にアクセスする。
    - [https://laravel.com/api/10.x/index.html](https://laravel.com/api/10.x/index.html)
- 調べたいlaravelバージョンを選択する。

    ![Namespaces___Laravel_API.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/00ee1390-bf85-b955-99c0-0f2453ce40ae.png)

- 「Search」のところに下記を入力して検索する。
    - `Illuminate\Support\Facades\調べたいファサード`
- 例えばCacheファサードで使えるメソッドを調べたいなら下記の様に検索する。
    - `Illuminate\Support\Facades\Cache`
    - 検索するとサジェストで「Illuminate\Support\Facades\Cache」が出るのでクリックして検索する。
    - ヒットしたページを見るとそのファサードで使えるメソッドを見ることができる。

        ![Search___Laravel_API.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/de769f59-9633-7d3b-4f19-a34974a65ba4.png)

        ![Illuminate_Support_Facades_Cache___Laravel_API.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/9a0dfeb6-186d-fcf2-5ec6-40266018d390.png)

- 日本語ドキュメントだと食わし書いてなかったりvendor直下見に行くとファサードの実装ってぱっとどこにあるか分からなかったりするのでこちらの方法がおすすめ！（完全所感！）

# 追加

- すみません。わざわざ検索しなくても、日本語ドキュメントから各ファサードの説明ページまでのリンクが貼ってありましたｗ

https://readouble.com/laravel/10.x/ja/facades.html#facade-class-reference![ファサード_10_x_Laravel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/af698051-21d5-1b9b-f7fc-c68ccc873828.png)


# 付録

- ファサードそもそもの仕組みについては下記の「ファサードの仕組み」がわかりやすかった。

https://readouble.com/laravel/10.x/ja/facades.html#how-facades-work
GoogleFontsを利用したフォントの変更

# [GoogleFonts](https://fonts.google.com/)を利用したfontの変更

- [GoogleFonts](https://fonts.google.com/)の検索バーより使用したいFontを検索する。
- 使用したいFontを候補の中から選んでクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962146/008f47ff-c01e-642b-c8b2-9a4c07cda530.png)

- `Remove Regular`をクリック。(出てこなければ右上の赤ポチが出ているマークをクリック)
- 左に出てきた`import`をクリック。




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962146/a700d581-4987-83ee-31d8-61ba3bf9c3f3.png)

- styleタグで囲まれている箇所をコピーし、CSSに張り付ける。(これでGoogleFontが使えるようになる)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962146/d20d1be3-8c94-812e-7dce-80c3d1b3a201.png)


- CSS rules to specify familiesのコードを変更したいタグに紐づけする。

~~~diff_CSS
header h1{
    margin: 0px;
    font-size: 24px;
    font-weight: normal;
    text-align: center;
    font-family: serif;
}
~~~
yieldについて

## リストを展開する時に、yieldという概念があることを知りまとめる。

- リストにして出力

```python
# sample.csvを読み込む処理
def fetch_line(filename:str):
  with open(filename,"r") as file:
    lines: list[str] = []
    for line in file:
      lines.append(line)
    return lines

result = fetch_line("sample.csv")
print(result)
```

実行すると、sample.csvに書かれている内容のリストが表示される

```text:実行結果
['実行!\n', 'いけた！\n', '3行目!\n']
```

- 以下はyieldを使用

```python
def fetch_line(filename:str):
  with open(filename,"r") as file:
    lines: list[str] = []
    for line in file:
      yield(line)

result = fetch_line("sample.csv")
print(result)
print(result.__next__())
print(result.__next__())
```

```text:実行結果
<generator object fetch_line at 0x10e571c80>
実行!

いけた!

```

```py:全て表示
for i in result:
  print(i)
```

```text:実行結果
実行!

いけた！

3行目!

```


## コードの説明
```python
for line in file:
   yield(line)
```
ここが、yieldのところ。
listにappendしなくても全てgeneratorというオブジェクトで作成してくれる。
`<generator object fetch_line at 0x10e571c80>`のようなオブジェクトとなり
1つづつ表示させるには.__next__()を使ってあげる(python3系)
全て表示させるには、forなどを使ってあげる。

## 利点など
1GBなんかの大きい重たい容量のデータを扱うときに使用する。
また、returnは1GB全て良い込んで返すのに対し、
yieldは読み込んでいる最中でもデータの参照が可能になる。

## その他
returnは処理を終了し値を返す
yieldは処理を一旦停止し値を返す

要素が要求される場面でその都度データを算出する（yield）ため、メモリ消費を抑える。

現状を保持できる関数でもあり、
直前に算出した値を次に算出する演算で使用可能
<通常の関数はまっさらな状態で処理が開始される>

何度も同じ関数を実行し、リストに追加するような処理ならyieldの方が良い。

あとは、タイムアウトする重たい処理の実行時に、使うこともある？？？
この辺わからんな〜〜〜

参考：
http://ailaby.com/yield/

https://www.lifewithpython.com/2018/05/python-yield.html
コミットのまとめ方について（備忘録）

普段開発をしていて「少しコミット細かすぎるからまとめておいてね」と言ったり言われたりするケースが多いので、
rebaseとスカッシュマージを使用したコミットのまとめ方について書き記しておく。

## 結論
- rebaseもスカッシュマージ（squash merge）も複数のコミットを1つにまとめることができるコマンドの1種
- rebaseは複数のコミットをまとめることができる
- スカッシュマージは作業ブランチと現在のブランチの差分をまとめて一つのコミットとして現在のブランチにマージする

## rebase

- rebaseを使用することで複数のコミットをまとめて新しいコミットを作成することができる
- 直近の連続したコミットであればまとめることができるが、rebaseしたいコミットの中に他の人のコミットがあった場合、rebaseしてしまうと自分がコミットしたことになってしまうので注意
- 基本的には自分の作業ブランチで細かく分けすぎたコミットを統合する目的で使用する

## rebaseでコミットをまとめる方法

### 1. git logでまとめたいコミットを確認
  - 今回以下の3つを1つにまとめたいとする

```bash
pick 12345 サンプルコミットC
pick 67890 サンプルコミットB
pick abcde サンプルコミットA
```

### 2. `git rebase -i HEAD~[まとめたいコミットの数]`　コマンドを実行する
- 今回の場合であれば、`git rebase -i HEAD~3`　となる
- vscodeでやっていれば下記のような画面が自動で開く
    
   ![スクリーンショット 2023-04-06 22.17.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528450/f02babfd-c3d9-53f6-ef94-f358962f6489.png)

    
- もしターミナルが開かれた場合、デフォルトエディタがターミナルになっているので[こちら](https://qiita.com/laughingman/items/3cce0effa0c9df8c00ad)の記事を参考にvscodeに設定する

### 3. 各コミットメッセージの左側のプルダウンからrebaseでどのような処理を行うか選択する
- 基本的にはコミットをまとめたい時に使用すると思うので、squashかfixupのどちらかを選ぶと思われる
    - コミットをまとめることで作成された新しいコミットのコミットメッセージを変更させたいときはsquashを選択する
        - 基本squashで良いと思われる
    - 一番下にあるコミットはsquashやfixupができない
        - ベースとなるコミットでそのコミットに対しfixupやsquashされたコミットが統合されるから
    
    | コマンド | 説明 |
    | --- | --- |
    | pick | 何もしない。統合元となる |
    | reword | コミットメッセージをまとめて変更する |
    | edit | 過去のコミットを修正する（該当箇所でrebaseを一時停止する） |
    | squash | 過去のコミットを統合する（コミットメッセージを変更する場合） |
    | fixup | 過去のコミットを統合する（コミットメッセージの変更が不要な場合） |
    | drop | 過去のコミットを削除する |
    
    ![スクリーンショット 2023-04-06 22.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528450/2f17ee02-92ee-32e3-297c-d967dd271cff.png)
    
- 「START REBASE」を押すと、コミットメッセージを書き換える画面が表示される
    ![スクリーンショット 2023-04-06 22.29.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528450/5ea251c4-36f6-86a7-74c6-157504ee834f.png)
    
### 4. コミットメッセージを好きなように変更する
- 変更後、ファイルを保存し×ボタンで閉じると、ターミナルが動きrebaseが完了する
    ![スクリーンショット 2023-04-06 22.29.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528450/191cd667-90b4-b6e4-8a52-0a6a81c76107.png)

## スカッシュマージ（squash merge）

- マージと名前についている通り、作業ブランチと現在のブランチの差分があった場合に作業ブランチから現在のブランチにまとめてコミットする手法
- こちらもコミット履歴が書き変わってしまうので、直近の変更が自分の連続したコミットである場合に使用するのが無難

### スカッシュマージでコミットをまとめる方法

1. 作業ブランチでまとめたいコミットをgit logで確認
1. 作業ブランチからマージしたいブランチへ移動
1. `git merge —squash [ブランチ名]`　を実行
1. 現在のブランチに変更したファイルが移動してくるので、それをコミットしてpush
1. 作業ブランチは不要であれば削除、今後も使用するのであれば移動してマージをしておく
      - コンフリクトしないようにするため

## まとめ
普段コミットをまとめる際にはrebaseしか使ったことがありませんでしたが、
今回両方使ってみて単純に作業ブランチでコミットをまとめるのであればrebaseが使いやすいなと感じました。

rebaseだとvscodeであればコマンドを実行した際、ファイルが自動で開いたり、squashやfixupのコマンドもプルダウンから選択するだけでわかりやすく、コミットメッセージの書き換えも簡単に行うことができました。



### 参考資料

- [https://tech.nkhn37.net/git-rebase-vscode-basic/](https://tech.nkhn37.net/git-rebase-vscode-basic/)
- [https://qiita.com/laughingman/items/3cce0effa0c9df8c00ad](https://qiita.com/laughingman/items/3cce0effa0c9df8c00ad)
代表的なMRAであるDovecotの説明と設定例

●代表的なMRAであるDovecotの説明と設定例

■Dovecot（ダブコット）
POP3/IMAPをサポートする、多くのディストリビューションで導入されているMRA。Maildir及びmbox形式のメールボックス、SSL/TLS対応のPOP3SおよびIMAPSもサポート。

Dovecot2.xの設定ファイルは、デフォルトでは「/etc/dovecot/dovecot.conf」＋「/etc/dovecot/conf.d/*.conf」。

●Dovecotの主な設定ファイル
「/etc/dovecot/dovecot.conf」：メインの設定
「/etc/dovecot/conf.d/10-auth.conf」：ユーザ認証の設定
「/etc/dovecot/conf.d/10-logging.conf」：ログ関連の設定
「/etc/dovecot/conf.d/10-mail.conf」：メールの配送関連の設定
「/etc/dovecot/conf.d/10-master.conf」：基本動作の設定
「/etc/dovecot/conf.d/10-ssl.conf」：SSL/TLS関連の設定
「/etc/dovecot/conf.d/20-imap.conf」：IMAP関連の設定
「/etc/dovecot/conf.d/20-pop3.conf」：POP3関連の設定

●SSL/TLSを使用する際の基本的な設定項目

・ssl　「yes」でSSL/TLS有効化
・ssl_cert　「<サーバ証明書のパス」でサーバ証明書を指定
・ssl_key　「<サーバ秘密鍵のパス」でサーバの秘密鍵を指定
laravel バリデーション integerは数値の1でも文字列の1でも通過する

# 概要

- integerバリデーションは指定されたキーの値が「整数なのか否か」をチェックしている。なので数値の1でも文字列の1でもintegerバリデーションは通過する。
- どうして整数なら何でも通すのかを確認してみる。

# 内容

- そもそもintegerバリデーションルールのチェックは下記で判定されている。
    - `vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php`の`validateInteger()`メソッド
- 当該メソッドの内容を下記に抜粋する。

    ```ValidatesAttributes.php
    /**
     * Validate that an attribute is an integer.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function validateInteger($attribute, $value)
    {
        return filter_var($value, FILTER_VALIDATE_INT) !== false;
    }
    ```

- どうやらPHPの組み込み関数であるfilter_var関数で値のチェックしているっぽい。
    - [filter_var](https://www.php.net/manual/ja/function.filter-var.php)
- filter_var関数の第2引数に渡しているフィルターの型で「FILTER_VALIDATE_INT」を指定している。当該フィルターの型は引数が整数の場合その値を、それ以外はfalseを返すらしい。
    - [検証フィルター](https://www.php.net/manual/ja/filter.filters.validate.php)

        ![PHP__検証フィルタ_-_Manual.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/9e352979-845c-0873-b4e5-51a5159262e9.png)

- なので、そもそもチェックするメソッドそのものが数値の1でも、文字列の1でも通過するようにチェックしてるっぽい。
- 実際にfilter_var関数を使って遊んで見る。

    ```php
    <?php
    // Your code here!

    $int = 2;
    $str = '2';
    $strFoo = 'aa';

    var_dump(filter_var($int, FILTER_VALIDATE_INT)); // int(2)
    var_dump(filter_var($str, FILTER_VALIDATE_INT)); // int(2)
    var_dump(filter_var($strFoo, FILTER_VALIDATE_INT)); // bool(false)
    ```
Leetcode 1254. Number of Closed Islands

https://leetcode.com/problems/number-of-closed-islands/

# アプローチ
- BFS

# コード
```java
class Solution {
    class Point {
        int x;
        int y;

        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    public int closedIsland(int[][] grid) {
        boolean[][] isVisited = new boolean[grid.length][grid[0].length];
        int[] dx = {1, 0, -1, 0};
        int[] dy = {0, 1, 0, -1};
        int count = 0;

        Queue<Point> queue = new LinkedList<>();

        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 0) {
                    boolean isOut = false;
                    if (!isVisited[i][j]) {
                        count++;
                        isVisited[i][j] = true;
                        queue.add(new Point(i, j));

                        while (!queue.isEmpty()) {
                            Point now = queue.poll();

                            for (int x = 0; x < 4; x++) {
                                int nextX = now.x + dx[x];
                                int nextY = now.y + dy[x];

                                if (nextX < 0 || grid.length <= nextX || nextY < 0 || grid[0].length <= nextY) {
                                    isOut = true;
                                    continue;
                                }
                                if (grid[nextX][nextY] == 1 || isVisited[nextX][nextY]) {
                                    continue;
                                }
                                queue.add(new Point(nextX, nextY));
                                isVisited[nextX][nextY] = true;
                            }

                        }
                        if (isOut) {
                            count--;
                        }
                    }

                }
            }
        }
        return count;
    }
}
```
【Django】loaddataコマンドを使用してfixturesデータをデータベースにロード（初期データを投入）する方法

# 概要
* Djangoでは、`python manage.py loaddata`コマンドを使用して`fixtures`データをデータベースにロードできます。
* これは初期データ投入時に使えます。今回は実際にやってみたので、本記事ではその方法を解説します。

## 実行手順

* `fixtures`ディレクトリ内のJSONファイルを作成します。
    * JSONであれば、ファイル名は必ず.json拡張子で終わる必要がありますが、JSONのほか、XMLやYAML形式でも書くことができます。
    * フィクスチャ (`fixture`) とは、 テストや開発のためのサンプルデータを含むファイルです。Django のデータベースに投入できるよう準備済みのデータの集まりのことです。Djangoでは、フィクスチャを使用してアプリケーションの初期状態を構築することができます。アプリケーションの開発やテストに欠かせない便利なツールです。

* 今回は、事前にマイグレーションはしてある前提とします。
* `python manage.py loaddata`コマンドを使用して、`fixtures`データをデータベースにロードします。例えば、以下のように実行します。

```terminal
$ python manage.py loaddata sample_data.json
Installed 10 object(s) from 1 fixture(s)
```

* `sample_data.json`の前に`fixutres`のパスを明示的に書いても良いですが、Djangoが`loaddata`コマンドを実行する際に、アプリケーションのフィクスチャディレクトリを自動的に検索します。
* 実行後に、正常にロードされていれば、`Installed...`と表示されます。
* 複数のjsonファイルがあれば、スペースで連続させることもできます。

```terminal
$ python manage.py loaddata hoge_data.json foo_data.json
```

## Django管理画面からの確認方法
* アプリ側で確認できるよう実装されていればそこから確認してもOKですし、DBクライアントソフトから確認するでもOKですが、djangoでは管理画面からロードされたデータを表示することができます。
    * Djangoの管理画面とは：Webブラウザを使用してデータベースの内容を閲覧・編集するためのユーザーインターフェース
* Djangoのプロジェクトにアクセスし、管理画面にログインします。URLは`admin`をつけてあげればアクセスできます。
* admin画面で見る場合、`models.py`と`admin.py`には以下のような記載が必要です。

```models.py
from django.db import models

class Movie(models.Model):
    title = models.CharField(max_length=100)
    director = models.CharField(max_length=100)
    released_date = models.DateField()

    def __str__(self):
        return self.title
```

```admin.py
from django.contrib import admin
from .models import Movie

admin.site.register(Movie)
```

* admin画面にアクセスすると、以下のように表示されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/9a7b7171-7014-d61b-8b7e-0f0b9e01c064.png)

* モデルの管理ページで、特定のレコードを表示するには、レコードをクリックします。これにより、レコードの詳細ページが表示されます。
* と言うことで簡単に初期データをロードできるのがいいですよね。`fixtures`ディレクトリには、アプリケーションの初期状態を表すフィクスチャが含まれていて初期状態を再現するために使用されると覚えておくと、今後も利用することが多そうです。
* Djangoでいうフィクスチャは、Ruby on Railsでいう「seedデータ(`seeds.rb`)と似ていますね。
    * 以前に書いた記事はこちら：[【Ruby on Rails】seedとseed_fuの違い・CSVファイルで読み込ませる方法](https://qiita.com/Ryo-0131/items/a5662c8701aad4af9f65)
* `loaddata`と逆で、DBに格納されているデータをダンプしておくコマンド`dumpdata`もあります。
    * 参考記事：[DjangoでDBに格納したデータをダンプ(バックアップ)させる【dumpdata】](https://noauto-nolife.com/post/django-dumpdata/)
【GA4】スクロール距離ごとにイベント検出する方法（疑似ヒートマップ）

初期の scroll の割り当て値が90%と高く、CV地点がページ内離脱を前提としている際の分析の際に困るため。
個別にscrollの値を設定し、離脱ポイント毎の数字を出すことで、擬似的にヒートマップのように離脱地点を閲覧できるようにする。

方法に関しては、下記記事を参考に「Google Tag Manager」も使用。

https://index-lab.jp/tools/0524090743/ga4-customize-001/

当該記事の手順では「15,30,45,60,75%の範囲がそれぞれアクション計測されてしまい、GA4上のUIではまとめて表示されてしまう」仕様となるため(GA4の「レポート＞エンゲージメント＞ページとスクリーン」の画面の場合)、WEB解析時の利便性を考慮し、個別に割り当てて行く設定を行う。

<h2>Google Tag Managerの設定</h2>
まずはGoogle Tag Manager(以降GTMと省略)の設定からご紹介します。

「変数」の設定で「Scroll Depth Threshold」を追加します。

![GTM01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214763/41b88aec-710a-df3a-8dd1-2f82c8be7a03.jpeg)

「トリガー」の項目で以下の画像のように、計測したいスクリーン距離を個別に指定していきます。

![GTM02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214763/8a9b0254-d861-4bc4-5d09-86116f7f81cd.jpeg)

設定は以下の通りで、スクロール距離の割合に任意の数字を指定してください。(今回は15%区切りで個別に設定)

![GTM03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214763/f9637e20-f0c1-2169-61de-14306a804a05.jpeg)

次に「タグ」の項目でGA4のイベントにて、トリガーを割り当て、GA4上に実装されるイベントを作成していきます。

各距離毎に、以下の画像を参考に設定します。

![GTM04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214763/a9f798a0-5485-58a5-6f9a-34e5d36db8b8.jpeg)

イベント名はGA4上で表示されるため

・半角英数字
・GA4上で視認した際に意味がわかるもの

にしておきましょう。

パラメータ名：percent_scrolled
値：{{Scroll Depth Threshold}}」

スクロール距離ごとにタグの設定が終わったら、右上の「公開」画面を押して、タグ実装します。

![GTM05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214763/e923aac0-258f-05d1-7578-d2c537352a92.jpeg)

:::note warn
※注意
GA4へのデータ反映は1日ほどかかるので、ここで一旦作業停止し、後日、GA4上の設定を行います。
:::
Amazon Linux 2 に PHP8.2 をインストールする時にハマった件(libgd.so.103)

# Amazon Linux 2 に PHP8.2 をインストールする時にハマった件(libgd.so.103)

# userdataの抜粋

EC2インスタンスのUserDataの保存場所としては、`/var/lib/cloud/instance/scripts`に入っている

私の環境では、`/var/lib/cloud/instance/scripts/runcmd`

中身を抜粋するとこの様な感じ

```/var/lib/cloud/instance/scripts/runcmd
#!/bin/sh
amazon-linux-extras install -y epel
yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
yum -y update
yum -y install git nfs-utils yum-utils
yum -y --enablerepo=remi install composer
yum -y remove php* php-* libzip* libsodium
yum -y install libzip5 libsodium krb5-devel openssl-devel gcc gcc-c++ libxml2-devel libtool oniguruma5php jbigkit gd gd-devel libwmf libraqm gd3php
yum -y --disablerepo=* --enablerepo=remi-php82 install php php-{cli,fpm,zip,devel,gd,mbstring,curl,xml,pear,bcmath,mysqlnd}
```

結論から言うと
　gd3phpがインストールされていないと、php-gdが依存関係でインストールできませんでした。

この様なエラーが出る

```
エラー: パッケージ: php-gd-8.2.4-1.el7.remi.x86_64 (remi-php82)
       要求: libgd.so.103()(64bit)
```

今回の記事のキモは、どうやって、gd3phpまで辿りつけたかです。

libgdって事で、なんとなくgd系のものが足りないとはわかるが・・・

## 1. ldconfigコマンドで共有ライブラリのリンクを調べます。

```
ldconfig -p

libgd.so.2 (libc6,x86-64) => /lib64/libgd.so.2
libgd.so (libc6,x86-64) => /lib64/libgd.so
```

確かに`libgd.so.103`が無い・・・
ピンポイントで１０３がインストールされないとダメらしい

`libgd.so.103`で、ググると`gd3php-2.3.3-7.el7.remi.x86_64 ...`がヒットする

## 2. yum info gd3phpしてみると、インストールされていなかった

php82に絞りたかったので、`--disablerepo=* --enablerepo=remi-php82`としていた

remi-php82に絞らないと、`yum install php82 php82-php-*`と指定する必要があり
そうすると、`/usr/bin/php82`になってしまって、alternativesでパス通さないといけなくなるので、リポジトリを絞っていました。

## 3. gd3phpは、remiリポジトリなので、phpの前にyumで入れておく

```
yum -y install libzip5 libsodium krb5-devel openssl-devel gcc gcc-c++ libxml2-devel libtool oniguruma5php jbigkit gd gd-devel libwmf libraqm gd3php
```

```
ls -la libgd.so.103.0.11
-rwxr-xr-x 1 root root 420624  6月 23  2022 libgd.so.103.0.11
```

入った！

## おしまい
【Kaggle】コピーしたノートブックのパッケージのバージョンが古すぎるときの対処法

## 結論

Notebook options の ENVIRONMENT で Always use latest environment を選択する。

![スクリーンショット 2023-04-06 221621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617307/34d84b28-9b88-9a24-26d9-fb3d8a9d7e9e.png)
JavaScriptのクラスについて

# クラスについて

JavaScriptにおけるクラスは、オブジェクト指向プログラミングの概念の1つで、共通のプロパティとメソッドを持つオブジェクトの種類を定義するために使用されます。クラスは、オブジェクトの設計図として考えることができます。

JavaScriptのクラスは、ES6（ECMAScript 2015）で導入されました。ES6以前のバージョンでは、クラスを定義する代わりに、関数を使用することが一般的でした。

以下は、JavaScriptのクラスの基本的な構文です。


```javascript 
class ClassName {
  constructor() {
    // コンストラクタ
  }

  method1() {
    // メソッド1
  }

  method2() {
    // メソッド2
  }
}

```

上記の例では、`ClassName`という名前のクラスが定義されています。クラスは、コンストラクタと1つ以上のメソッドで構成されています。コンストラクタは、クラスのインスタンス化時に実行されるメソッドであり、通常、クラスのプロパティを初期化します。メソッドは、クラスで定義された関数であり、クラスのインスタンスで呼び出すことができます。

以下は、クラスを使用してオブジェクトを作成する例です。

```javascript 
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const person1 = new Person('John', 30);
person1.sayHello(); // "Hello, my name is John and I'm 30 years old."

```

上記の例では、`Person`という名前のクラスが定義されています。`constructor`メソッドは、`name`と`age`という2つの引数を受け取り、それらを**`Person`**オブジェクトのプロパティに設定します。`sayHello`メソッドは、`Person`オブジェクトの情報を出力します。

`Person`クラスから新しいオブジェクトを作成するには、`new`キーワードを使用して`Person`クラスを呼び出します。`new`キーワードは、クラスのインスタンスを作成し、**`constructor`**メソッドを実行します。オブジェクトを作成した後、`sayHello`メソッドを呼び出すことができます。



## カプセル化について

カプセル化とは、オブジェクト指向プログラミングにおいて、オブジェクトの内部状態や振る舞いを、外部から隠蔽することを指します。

具体的には、オブジェクトのプロパティやメソッドを、外部から直接アクセスできないようにすることで、外部から誤った値を設定されたり、本来の使い方と異なる使い方をされたりすることを防止することができます。また、オブジェクトの内部状態を隠蔽することで、オブジェクトの実装を自由に変更できるようになります。

JavaScriptでは、カプセル化を実現するために、通常、クロージャやプライベートメソッドを使用します。クロージャは、関数とその関数が定義されたスコープの組み合わせで、外部からアクセスできないスコープを作ることができます。プライベートメソッドは、クロージャを使用して実現される、オブジェクトの外からアクセスできないメソッドのことを指します。これらの機能を使うことで、JavaScriptでもカプセル化を実現することができます。

## クラスは、オブジェクト指向プログラミングの基本的な概念の1つ

クラスはオブジェクトを生成するためのテンプレートまたは設計図です。クラスは、データとメソッドを組み合わせたものであり、同じ種類のオブジェクトを生成するための設計図として利用されます。

例えば、クラスを使用して、犬のオブジェクトを作成することができます。

クラスには、犬の属性（例：名前、年齢、色など）と犬ができること（例：吠える、歩く、食べるなど）が含まれます。

そして、クラスを使用して、異なる犬のオブジェクトを生成することができます。

カプセル化は、クラスにおいて、データを隠蔽し、外部からの直接アクセスを制限することを指します。この隠蔽されたデータは、クラス内部でのみ操作できます。カプセル化により、外部からの不正なデータ操作や、意図しない副作用が発生するのを防止し、クラスのセキュリティと信頼性を向上させることができます。

カプセル化は、クラスのプライベート変数やメソッド、アクセッサー（getterやsetter）を使用して実現されます。プライベート変数やメソッドは、クラス内部でのみアクセスでき、アクセッサーは、外部からのアクセスを制御し、プライベート変数を操作するためのインターフェースを提供します。

例えば、犬のクラスで考えてみましょう。

犬の名前、年齢、色を格納するプライベート変数を持ち、それらの値にアクセスするためのアクセッサー（getterやsetter）を提供することができます。これにより、犬の名前、年齢、色を直接操作されること防ぐことができます

```javascript
class Dog {
  #name; // プライベート変数
  #age;
  #color;

  constructor(name, age, color) {
    this.#name = name;
    this.#age = age;
    this.#color = color;
  }

  // アクセッサー（getter）
  get name() {
    return this.#name;
  }
  get age() {
    return this.#age;
  }
  get color() {
    return this.#color;

```

## ※アクセッサー（getterやsetter）とは
getterは、オブジェクト内にあるプロパティを取得するためのメソッドです。つまり、外部からオブジェクト内の変数を読み取ることができます。getterは、オブジェクト内の変数を外部に公開する方法です。

setterは、オブジェクト内のプロパティに値を設定するためのメソッドです。つまり、外部からオブジェクト内の変数に値を設定することができます。setterは、オブジェクト内の変数に値を設定するための方法です
`new Date('2023-04-06')` と `new Date('2023-04-06 00:00')` の違い

この違いにすぐ答えれる人はJSチョットデキル人じゃないだろうか。
自分は初めて知った。

それぞれの出力は↓。

```js
new Date(`2023-04-06`).toString()
// => 'Thu Apr 06 2023 09:00:00 GMT+0900 (Japan Standard Time)'
new Date(`2023-04-06 00:00`).toString()
// => 'Thu Apr 06 2023 00:00:00 GMT+0900 (Japan Standard Time)'
```

前者のほうは日本時間の午前9時を指している。どういうことか。

[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date)には↓のように記載されている。

> Date-only strings (e.g. "1970-01-01") are treated as UTC, while date-time strings (e.g. "1970-01-01T12:00") are treated as local. You are therefore also advised to make sure the input format is consistent between the two types.

つまり、日付だけで時刻を省略すると、UTC扱いになるとのこと。
`new Date('2023-01-01')` はUTCの2023年1月1日の0時になるので、
JSTに変換すると午前9時になる。
時刻を省略しないと、各地域のローカルタイムになる。

Gatsbyをいじってるとしょっちゅう日付の操作が発生するんだけど、
この違いを分かってないとバグるので注意(1敗)。

例えば、「2023年の1月の記事を集めたいな〜」って思ったときに、
`'2023-01-01'` 以上 `'2023-02-01'` 以下といった指定はNG。
これだとUTCになるので、2023年1月1日のAM9時〜2023年2月1日のAM9時の期間が対象になってしまう。

## このバグが発生してるGatsbyの拙作theme

[こちら](https://github.com/tenpaMk2/gatsby-theme-figure-blog)❗
今から治すので待って...待って...。
laravel formRequest バリデーションチェック後に実行されるメソッド

## 概要

- バリデーションチェック後に実行されるメソッドをまとめる。

## `withValidator()`メソッド

- このメソッドそのものはバリデーションチェックと同時に実行されるイメージである。
- わざわざ独自ルールを定義するほどでもない独自ルールでチェックしたいときなどに使う。
- idが1のときにエラーにキーidで「The id must not be 1.」を返したい場合下記のように記載する。

    ```FormRequest.php
    public function withValidator($validator): void
    {
        $validator->after(function ($validator) {
            if ($this->input('id') === 1) {
                $validator->errors()->add('id', 'The id must not be 1.');
            }
        });
    }
    ```

## `passedValidation()`メソッド

- バリデーションチェックが正常に完了した場合に実行される。
- バリデーションチェックが正常に完了した場合、リクエストにnameキーで「FooVar」という値を付与したい場合下記のように記載する。

    ```FormRequest.php
    public function passedValidation(): void
    {
        $this->merge([
            'name' => 'FooVar',
        ]);
    }
    ```

## `failedValidation()`メソッド

- バリデーションチェックがエラーで失敗した場合に実行される。
- API系のバリデーションチェックに失敗した場合、JSONでエラーレスポンスを返したい場合などに使う。

    ```FormRequest.php
   protected function failedValidation(Validator $validator):  HttpResponseException
    {
        throw new HttpResponseException(
            response()->badRequest(
                $validator->errors()->toArray(),
            )
        );
    }
    ```
gitのコマンドの使い方-超簡易版



しばらく、離れていましたが
備忘録として書きたいので投稿します。
下記以外にもgithubの設定や
紐づけなどありますが

今回の記事は本当に基本作業に
必要な最低限の記事です。


# ①作業ディレクトリへの移動 

まず前提としてgithubに送りたいフォルダに移動します
cd と書いてそのまま対象のフォルダをのっければ一番早くやれます

```terminal:現在のフォルダ
C:\Users\name>　cd ここにファイルをドラッグ＆ドロップ
```

# ②初期化(作業ディレクトリの宣言)

ここでgitとして作業するよとコードを打ちます
ようはgitリポジトリにします

```terminal:初期化
C:\Users\name>　git init
```
# ③インデックスの追加(作業ファイルの宣言)

さらに細かいファイルをここで選びます

```terminal:ファイルの追加
C:\Users\name>　git add ファイル名
全て選択したい場合は下記の通りに
C:\Users\name> git add .
```

# ④インデックスのコミット(githubへの前準備)

githubへの納品前段階にします
-mをつけることでコメントも追加します

```terminal:githubへ送る前
C:\Users\name>　git commit -m "何がしがのコメント"
```

# ⑤githubへの紐づけ

ここで送りたいさきへ紐づけします

```terminal:githubへ送る前
C:\Users\name>　git remote add origin https://github.com/ユーザ/[リポジトリ].git
```



# ⑥プッシュ(githubに送る)


```terminal:githubへ送ります
C:\Users\name>　git push origin master
```

# ※コードの一覧

```terminal:一覧
C:\Users\name>　cd ここにファイルをドラッグ＆ドロップ
C:\Users\name>　git init
C:\Users\name>　git add ファイル名 (git add . で全選択)
C:\Users\name>　git commit -m "何がしがのコメント"
C:\Users\name>　git remote add origin https://github.com/ユーザ/[リポジトリ].git
C:\Users\name>　git push origin master
```
SourcetreeでGitHubに接続できなかったという話

# はじめに
　まず私はGitHubに慣れていなければ、Sourcetreeも数年ぶりに触りましたことをここに表明します。アーメン。
　職場でSourcetreeを使ってGitHubからリポジトリをクローンしようぜ！っていう環境構築の手順の一つがありまして、その時の話です。
　ターミナルからどりゃーってコマンド叩いてみたもののオーソリで怒られる。
　~~まぁそんな気はしてたけどな！~~

**前提条件**
 <ul>
  <li>GitHubのセキュリティ設定で2段階認証が有効になっています。</li>
  <li>元々個人で使っていたGitHubアカウントが組織に属している形です。</li>
 </ul>

# 教えてChatGPT君
　まずは相方に聞いてみることにする。
![スクリーンショット 2023-04-06 212358.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/1fff1d60-42de-3b06-49c0-57c76df7600e.jpeg)
　個人的には５の提案は好き。でもね、それじゃあ人間は成長しないと思うんだよ相棒。

# 結局やったこと
1. Sourcetreeの[設定]を開く。
2. [設定] > [アカウント] を開く。
3. [追加] を押下して、以下のように入力する。
ホスト：GitHub
認証タイプ：OAuth
プロトコル：SSH

4. ユーザー名の所で、[アカウントを接続] を押下して、GitHubアカウントと接続する。
この時に2段階認証とかをやる。
5. SSHキーの所で、[キーを生成]を押下する。

6. [保存]を押下して作業完了。

:::note info
SSHでうまくいかない場合はプロトコルをHTTPSにして動作しました。
:::


# 所感
　ChatGPTが言っている３の提案が結局やったことになるんだろうか:thinking:
　もうちょっと具体的な所まで教えてくれると嬉しいよChatGPTくん ~~（成長を放棄した人間）~~
GitHub Education で学生証アップロード後、即リジェクトされるなら

自分の GitHub プロフィールと学生証に書かれた内容が一致していることを確認するだけでなく、 **GitHub Education 側が、そのプロフィールの更新を取得できているか** も確認してください。

現在（2023-04-06）の仕様では、学生証の画像はどうやら OCR され、認識された文字列とプロフィールの内容が一致しているかが判定基準となるようです。
また、文字認識が成功していても、直近の GitHub プロフィールの更新はすぐには取得できないのか、不一致があるとしてリジェクトされることがあります。
リジェクトを送信画面ですぐ知らせてくれる場合もあれば、そのまま送信後の画面に遷移してしまう場合もあります。いずれも、メールにてやり直しを求める文面が届くので、成否はメールで確認できます。

# ①画像の用意
まず認識されやすいように、学生証の画像に英訳を付けます。例：
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253921/713e9327-bd5b-193b-caa5-186cf6e97223.png)
> cited from @SNQ-2001 https://qiita.com/SNQ-2001/items/796dc5e794ac3f57a945

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253921/204ad1af-d8fc-0205-3a83-d37c34cb5988.png)
> ↑自分の場合は、MS Paint で画像の縦幅を２倍にしてできた余白に、実際の学生証と同じようなレイアウトで英訳を打ち込みました。

# ②プロフィールの更新
打った英訳をもとに、GitHub プロフィールを更新します。
あとで元に戻せるので、関係しそうなものは放り込んでおきましょう。

# ③プロフィール更新の確認
GitHub のページ上で更新できていても、GitHub Education 側で取得できていないことがあります。リジェクトのメールの文面で、自分の名前が最新のものになっていない場合、更新を取得できていないものと思われます。しばらく待ちましょう。数時間も待てば反映されてると思います。

# そのほか
カメラでの撮影を推奨する文面が書かれていましたが、自分の PC カメラでは文字が鮮明に撮れなかったので、スマホで撮った写真を PC に移し、それを PC のブラウザで開き（新規タブに D&D ）、さらにそのウィンドウを OBS の Virtual Camera 越しに認識させました。撮影ボタンを押すとアスペクト比が変化しますが、特に問題なさそうです。
ちなみに画像をブラウザのタブに出すのは、OBS 側で MS Paint や MS Photos のウィンドウをキャプチャしても黒矩形しか表示されなかったからです。ブラウザは簡易ビューア替わりになりますし、全画面表示もできるので、ここで使いました。
whereとfind_byの違いについて

# はじめに
whereとfind_byの違いについてメモを残します

### 今回の背景
```
User.find(id)
```
### 違いを一言で
where・・・条件に該当する全てのレコードをActiveRecord::Relation オブジェクトとして返す
find_by・・・最初に該当する 1 件のレコードを返す


### 実際に書いてみる
##### 該当するレコードが存在する場合
```user.rb
class User < ActiveRecord::Base
end
```

where(id: 1) を使った場合は、id が 1 である全ての User レコードを取得する
```
users = User.where(id: 1)
#=> #<ActiveRecord::Relation [#<User id: 1, ...>, #<User id: 1, ...>]>
```

find_by(id: 1) を使った場合は、id が 1 である最初の User レコードを取得する
```
user = User.find_by(id: 1)
#=> #<User id: 1, ...>
```
##### 該当するレコードが存在しない場合

where メソッドを使用して、存在しない id を指定すると、空のActiveRecord::Relation オブジェクトが返される

これは、where メソッドが条件に該当するレコードがなくてもエラーを発生させずに処理を継続するための仕様
```
users = User.where(id: 999)
#=> #<ActiveRecord::Relation []>
```
該当するレコードが存在しない場合には nil が返される
find_by メソッドが単一のレコードを取得することを期待するため、該当するレコードがない場合には nil を返す仕様

```
user = User.find_by(id: 999)
#=> nil
```
Googleフォームと自作フォームを連携した時、回答済みにならない問題の対処法

＜やりたかったこと＞
自作の問い合わせ確認画面の送信ボタンを押した後、
自作の問い合わせ完了画面に飛ぶ。
googleフォーム側は回答済みになっている。

＜発生した問題＞
自作の問い合わせ確認画面の送信ボタンを押した後、
googleフォームの入力画面に飛ぶ。googleフォーム側で回答済みになっていない。
（googleフォームの入力画面は未入力の項目はなかった。）

＜問い合わせフォームのページ遷移＞
自作の入力画面 -> 自作の確認画面 -> 自作の送信完了画面
※自作の送信完了画面が表示された段階で、
googleフォームでは回答済みになる。

＜対処方法＞
自作の送信完了画面に飛ぶ処理をjQueryの非同期処理（Ajax送信）で記述する。
以下のソースを自作の確認画面のformタグの下(`</form>`の下)に記述する。

```javascript:書き方
<script type="text/javascript">
    /**
    *	問い合わせ完了画面にAjax送信で遷移させる関数
    *
    *	@param event	eventオブジェクト
    *	@return bool	結果
    **/
    $('#confirm_form').submit(function(event){	// 問い合わせ確認画面の送信するボタンをクリックすると呼ばれる。
        event.preventDefault();					// formの送信機能を停止する。
    
        // googleフォームのformタグのaction属性と各項目の値を変数に代入。
        let baseUrl = "https://docs.google.com/forms/...../formResponse";
        let kind = $("[name=\"entry.xxxxx\"]").val();
        let name = $("[name=\"entry.xxxxx\"]").val();
        let kana = $("[name=\"entry.xxxxx\"]").val();
        
        // jQueryのajax関数を使用して非同期処理を実行する。
        $.ajax({
            url: baseUrl,
            data: {
                "entry.xxxxx": kind,
                "entry.xxxxx": name,
                "entry.xxxxx": kana,
            },
            type: "POST",
            dataType: "xml",
        }).always((jqXHR, textStatus, errorThrown) => {		// ajax送信が成功すると、問い合わせ完了画面に移動する。
            window.location.href = "問い合わせ完了画面のファイルの相対パス";
        });
        return false;
    });
</script>
```

＜参考サイト＞
https://haniwaman.com/google-form-original/

＜問題が発生した原因＞
ブラウザのセキュリティ対策として、1つのサイトで複数のドメインからデータを
持ってこれないようになっている(クロスドメイン)ため、
自作の問い合わせフォームに入力した内容がgoogleフォームに送信できなかったと考えた。
（あくまで個人的見解なので、合っているかは不明。）
参考サイト：
https://shogo-log.com/google-form-ajax/
踏み台サーバ用の .ssh/config 自動生成

# はじめに
AWS で EC2 を複数立ち上げて、それらに踏み台サーバ (bastion サーバ) 経由でSSHログインすることはよくあることかと思います。その際、管理コンソールでプライベートIP調べてログインとか面倒ですよね。できれば名前でログインできたら便利ですが、そのための .ssh/config を管理コンソールから読み取って作るのも、それなりに面倒です。本記事では aws CLI を使って .ssh/config を生成する bash スクリプトをご紹介します。

# 前提
* Linux 環境 (WSL2 とか) が利用可能であること
* AWS CLI がインストールされていること
* ec2:DescribeInstances の許可を受けた IAM を所有していること
* EC2 に名前が設定されていること (タグのキーが Name、値にホスト名が設定されていること)
* 踏み台サーバにログインユーザーの秘密鍵が、踏み台経由でログインしたいサーバにその公開鍵が配置されていること (そのような設定をした AMI からインスタンスを作成する必要がありますね)
* jq コマンドがインストールされていること

# 生成スクリプト
以下が .ssh/config を自動生成する bash スクリプトです。

```bash
#!/bin/bash

aws ec2 describe-instances | \
    jq -r '.Reservations[].Instances[] | [(.Tags[] | select(.Key=="Name").Value), .PrivateIpAddress] | @csv' | \
    sort | \
    sed -e 's/"//g' | \
    awk -F, '{
        if (NF == 1) { next; }
        if ($2 == "") { next; }
        printf("Host %s\n", $1)
        printf("  HostName %s\n", $2)
        printf("  Port 22\n\n")
    }'
```

上記スクリプトではログインユーザーを指定していませんが、踏み台サーバにログインしているユーザと異なるユーザでログインする必要がある場合には HostName を設定している行の後に以下を追記するといいでしょう。

```bash
        printf("  User ユーザ名\n")
```
**ユーザ名** の部分を適宜置き換えて下さい。
非同期処理について

## 非同期処理とは
**ある処理が完了する前に、次の処理を開始することができる仕組み**のことです。通信が発生する処理(WebAPIを叩く・DBへクエリを投げるなど)で発生します。実行処理を待たずに、並行して次の処理を実行するので、待ち時間の無くプログラムを実現することができます。

一方、同期処理は、**ある処理が完了するまで、次の処理を開始することができません**。そのため、処理が完了するまで待たなければならず、プログラムの実行速度が低下する可能性があります。

図に表しました。非同期処理では、タスクAがずっと動いています。![asynchronous.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668222/fc1263d5-fd73-9086-32a1-513a42cfbf49.png)

### 非同期処理のメリット

**ユーザーを待たせない：** 重い処理や時間のかかる通信中でもユーザーは別の操作ができる(便利・離脱を防ぐ)

### 非同期処理のデメリット
**制御が難しい：** 例えば、APIを叩くと非同期処理になりますが、これが長い。実行完了までに次の処理に行ってしまうとエラーになる。「実行完了までデータが存在しない」状態になる。

## 対処法
`Promise`を使って非同期処理の実行完了を制御する。この`Promise`には3つの状態があります。

`pending`: 初期状態
`fulfilled`: 処理が成功して完了した状態
`rejected`: 処理が失敗して完了した状態

基本的な記述は以下です。
```javascript
const promiseFunc = () => {
  return new Promise((resolve, reject) => {
    someAsynchronousFunc(() => {
      // 非同期処理
    }).then(() => {
      resolve('成功')  // 非同期処理が成功した場合
    }).catch(() => {
      reject('失敗')  // 非同期処理が失敗した場合
    })
  })
}
```
この場合、`.then`の`resolve('成功')`に到達するまで`promiseFunc`が次の処理に進まないようになります(失敗したら`reject('失敗')`)。これで、デメリットである「制御が難しい」を解決しています。

なお、`Promise`そのものは**非同期処理の状態を管理する役目**しかありません。この`Promise`を使った構文は、もっと簡潔に書くことができます(シンタックスシュガー)


## async/await
`async`と`await`を利用した、非同期処理の構文のことです。`Promise`に比べて記述がシンプルで、直感的にわかりやすいものになります。

**使い方**
・非同期処理を伴う**関数定義に`async`** を付ける
・非同期処理を伴う**関数実行時に`await`** を付ける
・`await`は`async`付き関数内でしか使用できない

```javascript
const asyncAwaitFunc = async() => {
  const hoge = await someAsychronousFunc(() => {
    // 非同期処理
  }).then(() => {
    return '成功'  // 非同期処理が成功した場合
  }).catch(() => {
    return '失敗'  // 非同期処理が失敗した場合
  })
  return hoge
}
```

### 参考
とてもわかりやすかったです！！

https://youtu.be/Vhnz1V-v1cU
【SwiftUI】TCAでアラートを実装する

# はじめに
最近、個人開発アプリをTCAにリアーキテクチャしています。
これから学習も兼ねて、基本的な形を記事にしていこうと思います。

# 実装
```swift:ContentView
import SwiftUI
import ComposableArchitecture

struct ContentView: View {
    let store: StoreOf<Feature>

    var body: some View {
        WithViewStore(store) { viewStore in
            Button {
                viewStore.send(.alertPresented)
            } label: {
                Text("アラート")
            }
            .alert(store.scope(state: \.alert), dismiss: .alertDismissed)
        }
    }
}
```

```swift:Feature
import Foundation
import ComposableArchitecture

struct Feature: ReducerProtocol {
    struct State: Equatable {
        var alert: AlertState<Feature.Action>?
    }

    enum Action: Equatable {
        case alertPresented
        case alertDismissed
    }

    func reduce(into state: inout State, action: Action) -> EffectPublisher<Action, Never> {
        switch action {
        case .alertPresented:
            state.alert = .init(title: .init("テストアラート"))
            return .none
        case .alertDismissed:
            state.alert = nil
            return .none
        }
    }
}
```

# 完成
![Simulator Screen Recording - iPhone 14 Pro - 2023-04-06 at 20.32.28.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/44dbdb5f-2acc-e683-8ea6-977ef104c80a.gif)

# おわり
TCAめっちゃ気に入りました笑
Macのカーソル移動の高速化を限界突破しよう

## はじめに
Macのカーソル移動がMac標準のキーボード設定で高速化できるのだが、もっと早くしたいという人向けの記事。

## キーリピート速度とリピート入力認識
Macのキーボード設定には`キーリピート速度`と`リピート入力認識`があります。
まずは各設定内容について。

![スクリーンショット 2023-04-06 20.16.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644766/7a757a48-ddb3-98ae-6a33-bec35d413b6f.png)

### キーリピート速度
>リピート速度は、押されたキーが 1 秒間にリピートされる回数を表しています。 

### リピート入力認識
>キーボードを押し続けてキャレットが動き出すまでの時間

## 各設定MAXでも物足りない方向けの設定
コマンドで設定することでシステム環境設定の上限値を開放できます。

### キーリピート速度を限界突破させる
ターミナルで下記コマンドを入力。
```
$ defaults write -g KeyRepeat -int 1
```
ちなみに現在の設定は以下で確認できる。
```
$ defaults read -g KeyRepeat
```
システム環境設定の上限値は`2`らしいです。

## 注意事項
上記設定は必ずMacの再起動が必須ですので再起動を忘れずにお願いします。
```
% sudo shutdown -r now
```

### リピート入力認識を限界突破させる
ターミナルで下記コマンドを入力。
```
$ defaults write -g InitialKeyRepeat -int 11
```
ちなみに現在の設定は以下で確認できる。
```
$ defaults read -g InitialKeyRepeat
```
システム環境設定の上限値は`15`らしいです。

## さいごに
今回はMacの`キーリピート速度`と`リピート入力認識`を限界突破させてみました。
小さな効率化が大きな生産性を生むのでまだ設定していない方は是非試してみてください！！
ではまたっ！！
AWS SAPの資格取得までに通った道

# SAPとはなんぞや？
AWSの一番難しいとされている試験で、かなり広い範囲の知識を求められています。(AWSのサービスもそうですが、ITの基本的なこと全般を理解していないと解けません。)
公式が試験範囲とサンプル問題として以下のように出しているので、気になる方はぜひご覧ください。
- [試験範囲](https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sa-pro/AWS-Certified-Solutions-Architect-Professional_Exam-Guide.pdf)
- [サンプル問題](https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sa-pro/AWS-Certified-Solutions-Architect-Professional_Sample-Questions.pdf)


# 前提
Developper Associate(DVA)を一昨年の5月、DevOps Professional(DOP)を一昨年の12月に取得済みです。
SE歴4年目、AWSは3年に満たないぐらい現場で利用していました。

# やったこと
大きく、
- 全体のサービスの把握
- Koiwa clubで問題を解く
- 模擬問題集

## 全体サービスの把握：2022/12〜2023/2
### Udemyの結構有名な人の講義(English)
- https://www.udemy.com/share/102INo3@guHrBrO-_M78JCiGp-CR1XmGEOCySIjjoGI6sh4XuL45mjJLe_5M1HcwYryz1Wupkw==/
- 最初は、動画を見ていたのですが、ハンズオンでもないし、別にスライド共有されているから、それをざっくり読めばいいやってなって軽くスライドを読みました。
- 英語だけど、結構読みやすいのと絵がついている箇所が多いので理解しやすいです。
### 日本語の本を読む
- https://www.amazon.co.jp/dp/4815609063
- 一応、日本語でもざっくり読んで定着を図りたかった。
- ちゃんと読もうとすると、大変なので、ポイントを流し読みする感じで。

## Koiwa clubの問題を解く: 2022/2 ~ 2022/3中旬
https://techstock.jp
- 問題がたくさんある有料サイト
    - AWS全資格 + GCPの資格も少しある。
- 6000円ぐらいで3ヶ月利用できる。(高い。。)
    - 逆にお金をかけてるからやろうという気持ちにはなります。
- 1問1答のスタイルなので、間違えた問題がすぐわかるのと、そこそこ？優しい解説もあるので反復学習に向いている。
    - Solution Architect Associate(SAA)のサンプル問題はログインなしでも見れるらしいので、どうぞ見てみてください。
    - https://techstock.jp/sample/aws-sample-exam/
- 2ヶ月ぐらいかけて2週やりました。(間違えた問題だけ、とか、問題のお気に入りとかもできるので振り返りもしやすかったです。)
    - 解説を読みつつ、理解できないところはお約束のBlackBeltを読みました。
    - IT全般系でわからない用語はまずググりました。(特に僕が調べていたのはネットワーク周りと認証周りです。)
    - DOPを取得するときは、ほぼBlackBeltと公式ページだけでしたが、SAPではIT全般の知識も広い範囲で求められた印象です。

## 模擬試験問題を解く: 2023/03中旬~2023/4/3
- Udemyの模擬試験問題を解く
    - https://www.udemy.com/share/10213E3@9_G1K3z74oKRb_QUskncMTPvOw6zl0lqhPj0-7J_CEc8rp_ZHO0l_WVzxie3IY2WXA==/
    - 時間もなかったので5個分の1つを2週実施。
    - 試験時間の180分が予想以上に時間がないことに気づく。。
    - 75問を180分だから、気持ち2分30秒/問ぐらいのペースでなければならない。。

# 試験本番
モンスターを買い、飲んでテストセンターで頑張る。
かなり諦め問題(後で見直すフラグ)がありつつ、結局15分ぐらいしか余りませんでした。。なので、ちゃんと問題に全部答えられているか確認し、テスト終了でした。(一通り解いた後、疲れて寝落ちしそうでした。。)
そうなんだー、と思ったのは1時間ぐらい予定より早く到着していたけど、いつでも出来ます、と試験担当者から言われたことです。(多分、誰も予定を前に入れていなかったら、早めに始めることもできるのだと思いました。オンラインだと開始時間ちょうどじゃないとチェックインできません。)

# 試験後
- 5営業日以内に必ず試験結果は送る、と伝えられ、帰りました。4/3 19時ぐらい
- 結局通知が来たのは4/5のa.m.4時ぐらいでした。(awsさんからメールで通知が飛んできます。)
- ただ、awsさんからのメールの通知前に、試験が受かっていたら、その旨の連絡がcredyさん(AWSの認定バッチを提供している会社)からメールで通知が来ます。(フライング通知 w) 4/4の19時ぐらい。

なので、ドキドキの時間は大体2日間目安ぐらいかなと思っていてOKだと思います。(私は試験が終わった日にドキドキしすぎて、この情報をすごく調べてました。。)


以上です。読んでいただき & 応援ありがとうございました。
Node.jsでS3オブジェクトをまとめてZip圧縮してS3に保存する

## 要点
Node.js Streamを使うことで、メモリやストレージにデータを保持せず、高速かつ省メモリに実現できる。

## 実装
AWS Lambda (Runtime: Node.js 16.x) を想定した実装になっている。事前に [archiver](https://www.npmjs.com/package/archiver) をインストールしておくこと。  
``` index.js
const archiver = require('archiver');
const { PassThrough } = require('stream');
const { S3 } = require('aws-sdk');

const s3 = new S3();

const bucket = 'bucket';
const keys = ['key1', 'key2', 'key3'];

exports.handler = async (event) => {

  const archive = archiver('zip');
  const output = new PassThrough();

  archive.pipe(output);

  keys.forEach((key) => archive.append(s3.getObject({ Bucket: bucket, Key: key }).createReadStream(), { name: key }));

  archive.finalize();

  await s3.upload({ Bucket: bucket, Key: 'archive.zip', Body: output }).promise();
};
```
WebAssembly 版 esbuild を使って Deno で JavaScript を Minify する

esbuild の公式が推奨している方法ではインストール時に Node.js と npm が必要ですが、WebAssembly 版を使用する場合は Node.js も npm も不要です。

ただし、WebAssembly 版はネイティブ版より esbuild の実行速度が遅くなることに注意が必要です。

本記事では説明しませんが、他の方法として実行バイナリを直接ダウンロードすることもできます。

参考「[esbuild - Getting Started](https://esbuild.github.io/getting-started/)」

# 1. 例

下記のコードがあるとします:

```js:src/main.js
const divElement = document.createElement('p');

divElement.textContent = 'うさぎさん';
divElement.style = 'height: 64px; background-color: #fde;';

document.body.appendChild(divElement);
```

```js:scripts/minify.js
import * as esbuild from 'https://deno.land/x/esbuild@v0.17.15/wasm.js';

const code = await Deno.readTextFile('./src/main.js');

const result = await esbuild.transform(code, { minify: true, format: 'iife', charset: 'utf8' });

for (const warning of result.warnings) {
	console.warn(warning);
}

await Deno.writeTextFile('./dist/main.min.js', result.code);

esbuild.stop();
```

参考「[Transform - esbuild - API](https://esbuild.github.io/api/#transform)」
参考「[Minify - esbuild - API](https://esbuild.github.io/api/#minify)」

上記のコードがある状態で以下のようなコマンドで Deno を実行すると Minify できます:

```sh
deno run --allow-read=./src --allow-write=./dist --allow-net=deno.land scripts/minify.js
```

実行結果は以下のようになります:

```js:dist/main.min.js
(()=>{const e=document.createElement("p");e.textContent="うさぎさん";e.style="height: 64px; background-color: #fde;";document.body.appendChild(e);})();
```

Deno から esbuild を利用する場合、実行終了したいタイミングで `esbuild.stop()` を呼ぶ必要があります。

参考「[Deno instead of node - esbuild - Getting Started](https://esbuild.github.io/getting-started/#wasm)」

ここでは WEB ブラウザ向けのモジュールでない JavaScript コードにするため、フォーマットを `iife` にしています。

参考「[Format - esbuild - API](https://esbuild.github.io/api/#format)」

ちなみに esbuild で文字コード `charset` の設定をしないとコード中の文字列が Unicode エスケープされますが、ここでは Unicode エスケープしないために文字コードを `utf8` に設定しています。

参考「[Charset - esbuild - API](https://esbuild.github.io/api/#charset)」

# 2. Deno でカスタムコマンドを定義する

`deno.json` に `tasks` を記述することでカスタムコマンドを定義できます:

```json:deno.json
{
	"tasks": {
		"minify": "deno run --allow-read=./src --allow-write=./dist --allow-net=deno.land scripts/minify.js"
	}
}
```

```sh
deno task minify
```

参考「[deno task | Manual | Deno](https://deno.land/manual@v1.32.3/tools/task_runner)」
【※個人の感想です】Webアプリをスマホのホーム画面にインストールするだけなら、Service Workerとかデバッグめんどいしいらね



Googleマップ上に、洪水ハザードマップを表示するWebアプリを開発しています

このWebアプリをスマホのホーム画面にインストールできれば、定期的な防災意識の啓発につながるのではと思い、PWAについて調べた内容と感想を書きます

PWAの実装方法を説明するというより、概要と感想がメインです

主に参考にしたのは [MDNのページ](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps) です


# PWAとは？

Webアプリをスマホアプリっぽく利用できるようにするために、ホーム画面にインストールしたり、プッシュ通知したり、ファイルをローカルにキャッシュしてオフラインでも動作を継続できるようにしたりする技術の総称です


## PWAに必要なこと

- サーバがHTTPSであること
- サービスワーカーの実装
- webアプリmanifestファイル

詳しくは [MDNのこちら](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Add_to_home_screen#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92_a2hs_%E5%AF%BE%E5%BF%9C%E3%81%AB%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF)

## サービスワーカーとは？

プッシュ通知や、ローカルのファイルキャッシュを実装するのには、必須の技術です

またWebサイト上で、インストールをうながすポップアップを表示するには **一定条件を満たした** サービスワーカーの実装が必要になります

検証の際に、この一定条件を詳しく書いてあるサイトを見つけられませんでした(あまり深追いしてない)
うまく動かないときに [stack overflowのコメント](https://stackoverflow.com/questions/51586398/pwa-add-to-home-screen-not-showing-popup#answer-56094938) を見て、なんとか動かせた

## A2HSとは？

Add to Home Screen の略。PWAの世界で、ホーム画面にWebアプリをインストールすることを、こうやって読んでいるようです

インストールするだけなら、ブラウザのメニューからインストールを選択すればできます(冒頭のリンク先参照)
問題は、Webアプリの画面上にホーム画面にインストールをうながすポップアップを表示するときです

このポップアップを表示する機能はブラウザ側で実装されているのですが、これが使いにくいです

- ポップアップが表示される条件が不明瞭で、安定的に表示させることが難しい
  - ユーザの初回アクセス時しか表示されないっぽい？
  - Service Workerがアップデートされると表示が復活したりしなかったりする
  - ユーザの操作を邪魔しないよう、頻繁に表示させないような仕様になっているらしい
- この仕様により、何度もブラウザキャッシュをクリアしつつデバッグする必要があるのでつらい
- ポップアップのデザインや表示位置も、ブラウザ依存でカスタマイズできない
- インストールボタンを決まったデザインで、決まった位置に表示したい場合もあるはずなのに、そういったことはできない


# いろいろ試してみたけど

ポップアップは表示させなくても、ブラウザのメニューからインストールはできます
なら、その手順を説明するページを作ってユーザにやってもらえばよくね、という結論に至り、下記のページをユーザに案内することにしました


https://mybest.monarchs.name/pwa/add-to-home-screen