OmniAICreator/Qiita-1.07M huggingface dataset:
ChatGPTに謝罪の方法を聞いてみた

# 最初に
4回目の投稿になります。品質管理を担当して4か月を過ぎましたが、品質事故がなくなりません。基本の徹底不足やルールの形骸化など理由はいろいろ。理想だけではなく、事実に基づく予防・改善活動という観点で店舗のサポートができればと考えております。
今回は、凝り固まった私の思考だけでなく、違う視点で問題をとらえられるようにChatGPTを試してみました。
# やりたいイメージ
・携帯→ChatGPT→携帯＝質問→AIに回答をもらう→答え
・正しい対応に辿り着けるのか検証したい
・違う視点の対応を知ることができれば対応方法の幅が広がる
# Makeのシナリオ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/00c0de87-759c-2f16-ffee-e756d96e1de5.png)

# 使用ツール
[Make](https://eu2.make.com)
[LINE Developers](https://account.line.biz/login?redirectUri=https%3A%2F%2Fdevelopers.line.biz%2Fconsole%2Fchannel%2F2003522909%2Fmessaging-api)
[ChatGPT](https://chat.openai.com/)
# ⓪学習なしのLINEBotに質問してみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/9761fc0e-edbe-b845-8115-ae1ebc997a63.png)
責任者に変われ！店長は！のパターンですね。

# ①ChatGPTに質問する
### 　お申し出対応（お客さまからの電話対応）
初めてのQ＆A
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/27adc54d-66e8-b76b-22c3-96d0cfdfad10.png)
### 　質問を繰り返していく
一番下の入力可能な枠に質問を入力していく。
最後に右の↑をクリック。この作業でより指示を繰り返していくだけ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/7f69bbf6-5a1e-dc37-8557-53f2a466a595.png)






# ②LINEBotを作成する
### 　オウム返しBotを作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/0bf5b619-4454-74ab-13e5-7a3a264db273.png)
・参考記事はコチラどうぞ


https://zenn.dev/inoue2002/articles/3067c574b3a605

### 　オウム返しBotにChatGPTを組み込む
・`Add a module`をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/e575026f-3d31-c017-2544-c476b2c525d0.png)
・ChatGPTを検索し挿入（`Create a Completion`を選択）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/7a728d99-894c-2a56-71e8-007b155c394d.png)
・ChatGPTの中を設定していく
Addを選択し,`connection name` を変更
`gpt-3.5-turbo`　`User`　に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/b390195f-de6e-3905-b6fe-6b90b86c1eaf.png)

Meseeages contentの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/b953e806-50c3-1a6f-180a-26a3f151764b.png)
右側のLINEの設定（Textの変更）　
`Choices` `Message.Content` に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/b94011f5-bf2c-1707-fec0-651e224d0210.png)

Ran　once で確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654366/640ba30a-8a48-3614-fd15-150039e94694.png)


# ③LINEで質問し、返事を受け取る
https://x.com/miyatatu0204/status/1761116565737718087?s=20
# ④最後に
### ChatGPTについて
:::note info
ChatGPTに聞いて、違うかな？という内容の修正を繰り返しました。たぶん聞き方が下手なんですが、うまくいかない。
ベテラン店長といって聞かせたはずが、私は店長ではないと最後に言われた。逃げたらあかんよね。
:::
### LINEBotとの連携について
学習して、データを蓄積していくものかどうかがまだわからない。（未確認）

:::note warn

今後は、より専門的な品質事故の対策や予防について聞いていきたい。蓄積した自分の相棒Botとして機能できるようにしたいと思いました。
:::
### 今後について
それらしく回答はしてくれるようになりました。繰り返し学習させれば、精度はよくなるがお客さまへ直接対応は危険である。返答の仕方を参考にする、サポートの役割として考えるほうがいい。




https://qiita.com/tak001/items/7605f0be7b424118e5a5

https://chatgpt-lab.com/n/n55257c082a9d#b51b6d3a-0bfd-4636-89e2-83570464e07e
C# Excelの.xls形式を.xlsx形式に変換

Excelには`.xls`(旧フォーマット)と`.xlsx`(新フォーマット)が存在し、業務上で処理する際に両方のファイルを取り扱う場合があります。

各プログラム言語の`Excel`を操作するライブラリでは、`.xlsx`のみ対応しているケースが多い為、ファイル形式を変換して扱うようにしてみようと思います:sunny:

## COM参照の追加
COMオブジェクトを扱うため、以下の参照を追加しました。
※PCにExcelがインストールされている必要があります
- Microsoft Excel 16.0 Object Libraly
- Microsoft Office 16.0 Object Libraly

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482880/c31e75b9-abf9-4bbc-2a7b-7373ee0e638a.png)

## 変換処理
とりあえず、やりたいことだけ実行するように処理を記述。
```cs
using Excel = Microsoft.Office.Interop.Excel;

class Program
{
	static void Main(string[] args)
	{
		// コマンドライン引数からファイルパスを取得
		// ExcelConverter.exe "C:\path\to\your\inputFile.xls" "C:\path\to\your\outputFile.xlsx"
		if (args.Length < 2)
		{
			Console.WriteLine("Usage: ExcelConverter <input file> <output file>");
			return;
		}
		string inputFile = args[0];
		string outputFile = args[1];
		
		var excelApp = new Excel.Application();
		Excel.Workbook workbook = null;

		try
		{
			// .xlsファイルを開く
			workbook = excelApp.Workbooks.Open(inputFile);

			// .xlsx形式で保存
			workbook.SaveAs(outputFile, Excel.XlFileFormat.xlOpenXMLWorkbook);
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
		finally
		{			
			if (workbook != null)
			{
				workbook.Close(false);
				excelApp.Quit();
			}

			// COMオブジェクトの解放
			System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
			System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
		}
	}
}
```

## ビルドして実行
コマンドプロンプトから実行して、ファイルが変換できることを確認。
```bash
ExcelConverter.exe "C:\path\to\your\inputFile.xls" "C:\path\to\your\outputFile.xlsx"
```

とりあえず、変換処理はできたので次はこれを別のプログラムから呼び出してみたいと思います。

## Reference
- [チュートリアル: C# での Office のプログラミング](https://learn.microsoft.com/ja-jp/dotnet/csharp/advanced-topics/interop/walkthrough-office-programming)
- [チュートリアル: Visual Studio でシンプルな C# コンソール アプリを作成する (パート 1/2)](https://learn.microsoft.com/ja-jp/visualstudio/get-started/csharp/tutorial-console?view=vs-2022)
基本的なGitHubへのプルリクエストの方法

## アジェンダ
この記事では、GitHubでのブランチ管理の基本的なフローを紹介します。ブランチの最新化、ブランチの切り出し、変更のpush、プルリクエストの作成、そしてブランチの削除まで、開発プロセスをスムーズに進めるためのステップを順に説明します。

## フロー
### ブランチの最新化
開発を始める前に、ローカルの`develop`ブランチが最新の状態であることを確認します。これを行うには、以下のコマンドを使用します。
1. `git fetch`：リモートの変更をローカルに取得しますが、ローカルブランチにはマージしません。
2. `git pull origin develop`：リモートの`develop`ブランチから変更を取得し、ローカルの`develop`ブランチにマージします。

### ブランチの切り出し
新しい機能の開発を始めるには、`develop`ブランチから新しい`feature`ブランチを切り出します。例えば、`feature/test`ブランチを作成するには、以下のコマンドを使用します。
```
git checkout -b feature/test develop
```
このコマンドは、`develop`ブランチから`feature/test`ブランチを作成し、そのブランチにチェックアウトします。

### GitHubにpush
機能追加や変更を行った後、それらをリモートリポジトリに反映させる必要があります。以下のコマンドを実行します。
1. `git add .`：変更されたファイルをステージングエリアに追加します。
2. `git commit -m "任意のメッセージ"`：ステージングエリアの変更をローカルリポジトリにコミットします。
3. `git push origin feature/test`：ローカルの変更をリモートの`feature/test`ブランチにpushします。

### プルリクエストの作成
機能追加や変更を`develop`ブランチにマージするためには、プルリクエストを作成します。GitHubのWebインターフェースで、`feature/test`ブランチから`develop`ブランチへのプルリクエストを作成し、必要なレビューをリクエストします。これにより、コードのレビューと承認プロセスが開始されます。

### ブランチの削除
プルリクエストが承認されてマージされた後、不要になったブランチは削除することが推奨されます。リモートブランチを削除するには、以下のコマンドを使用します。
```
git push origin --delete feature/test
```
ローカルブランチを削除するには、以下のコマンドを実行します。
```
git branch -D feature/test
```

## リファレンス
[GitHubに、developブランチから新しくfeatureブランチを切り出してpushする。 #Git - Qiita](https://qiita.com/kagami_t/items/f1e521b7e95a90376808)
表示されている画像情報を取得

Web上にある画像情報を取得して表示させます。

```html
<img id="target" src="./images/img/apple.jpg" alt="リンゴ">
```
```js
const element = document.getElementById("target");

const width = element.naturalWidth;
const height = element.naturalHeight;
const alt = element.getAttribute('alt')
const src = element.getAttribute('src')
const resultPath = src.split("/").reverse().slice(1).reverse().join("/");
const filename = src.split("/").reverse()[0].split('.')[0];
const extend = src.split("/").reverse()[0].split('.')[1];

console.log(`横：${width}px`)
console.log(`縦：${height}px`)
console.log(`Alt：${alt}`)
console.log(`パス：${resultPath}`)
console.log(`ファイル名${filename}`)
console.log(`拡張子${extend}`)

```
ファイルサイズや更新日はサーバーからのHTTPレスポンスデータから取得するみたい。

```js
let lastModified = "";
let contentLength = "";

(async () => {
  const element = document.getElementById("target");

  const fetchImg = await fetchImage(element.src);
  await getData(fetchImg);

  console.log(`ファイルサイズ：${Math.floor(contentLength / 1024)}KB`)
  console.log(formatDate(lastModified))
})();

async function fetchImage(target) {
  const response = await fetch(target);
  return response;
}

function getData(target) {
  const headers = target.headers;
  for (var pair of headers.entries()) {
    if (pair[0] === "last-modified") {
      lastModified = pair[1];
    }
    if (pair[0] === "content-length") {
      contentLength = pair[1];
    }
  }
}

function formatDate(targetDate) {
  const modified = new Date(targetDate);
  const y = modified.getFullYear();
  const m = modified.getMonth() + 1;
  const d = modified.getDate();
  const h = modified.getHours();
  const min = modified.getMinutes();
  return `更新日：${y}年${m}月${d}日 ${h}時${min}分`;
};
```

#### 参考
https://tektektech.com/javascript-get-fileinfo-from-path/

https://m-kenomemo.com/get-imginfo/
yolov9 を導入してみました。

以下手順
```shell
git clone  https://github.com/WongKinYiu/yolov9.git
cd yolov9
```
# ダウンロード
公式GitHub → https://github.com/WongKinYiu/ にも乗っていますが、一応yolov9-eのリンクをここにも置きます。ダウンロードのリンク →
https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-e.pt

# bug 修正
vscode 等のエディターで修正
utils/general.py 903行元々prediction = prediction[0] 正しいは prediction = prediction[0][0]

source 0 カメラ 
```shell
python detect.py --device 0 --source 0 --weights './yolov9-e.pt'
```
# 動作動画です
<iframe width="560" height="315" src="https://www.youtube.com/embed/1C79DzU1SXw?si=hskm8HHeCiuqIFDN" title="YouTube video player" frameborder="0" allow="https://youtu.be/1C79DzU1SXw?si=BCNTEJ1cISsvn3YX; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# 入力画像

![000000006939.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506235/929c84f5-0891-e46f-90ec-f838b1675979.jpeg)
source file(画像) はこのような感じで保存されます
```shell
python detect.py --device 0 --source "D:\python_learning\datasets\coco\images\test2017\000000006939.jpg" --weights './yolov9-e.pt'
```
 
detect: weights=['./yolov9-e.pt'], source=D:\python_learning\datasets\coco\images\test2017\000000006939.jpg, data=data\coco128.yaml, imgsz=[640, 640], conf_thres=0.25, iou_thres=0.45, max_det=1000, device=0, view_img=False, save_txt=False, save_conf=False, save_crop=False, nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs\detect, name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False, vid_stride=1    
YOLOv5  v0.1-3-g6e49803 Python-3.10.10 torch-2.2.1+cu118 CUDA:0 (NVIDIA GeForce RTX 3080 Laptop GPU, 8192MiB)

Fusing layers... 
Model summary: 1119 layers, 69470144 parameters, 0 gradients, 244.0 GFLOPs
image 1/1 D:\python_learning\datasets\coco\images\test2017\000000006939.jpg: 512x640 1 person, 1 truck, 1 handbag, 206.5ms
Speed: 1.0ms pre-process, 206.5ms inference, 53.8ms NMS per image at shape (1, 3, 640, 640)
Results saved to runs\detect\exp9

# 結果
![000000006939.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506235/1870c29a-df59-0adc-34c6-d3f273ce6c8f.jpeg)
Apache Tomcat(トムキャット)の起動/停止方法

Apache Tomcat の起動と停止
- 起動：「C:\{Tomcatインストールディレクトリ}\bin\startup.bat」をダブルクリック
- 停止：「C:\{Tomcatインストールディレクトリ}\bin\shutdown.bat」をダブルクリック
Google Play Storeのクローズドテスト要件に対応する

# Google Play Storeのリリース前の条件が厳しい
Google Play Storeにアプリをリリースするためには、20人以上で14日間以上のアプリのテストをする必要がある。友達が少ない自分にとって、20人以上というのはかなりの人数であるため、どうにかしようと頑張った。

# コミュニティを見つけた
以下の記事で紹介されているが、discordで相互に助け合う形のコミュニティが作られているので、ここで様子を見てみる。
https://zenn.dev/mada/articles/22d2e8b263ae6f
Gitのコマンドにオリジナルのエイリアスを追加する方法

## 概要
- エイリアスの設定方法の解説

## 方法
```sh
# 設定ファイルの場所を確認
git config --global --list --show-origin
file:C:/Users/"username"/.gitconfig
```

```config:C:/Users/"username"/.gitconfig
[user]

[alias]
  # 1つ前のコミットをステージングまで戻す
  rs = reset --soft HEAD^
  # 1つ前のコミットをワーキングディレクトリまで戻す
  rh = reset --hard HEAD^
  # ログを見やすく表示
  lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
```

## 確認
```sh
git lg
```
設定がうまく反映されたら成功です。

引数を直接与えるなどの処理は、関数化して.psなどで記述する必要があるっぽいですが、よく使うものを簡単な名前で保存するとめちゃくちゃ便利ですね！！

## まとめ
設定簡単なのに超楽になってgitを使うのが楽しくなった
VSCodeの統合shellでwindows標準のPowerShellを設定する方法

## 概要
- Windows本体のほうに拡張機能やエイリアスを設定したがVScodeのほうに反映されず困ってしまった
- Windows本体のshellを利用するように変更して解決した

## 現状確認
```sh
# 現在利用しているPowerShellのPathを確認する
$ Get-Process -Id $PID | Select-Object Path
Path
----
C:\Program Files\PowerShell\7\pwsh.exe

#本体のPowerShellで同様に実行する
$ Get-Process -Id $PID | Select-Object Path
Path
----
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

#このように利用しているソースが違うためにこの問題は発生している
```

## 設定方法
設定に移動して、検索バーに以下のように入力する
```sh
Terminal:pro win
```
すると画像のように既定のターミナルプロファイルと、Windowsでのプロファイルの設定を行うJsonファイルが表示される。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125890/d771a5d0-3d0f-81f8-09f2-cc0b91ba43e9.png)

規定のターミナルプロファイルを、`Windows PowerShell`を選択する。
また、合わせてsetting.jsonを編集していく。

```json
  "terminal.integrated.profiles.windows": {
    // Windowsを追記する
    "Windows PowerShell": {
      "source": "PowerShell",
      // 追記する
      "path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
      "icon": "terminal-powershell"
    },
    "Command Prompt": {
      "path": [
        "${env:windir}\\Sysnative\\cmd.exe",
        "${env:windir}\\System32\\cmd.exe"
      ],
      "args": [],
      "icon": "terminal-cmd"
    },
    "Git Bash": {
      "source": "Git Bash"
    }
  },
  "terminal.integrated.defaultProfile.windows": "Windows PowerShell"
```

これで保存をして、新しくシェルを立ち上げるとエイリアスが効くようになるはずです！！

一応、Pathを確認すると、
```sh
$ Get-Process -Id $PID | Select-Object Path
Path
----
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

```
となり、きちんと設定が反映されていることが確認できます。


ついでに、PowerShellをver7に上げると、コマンド補完が協力になるみたいです。
本当にありがたい、、、

https://zenn.dev/tnagata012/articles/how-to-psreadline--80750ecebe1f74

あとは、おすすめの拡張機能です

https://zenn.dev/the_exile/articles/5176b7a5c29bce#4.-fd%EF%BC%88find%EF%BC%89

## まとめ
VScodeのターミナルは少し見づらく感じていたが、Windows本体のPowerShellを設定することで見た目も利用感もめちゃくちゃストレスが減りました！！！
今後はエイリアスバリバリに作って楽していこうと思います！
2週間で AWS Cloud Practitionarをとった記録

## 前座

2024が始まり、もう10分の1が過ぎてしまいました。何か新しいことしないといけないなと思いタイトル通り、AWSに手を付けることにしました。
<br>

いろいろなブログ記事で１週間や１ヶ月で合格というのがあるのなら、２週間で合格できるだろうという計算で始動。あくまで、資格を取るということに重きを置いて、９割を取るなんていう向上心なぞは芽生えさせず、「合格すれば良し！」のマインドです。
<br>
土台の話をすると、AWSには一切触ったことがなく、EC2とS3というサービスがあると認識している程度でした。
<br>
今回は自分がどうやって勉強したか記録を残しておく目的でこの記事を残します。
トレースしたら、同じように２週間で合格できるのではないかな。
（AWS資格はアソシエイトから本番なのだろう。ブログ見てもクラウドプラクティショナーは英検３級のような扱いされてるよね）


## 実行したこと

大きくしたことは以下の４つ。

- AWS公式の動画を見る

https://youtu.be/VIpulFnarKQ?si=rXE3d67cGsgdlS5F

- 教科書を読む。（２周）

https://amzn.asia/d/0apPCwt

▼2024年3月に現在の試験に対応した最新版が発売されるようです。

https://amzn.asia/d/97mKdsU


- 教科書の内容をノートに起こす
- 問題集サイトで300問ほど解く

https://aws-exam.net/clf/


１日の平均勉強時間は平日に2.5時間、休日は6時間ほど。
勉強時間は通勤の移動時間に本を読んだ時間も含んでます。


0日目
まず、上記のAWS公式の動画を見ました。目的はAmazonがAWSをどういう思想、理念をもって運用しているかをなんとなく把握することです。
結果的に、インフラストラクチャやS3についてなど有益な情報を手に入れることができました。
少し古い動画になるのであくまで、レガシーな考え方の取得です。

1ー2日目
- AWS公式動画を見る
- 教科書を１周（大枠を捉える）

3ー8日目
- 教科書をまとめてノート作成（１日２章ずつ）
- 問題サイト開始。気軽にやるのがポイント。（間違えた問題を記録残しておく）

9ー11日目
- 問題サイト継続
- 並行で教科書２周目開始。２日で１周する。（問題であやふやだったところを重点的、詳細に読む）

12日目
- 問題サイト継続
- 以前作成したノートでカバー出来ていない用語・概念を一問一答ベースでまとめる
- （余裕があれば、問題集の間違えをやり直す）

13日目　
- 何もしない（エビングハウスの忘却曲線を意識する）

14日目
- ノートを読み込む。全体を浅く広く捉える。早く寝る

15日目
テスト当日。試験の３時間前に起きて、あまりインプットしないでテストする。


### 問題の取り組み方及び、ノート作りに関して、補足
選択式の問題なので、まず２択に絞る。間違えの選択肢が間違えである理由を思い浮かべる。問題に対してはこの２点だけ意識。
問題中に出てくる「これ整理しておきたいな」を大事にしてノートを作成する。
最終的に、問題集の平均点数で８割行っていれば、７割５分ほどで試験に合格できることが多い印象。



## 振り返り
買った教科書が**CLF-C01**対応のものなのに対し、現在のテストが**CLF-C02**とバージョンが新しい試験問題なので対応できいない試験項目があった。
**AWS CAF**は教科書では触れていなかった。AWSサービスの変化が速いので、**CLF-C02 試験範囲**などのワードで最新の動向を調べた方がいい。

何はともあれ合格してたので、合格祝いにヘアオイル買いました<3
DriftにはDataClassを作成する際に'ies'を'y'に置換する仕様がある

# どんな挙動？
Driftではテーブル名を単数形にした名称のデータクラスを作成してくれます。
このデータクラスを作成するときに、通常であれば単語の末尾の`s`を除いて作成してくれるのですが、末尾が`ies`の場合に自動で`y`に置換されます。
基本的には上記の挙動で、ある程度の単語はいい感じに変換してくれるのですが、MoviesやBoxesなど一部適切に変換してくれない場合があります。

データクラスを作成する際の名称は以下のようになります。

|テーブル名|DataClass名|
|-|-|
|TodoItems|TodoItem|
|Categories|Category|
|Movies|Movy|
|Boxes|Boxe|

# 予期しない変換をされた場合の対処
`DataClassName`というアノテーションが用意されているので、こちらを使用しましょう。

使用例
``` dart
@DataClassName('Movie')
class Movies extends Table {
  IntColumn get id => integer().autoIncrement()();
}

@DataClassName('Box')
class Boxes extends Table {
  IntColumn get id => integer().autoIncrement()();
}

```

https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#names

# 補足
# DataClassってなに？
Driftでテーブルを作成後build_runnerを実行すると一緒にDataClassを継承したクラスを作成してくれます。
このDataClassはテーブルの1行単位を表すクラスです。

公式のsetup項のNextStepのあたりにちらっと出てくるのですが、
https://drift.simonbinder.eu/docs/getting-started/#next-steps

``` dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final database = AppDatabase();

  await database.into(database.todoItems).insert(TodoItemsCompanion.insert(
        title: 'todo: finish drift setup',
        content: 'We can now write queries and define our own tables.',
      ));
  // 👇ここで唐突に出てくる「TodoItem」がDataClass
  List<TodoItem> allItems = await database.select(database.todoItems).get();

  print('items in database: $allItems');
}
```

公式ページでDataClassについて説明しているところを探してみたのですが、見つかりませんでした...。
知っている方がいれば教えてください。

# 対処しないといつ困るの？
例えば、Selectした結果の戻り値の型定義
```
// 👇ここで`Movie`がないとか`Box`がないとかで怒られます。
Future<List<Movie>> get allTodoItems => select(movies).get();
Future<List<Box>> get allTodoItems => select(boxes).get();
```

# 終わりに
この件についてissueを書いてみたのですが、すべての単語への対応が難しいとのことで、上記の回避策を教えてもらいました。
今後修正される可能性は低そうですね。

🐞issue
https://github.com/simolus3/drift/issues/2891
Jetpackのライブラリでグラフを実装してみた

## ライブラリで実装した経緯
今回グラフを私のアプリで初めて導入すると言うことでグラフの作成方法としてはフルスクラッチで作成するか、ライブラリで作成するか考えられますが以下の点でライブラリの方が優れているのではないかと言うことでライブラリ実装になりました。
- 棒グラフや折れ線グラフなどユーザーの反応を見て今後変えていきたい
- 結構機能が豊富グラフ上のタップ時の挙動特定の時に値を表示しないなど
- 案件の実装の手間を少しでも軽くしたい

## 実際に使ったライブラリ
AndroidのグラフライブラリはJetpackになってから公式のライブラリや有名ライブラリがあるわけではなかったのでgithubで一番星の数が多かった[vico](https://github.com/patrykandpatrick/vico)と言うライブラリを使いました。
vicoを選んだ理由
- 機能が豊富で今回の案件に対応できそうだったため幾つかのライブラリを見たが基本的に何かしらの機能が足りないことが多かった
- UIがJetpackなのでJetpackのライブラリを使いたかったしxmlのライブラリはメンテがされているものがなかったため

## 今回うまくいかなかったこと
### 前提としての開発組織の体制について
組織としては企画とデザイナー・バックエンド・各種クライアントフロントと言う形で構成されており、企画・デザイナーが用意したデザインを実装していくような形で進めていきました。 かつグラフを導入した経験がある人がいない状態で案件が始まってしまった。

### 組織として辛かったこと（個人的な感想）
- グラフを導入するための事前の選定などが別案件と時期が被っていたので十分に時間が取れなかったこと
- デザイナーのデザインが重要視されている組織なのでデザインされたものをなるべく忠実に作らなくてはならなかった(ライブラリ上実装が厳しいものがたくさんあった)
- 各クラアントでライブラリも違うのでデザインを適宜ライブラリで実装可能なものにデザインを1人で全クライアント分を準備しなければならなかった
- 共通の仕様書を作成するのが大変になったかつ品証項目も自ずと増えてしまった
- 案件の対応画面がユーザーがよく使う画面のため何かあってもすぐに消しずらいところに実装になってしまったこと

### Androidとして大変だったこと
- グラフのライブラリが公式のものがなかったのでサードパーティーのライブラリを探さなければならなかった。
- フルスクラッチという手もあったがCanvasで一から対応するには時間が足りなかった
- ライブラリのデフォの仕様で対応できないものに関してライブラリをカスタマイズをして無理やり対応したため今後のメンテナンスが大変
- vicoのライブラリが案件のリリース直後にメジャーアップデートが発生した
- うちの会社としては最小端末を横幅320dpと定めているのですがグラフライブラリとデザインを掛け合わせると明らかに320を超えてしまっていた。

## グラフを導入するときに気を付けること
- なるべくライブラリのデフォルトの仕様で作成できるグラフにしてもらう（正直ここに尽きる）
- ライブラリのライセンス・サポートSDKバージョン・サポートする最小端末の確認
- なるべく何が起こっても消しても問題ない画面などで実装していくこと推奨
- なるべく早い段階で企画・デザイン会議に入ること

## 他のチームとの関係性
私の組織としては特にデザイナーが主体となって案件が進んでいっている状態なのですが、フロント・バックエンド・品証チームは週一の定例で会議しており連携がうマック行っていたのですがデザイナとの連携面が不足しており、slackでのコミュニケーションはとっていたものの口の方が明らかに早いことも多いなと思いました。何が言いたいかというとメインで進めるチームとはなるべくコミュニケーションをとる場を用意すること

あとは別の視点の話でデザイナーと開発との認識のズレ
ここは解消るのは結構厳しいなんでこのデザインになるのか納得いかずもやっとしながら実装するみたいな経験が最近多く、逆にデザイナーは開発が何が大変なのかがわかってないため話をしていてもうまく進まないみたいなこともあるのでここら辺を解消したいとは思ってはいるのですがどうしたものかな？と思っています。
unlinkコマンドで指定する削除対象の末尾に「/」付けてエラーになった話

# unlinkコマンドが通らない
誤って作成したシンボリックリンクを削除するためにunlinkコマンドを実行したらエラーが返ってきてちょっと困ったときのお話。

私「シンボリック作るだけの作業？こんなん瞬殺でしょ」
私「ほい出来た」
手順書くん「リンク元とリンク先、逆でしたわ笑」
私「は？？？？？？」

#### こんな感じのディレクトリ構成の環境
```
 ls -lR
.:
total 0
drwxrwxr-x 2 ec2-user ec2-user  6 Feb 23 14:44 link_DST
drwxrwxr-x 4 ec2-user ec2-user 28 Feb 23 14:44 link_SR

./link_DST:
total 0

./link_SR:
total 0
drwxrwxr-x 2 ec2-user ec2-user 22 Feb 23 14:46 SR1
drwxrwxr-x 2 ec2-user ec2-user 22 Feb 23 14:45 SR2

```
#### lnコマンドで./link_SR/SR1/と./link_SR/SR2/のリンクを作る
```
# シンボリックリンクを作る
ln -s ./link_SR/SR1 ./link_DST/
ln -s ./link_SR/SR2 ./link_DST/

# 作成結果
ls -lR
.:
total 0
drwxrwxr-x 2 ec2-user ec2-user 28 Feb 23 14:51 link_DST
drwxrwxr-x 4 ec2-user ec2-user 28 Feb 23 14:44 link_SR

./link_DST:
total 0
lrwxrwxrwx 1 ec2-user ec2-user 13 Feb 23 14:51 SR1 -> ./link_SR/SR1
lrwxrwxrwx 1 ec2-user ec2-user 13 Feb 23 14:51 SR2 -> ./link_SR/SR2

./link_SR:
total 0
drwxrwxr-x 2 ec2-user ec2-user 6 Feb 23 14:49 SR1
drwxrwxr-x 2 ec2-user ec2-user 6 Feb 23 14:49 SR2

./link_SR/SR1:
total 0

./link_SR/SR2:
total 0
```

私「リンク消すのってどうやるんだっけ（ググり）→**unlinkコマンド？そんなんあったっけか**」
#### で、unlinkコマンドで削除しようとする
```
unlink ./link_DST/SR1/
unlink: cannot unlink ‘./link_DST/SR1/’: Not a directory
```
私「消せない……」
私「『ディレクトリではありません』って言ってるけど、シンボリックリンクはそりゃディレクトリではないだろうよい」
私「……あ！」
```
unlink ./link_DST/SR1

# 消せた！
ls -lR
./link_DST:
total 0
lrwxrwxrwx 1 ec2-user ec2-user 13 Feb 23 14:51 SR2 -> ./link_SR/SR2

```

#### 気が付いた理由（理由か？）
私「なんで動かんねん、通れや（同じコマンド実行ｶﾁｬｶﾁｬｯﾀｰﾝ） [^1]」
私「あれ！？削除出来た！？」

#### 要するに
私「よく見たら同じコマンド実行してないじゃん」
私「tabでパス補完して入力したら**末尾に『/』ついてない**わ」
私「リンクの元先間違ってた手順書に書いてあるコマンドメモを信じてコピペしたのがいけなかったのね」

#### 学んだこと
1. コマンドを叩くときはちゃんと調べる
    1. そもそも手順書をちゃんとレビューする
1. 補完機能は偉大である
1. unlinkコマンドの存在



# 【参考資料】
https://beyondjapan.com/blog/2022/10/why-sl-unlink/

[^1]:https://twitter.com/simonharrer/status/1006843860679643137
「Software and cathedrals are much the same – first we build them, then we pray. — Sam Redwine」
~~なんか一時期知り合いの中で流行ってたので有名なのかと思ってたけどそういうわけでもなさそうだった~~
【iOS】iOSアプリのローカライズ方法

# はじめに
こんにちは。
iOS開発の一環として、アプリのローカライズ方法について学んだので、その手順を共有したいと思います。

# 実装前の準備
まず実装を始める前に以下の手順を行います。
1. Infoを選択してLocalizationsに使用する言語を「＋」をクリックして追加する
1. File > New > File...を選択してStrings Fileを選択しLocalizable.stringsを作成する
1. 作成したLocalizable.stringsをクリックしてUtilityAreaからLocalizationボタンをクリックして言語を確定させる
1. 2と3を言語分作成する

# 設定アプリの言語変更によりローカライズ対応を行う（StoryBoard）
StoryBoardで作成している人向けです。
設定アプリで言語変更を行いその変更に伴い言語を変わるようにするときは以下の対応を行います
1. storyboardにカーソルを合わせてUtilityAreaにあるLocalizationの設定言語にチェックを入れる
1. Main.strings(チェック入れた言語)が作成されるのでvalueに表示したい文言を入力する

上記の対応をすることにより設定アイコンから言語を変更することによりアプリ表示の文言が自動で変わります。

# 動的に言語を変更したい場合
次に動的に言語を変更したい場合のやり方です。
やっていることとしてはLocalizable.stringsのvalueを取得してボタンにセットする対応になります。

``` xml:Localizable.strings(Japanese)
"button_1" = "ボタン１";
"button_2" = "ボタン２";
```

``` xml:Localizable.strings(English)
"button_1" = "Button 1";
"button_2" = "Button 2";
```

``` swift:ViewController.swift
@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
private var bundle: Bundle?

override func viewDidLoad() {
    super.viewDidLoad()
    // 日本語にしたい場合
    updateLocalizedText(lang: "ja")
    // 英語にしたい場合
    updateLocalizedText(lang: "en")
}

private func updateLocalizedText(lang: String) {
    // key : Localizable.stringsのkey value : IBoutlet
    let ibOutletAndWordId: KeyValuePairs = [
        "button_1" : button1,
        "button_2" : button2,
    ]

    // ボタンのテキストを変更する
    for (key, value) in ibOutletAndWordId {
        setLanguage(lang)
        let displayText = localizedString(forKey: key)
        value?.setTitle(displayText, for: .normal)
    }
}

private func setLanguage(_ code: String) {
    // 言語を設定する
    guard let path = Bundle.main.path(forResource: code, ofType: "lproj"),
    let bundle = Bundle(path: path) else {
        self.bundle = nil
        return
    }
    self.bundle = bundle
}

// Localizable.stringsのvalueを取得する
private func localizedString(forKey key: String) -> String {
    return bundle?.localizedString(forKey: key, value: nil, table: nil) ?? key
}
```

# 最後に
動的な対応を入れなければほとんどファイルをいじらずにXcodeをポチポチするだけでできるなんて驚きました。
無理に動的な変更を入れなくてもいいと思えば修正はすごく簡単な対応で済むので便利だなと感じました。
この記事が参考になれば嬉しいです。
最後までご覧いただきありがとうございました。

# Androidのローカライズ対応（おまけ）
iOSでのローカライズ方法に加えて、Androidにもローカライズ対応が可能です。
興味のある方は、以下の記事を参照してください。
こちらは、Androidアプリのローカライズ方法に関する方法を説明しています。

https://calnikki.com/android_localize/
AWSノート


## リージョン

地域

AWSでは、地域のことを`リージョン`と言う

## アベイラビリティゾーン

リージョン内のデータセンターが置いてあるポイント

※AWSは、世界中に、データセンターを持っている

### 図で表すと

```mermaid

flowchart LR

subgraph GC[リージョン]
CP1("アベイラビリティゾーン")
CP2("アベイラビリティゾーン")
CP3("アベイラビリティゾーン")
end
```

## VPC

リージョン内に作成する仮想ネットワーク

### 図で表すと

```mermaid

flowchart LR


subgraph GC[リージョン]
    subgraph GS1[VPC]
            CP1("アベイラビリティゾーン")
            CP2("アベイラビリティゾーン")
            CP3("アベイラビリティゾーン")
    end
end

```

## サブネット

VPCネットワーク内に構築できる最小単位のネットワーク

※アベイラビリティゾーン内に、`サブネット`を作れる

### 図で表すと

```mermaid

flowchart LR


subgraph GC[リージョン]
    subgraph GS1[VPC]
        subgraph CP1[アベイラビリティゾーン]
            CP11("サブネット")
        end
        subgraph CP12[アベイラビリティゾーン]
            CP111("サブネット")
        end
        subgraph CP13[アベイラビリティゾーン]
            CP1111("サブネット")
        end
    end
end



```
Windows の PowerShell で dotfiles にある neovim 用の init.vim を設定する方法

最近 Windows で開発するようになったのでメモ。

普段は自分の`dotfiles`の `.config/nvim/init.vim` に nvim の設定を入れており、mac, linux はそれを読み込むようになっている。

Windows では "$env:USERPROFILE\AppData\Local\nvim\init.vim" においておくことによって nvim 起動時に init.vim が読み込まれる。

今回の目的はPCを買い替えてもすぐに設定が反映されるようにスクリプトを作成すること。

やることはシンプルで、

1. "$env:USERPROFILE\AppData\Local\nvim" が無かったら作成する。
2. "$env:USERPROFILE\AppData\Local\nvim\init.vim" に `dotfiles` にある `init.vim` へのシンボリックリンクを作成する。 

最終的に以下のようなスクリプトになる。

```setup-nvim.ps1
# create directory for init.vim if it doesn't exist

$nvimDir = "$env:USERPROFILE\AppData\Local\nvim"

if (-not (Test-Path $nvimDir)) {
    New-Item -ItemType Directory -Path $nvimDir
}

# create symbolic link to init.vim

$initVim = "$nvimDir\init.vim"

if (-not (Test-Path $initVim)) {
    New-Item -ItemType SymbolicLink -Path $initVim -Value $PSScriptRoot\.config\nvim\init.vim
}
```

dotfiles ディレクトリから作成したスクリプトを実行

```
./setup-nvim.ps1
```

次回nvim起動する時に `init.vim` が読み込まれることを確認。
【初心者必見】簡単！BootStrapとCSSで垢抜けたサイトを作る方法！

こんばんは！
BootStrapだとなんだかBootStrap感が出てしまう(?)と悩んでいる方、もしよかったらこちらの記事を参考にしていただけると幸いです。
最近ECサイトを作っていて、その際にBootStrapとCSSを多用してレイアウトやデザインを修正しているので、個人的に今後も使えるなと思ったデザインの実装方法をお伝えしようと思います！
Railsを使っていますが、紹介ではRails以外で使える書き方にしています。

# 環境
AWS Cloud9
Ruby on Rails 6系
Bootstrap Ver.4（実装方法は[こちら](https://qiita.com/gintaro0111/items/716035adcb8e097b244c)）
CSS
Font Awesome導入済み

## ホバー時に色が変わるボタン
完成後はこちら。ホバー時に背景色と文字色が反転します！BootStrapを使うので、難しいCSSの記述はありません。ちなみにアイコンはFont Awesomeを使用していますが、これは導入後コピペですぐできるものなので、実装は割愛します。
通常時
![スクリーンショット 2024-02-23 225054.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3677302/40916ec3-fb4e-4e46-2136-76ac02a17846.png)
ホバー時
![スクリーンショット 2024-02-23 231744.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3677302/48b3eece-bf50-352b-42fc-4b93ac63972b.png)

### 使用するBootStrapのクラス
aタグやlink_toなどのページ遷移のタグなどにこのクラスを追加してください。
`class="btn btn-outline-light rounded-pill"`
btn-outlineで背景が透過され、文字と外枠のみのボタンができます。ホバー時には色が反転します。rounded-pillでBootStrapでありがちな形から脱却し、角が丸みを帯びたお洒落なボタンができます。
### CSSの指定
今回の実装の場合、background-colorが重要です。通常時は白い文字と枠線がはっきり見えるようなbackground-colorが好ましいです。(一応文字と枠線は白以外にもできますが、BootStrapにありがちな色になってしまいます。アウトラインボタンと調べると公式で出てきます。カスタマイズすればいけるかもしれませんが、今回はやりません。)
```CSS
ボタンの親要素など{
  background-color: #5a4d43;
  font-family: 'Sawarabi Mincho', sans-serif;
}
.btn{
  margin-right: 5px;
}
```
ボタンの背景部分にあたる色の指定をしてください。今回はお手本の色で指定しました。
また、ボタンをいくつか横並びに配置している場合は、間隔をあけるためにmargin-rightを使用してください。ボタン1つの場合はお好きな場所に配置を指定してください。一応フォントも載せておきます。

## 文字の下に下線をひく
完成後はこちら。Item Listの文字の下にある下線の実装方法です。これに関してはCSSのみでできます。ただ、線の長さは要素の長さになるため、BootStrapでcol-md-数字などで区切っておくか、要素にwidthを指定しておくとよいです。今回は前者で長さが指定されています。
![スクリーンショット 2024-02-23 232459.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3677302/37477535-27e4-2c05-f4ea-5d3af12fc81c.png)
### css
```css
下線を付けたい文字のクラス・タグなど{
  padding-bottom: 5px;
  border-bottom: solid 4px #cdbca4;
}
```
padding-bottomを指定して、文字の真下ではなく少し間隔をあけた場所に線がくるようにしています。border-bottomで左からsolidで実線、線の太さ、線の色を指定しています。
### BootStrap
一応ですが、下線を引いている文字を囲っているタグに`class="text-center"`という要素の中での中央寄せになるようなクラス指定をしています。ですが、別に左寄せでも問題ないので好みに任せます。

## カードで画像付き一覧を作る
![スクリーンショット 2024-02-23 234423.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3677302/b859d2a4-307f-d46e-2725-97579c7f0ae3.png)

実装例です。なるべく無駄なものは省いてシンプルにしてみました。
```erb
<div class="card-deck"> 　
  <div class="card shadow-sm text-center" style="min-width: 230px; max-width: 250px;">
    <div class="card-img-top" >
      <img src="", style: 'width: 100%; height: 180px' %>
    </div>
    <h5 class="card-title pt-2">
       カードのタイトル
    </h5>
    <div class="card-text">
       カードの文章
    </div>
  </div>
</div>
```
各クラス指定の説明
`card-deck`　要素の中でカードを横並びにする
`card` カードにしたい部分を囲むとカードとして認識する
`shadow-sm` カードに影をつける
`card-img-top` カードの上の部分にくるイメージ。imgタグなどで画像を指定してください。
`card-title` カードのタイトル部分を指定
`card-text`　カードのテキスト部分を指定

何個もカードを並べたい場合はコピペして作るか、railsのerbファイルであればeach文を使って繰り返し表示してください。画像、カードのタイトル、文章は自由に変更してください。
研究開発職6年間のキャリアを振り返ると共に今後のキャリアについて考える

昨年末にFindyとのキャリア面談に参加してこれまでのキャリアについて面談担当者の方と話した内容を膨らませて以下の記事を投稿しました。

https://qiita.com/y-vectorfield/items/4d3c6957de7d28beb038

本記事では、上記記事ではあまり触れていなかった点をまとめたいと思います。

さて、これまでの6年間を振り返ると現状をどの様に打破しようと思い悩むことの多かった6年間だったと思います。そこで得られた教訓と今後のキャリアプランについて考えたいと思います。(あくまで現状のプラン)

# これまでの6年間

## 理想
AIの研究開発に取り組み、AIで社会課題の解決を図ると共に、社会実装の実現に向けた研究開発を行い、可能であればアカデミアにもOutputを出したい。

## 現実(~2023年上半期)
最早何をやりたいのか誰にも分からない。やることなすこと意味不明。研究開発職、ソフトウェア開発職とは名ばかりの組織において、頻繁に組織や業務が変わる。理不尽な降格や専門性なんて無意味だと言わんばかりの人材育成体制の組織の中で、どの様に自分のキャリアプランを実現していくか？どの様にロマンの有る業務に関われる環境へ移るか？考える日々が続く。。。

## 現状(2023年下半期~)
色々考える中で、現在取れる選択肢の中で最も自分の理想に近い物、多少のリスクを取っても、自分の成長に繋がる選択、自分に取って楽しい選択、ロマン有る選択を選ぶことを心掛けた結果、随分と遠回りをしましたが、自分のやっていることを評価してくれる人が徐々に増え始め、入社後6年目にして初めて社内転職制度を利用して部署異動。夢にまで見たロマン有る業務に従事する日々となる。(詳細は上述の記事内に記載)

# 得られた教訓
* 迷ったらロマン有る選択肢、楽しい選択肢、成長に繋がる選択肢を選択するべし
* リスクとベネフィットを秤にかけて、多少のリスクは長い目で見て判断すべし
* 話をしても分からない人々には新天地の成果で見返すこととし、速やかにサヨナラすべし

振り返ってみれば、6年間の大半の期間は、何とか新天地に移りたいと願いつつ、その実現に実に6年もの歳月を費やしましたが、それによって得られた教訓は上記3つと言えるでしょう。まず、ロマンや楽しさ、面白さが皆無な物はどれだけ時間を掛けても、研究開発職という職種の特性上恐らく成長には繋がらないと思います。よって、このことを常に頭の片隅に置いておくことは重要だと思います。また、リスクを見る時は、ベネフィットと双方を秤に掛けて見る必要が有ると思います。ノーリスクでベネフィットが大きい物は少ないと思います。なので2つを秤に掛けて、少しでもベネフィットが大きい場合は兎に角やってみるというのは大事だと思います。筆者も研究開発の経験を積むために、社内インターンに2回行きましたが、その間の当時の上司の評価は最悪だったと言えるでしょう。しかし、行かずに元の部署にそのまま居れば良い評価が貰えたかと言うと前述の様な部署において、筆者の性分を考えると極めて困難だっただろうと思います。その時は評価が下がりましたが、結果としてそのリスクを負ったことで、部署異動に繋がりました。(詳細は上述の記事)長い目線でリスクを評価したことは良かったと思います。また、過去に居た部署の人々とは兎に角技術的な話が全く噛み合いませんでした。彼らは何故かこれだけAIがホットになっているご時世にAI技術についてあまりに無知かつ理解が薄弱で、あまつさえAIに将来性が無いという一部の人々の主張をまことしやかに信じている風さえ有りました。こう言った人々と議論をしても明らかに時間の無駄な上に、精神衛生上よろしくありません。こう言った人々への対処方法は新天地の成果で見返すこととし、速やかにサヨナラすることが最善策だと思います。

# これからのキャリアプランを考える
さて、今後のキャリアプランですが、最近「リスキリング」なる言葉が市民権を得つつ有ります。人生100年時代と言われる昨今、研究開発職の今後のキャリアプラン、自信のスキルを揺るぎない物にするにはどうすれば良いのか？考えてみることにします。

## 技術者としてのスキルアップの方策
OSSへのコントリビューションに力を入れたいと思っています。GitHubのダッシュボードに1つでも多くの「草を生やす」ための努力をする。業務、プライベートで気になっているOSSにコントリビューションする中で技術力をUpしたいと思います。特にTensorFlowやPyTorchなどのAI系OSSに積極的にコントリビューションしたいと思っています。

## 研究開発職としてのスキルアップの方策
より上の学位、筆者の場合は、それが博士号になる訳ですが、リタイアするまでには取りたいと思っています。日本は欧米と異なり学位を取ってもあまり評価されないと言われます。「ドクターは靴に付いた米粒。取っても食えない。しかし、取らなきゃ気になる。」などと言われます。しかし、限られた期間にユニークなテーマを自分なりに掘り下げて研究する訓練は研究開発職の能力向上に小さくない影響を及ぼすと思います。これまでドクターを取られている方とお仕事をする機会に何度か恵まれましたが、非常に物事を俯瞰的に見ることが出来る上に、自分の知っていること、知らないことに謙虚な姿勢の方が多い様な印象を受けました。矢張り研究開発職として専門性を高める訓練を積む中でこう言った姿勢が醸成されるのではないかと思います。現状筆者は研究をしていないため、すぐには難しいと思いますが、今後のキャリアプランの中に学位取得を1つの通過点に加えるのは有りだと思っています。

## 形に残る物：資格取得
研究開発と資格。この2つは目指す物が全く異なります。ただ、筆者がこれまで6年間モチベーションを何とか維持することに役立った資格取得は今後も続けて行こうと思います。資格取得は形に残ります。また、研究開発とは使う頭の回路が異なるため、良い刺激が得られるということも言えると思います。

# 2024/4/18追記: 投稿キャンペーンの抽選に当選しました。
この記事を投稿した時に「自分のエンジニアとしてのキャリアを振り返ろう！」という記事投稿キャンペーンに投稿しました。このキャンペーンではエンジニアお守りが抽選で当たるとのことでした。投稿〆切後、筆者に抽選当選のメールが届き、エンジニアお守りを頂きました。「タイポ用心」のお守りで、筆者はタイポが多いので御利益が有るかもしれません(笑)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/861424b4-fda2-331d-6e1a-bac858230d83.png)
【JavaScript】ErrorをJSON.stringifyする方法

以下のように記述することでErrorを`JSON.stringify`できます。

```js
    const error = new Error('this is sample error')
    
    console.log(JSON.stringify(error)) // このように記述すると{}となる
    
    console.log(JSON.stringify(error, Object.getOwnPropertyNames(error)))
    // {"stack":"Error: this is sample error\n    at handleFetchError (/home/node/.next/server/chunks/745.js:227:23)\n    at /home/node/.next/server/chunks/102.js:265:62\n    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at async UserPage (/home/node/.next/server/chunks/102.js:287:20)","message":"this is sample error"}
```

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
【スクラム】ロールの兼任について

語り尽くされている感はありますが、スクラムにおいてロールの兼任について考えてみます。
※スクラムの実務経験がない個人の私見です

# 結論
さまざまな兼任したくなる事情はあるでしょうが、やはりロールの兼任はどの組み合わせもうまく機能する気がしません。

その理由の一部を以下に記します。

## PO（プロダクトオーナー）とSM（スクラムマスター）の兼任
言わずもがなというか、いろんなサイトで非推奨とされていますね。
POの「早くリリースしたい」、「チームビルディング的なことよりプロダクトに関わる作業を進めて欲しい」という思いが行き過ぎた時に、静止するのがSMの務めなのにPOが兼任してしまうわけですから。
スクラムとしての正しさを優先して自制できれば良いですが、都合が悪いことは言わずに、開発チームに無理をさせればステークホルダーたちが満足するスピード感でデリバリーできますもんね。
そして開発チームは疲弊します。

## POと開発チームの兼任
無理でしょ。

POってちゃんとプロダクトに対して責任を果たそうと思うとめっちゃやること多いと思います。
開発もまとまった時間取れないと全然生産性出ませんよね。
開発を優先しすぎて次スプリントに捌けるPBIがなくなったり、POとしての仕事がPBI管理だけになったり、POを優先しすぎて全然開発できなかったり、という状態になると思います。

また、POの人の意見が強くなって開発チーム内の平等性が失われると思います。

## SMと開発チームの兼任
SMの業務って軽視されがちですよね。
要件決めたりPBI作ったりするわけでもなく、実際に物を作るわけでもないので、SMが居なくともプロジェクト自体は進みます。
そうするとSMとしての業務がどんどん後回しになり、ただのスクラムに詳しい開発者のような動きになる気がします。
妨害リストの解決が遅くなったり、レトロで決めた改善策がきちんと実行されず形骸化したり、POのサポートが疎かになったり。

---
ロールの兼任ではなくチーム（プロジェクト）の兼任の方がまだ可能性を感じますね。
専任できるなら専任に越したことはないと思いますが。
M365 コマンド関連

# Microsoft Graph
・セットアップ

https://qiita.com/negi_c/items/0854376a4ca12c77dcca

https://learn.microsoft.com/ja-jp/powershell/microsoftgraph/installation?view=graph-powershell-1.0

https://jpwinsup.github.io/blog/2021/06/14/UserInterfaceAndApps/PowerShell/how-to-setup-install-module/

・ライセンス確認

https://jpazureid.github.io/blog/azure-active-directory/operating-license-with-microsoft-graph/#idx4

# ExchangeOnline
・インストール

https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.4.0

https://o365info.com/install-exchange-online-powershell-module/

・接続

https://o365info.com/connect-exchange-online-powershell/

https://learn.microsoft.com/ja-jp/powershell/exchange/connect-to-exchange-online-powershell?view=exchange-ps
Streamlitの使い方ーファイルを保存、ダウンロード

お疲れ様です。

今日は「Streamlitの使い方ーファイルを保存、ダウンロード」について部分いたします。

2020年頃より、機械学習エンジニアやデータサイエンティストが Python でお手軽にウェブアプリを構築できる Streamlit が流行っていて、最近では業務で使っている人も多いのではないかと思います。

Streamlit は、その設計思想から、シンプルな可視化であればほとんど困ることはないだと思います。

# アップロードしたファイルを保存する

Streamlit には、ファイルのアップロード機能が実装されていて、簡単に手元のデータをサーバーにアップロードできます。アップロードしたファイルはメモリ上に配置され、ディスク上に保存されるわけではないので、ファイルを保存したい場合や、パスが必要な処理を施したい場合は、ファイルを開いて書き込んでやる必要があります。

https://share.streamlit.io/ohtaman/st_samples/main/st_upload/app.py

サンプルコード
```python
import os

import streamlit as st
from PIL import Image


IMG_PATH = 'imgs'


def main():
    st.markdown('# 画像を保存するデモ')
    file = st.file_uploader('画像をアップロードしてください.', type=['jpg', 'jpeg', 'png'])
    if file:
        st.markdown(f'{file.name} をアップロードしました.')
        img_path = os.path.join(IMG_PATH, file.name)
        # 画像を保存する
        with open(img_path, 'wb') as f:
            f.write(file.read())
            
        # 保存した画像を表示
        img = Image.open(img_path)
        st.image(img)

if __name__ == '__main__':
    main()
    
```

# ファイルをダウンロードする

以前はダウンロード専用の機能がなく、独自に実装する必要がありましたが、0.88.0 以降では `st.download_button`を使って、簡単にダウンロードリンクを作ることができるようになりました

https://share.streamlit.io/ohtaman/st_samples/main/st_upload/save.py

サンプルコード
```python
import os

import streamlit as st
from PIL import Image


IMG_PATH = 'imgs'


def list_imgs():
    # IMG_PATH 内の画像ファイルを列挙
    return [
        filename
        for filename in os.listdir(IMG_PATH)
        if filename.split('.')[-1] in ['jpg', 'jpeg', 'png']
    ]

def main():
    st.markdown('# 画像を保存するデモ')
    file = st.file_uploader('画像をアップロードしてください.', type=['jpg', 'jpeg', 'png'])
    if file:
        st.markdown(f'{file.name} をアップロードしました.')
        img_path = os.path.join(IMG_PATH, file.name)
        # 画像を保存する
        with open(img_path, 'wb') as f:
            f.write(file.read())
            
        # 保存した画像を表示
        img = Image.open(img_path)
        st.image(img)
        
    # IMG_DIR 以下の画像から選択
    filename = st.selectbox('ダウンロードする画像を選択', list_imgs())
    # ダウンロード
    st.download_button(
        'ダウンロード',
        open(os.path.join(IMG_PATH, filename), 'br'),
        filename
    )

if __name__ == '__main__':
    main()
```

# Streamlit アプリを公開する

2021/11/2 に公開された [Streamlit Cloud](https://streamlit.io/cloud) の機能を使うと、アプリを簡単に公開することができます。
アプリの公開機能は、もともと Streamlit Sharing と呼ばれて提供されていましたが、 使い勝手が大幅に改善され利用内容に応じた料金プランも設定されました。

使い方はとても簡単で、コードを GitHub にあげ、リポジトリとブランチ、ファイルを指定するだけです。コードを修正した場合も、GitHub に push するだけで自動的に更新されます。また、無料のコミュニティプランであっても、メールアドレスによるアクセス制御をしてくれるプライベートアプリを1つ作れます。

![0075f0452db7-20211210.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507088/ec2f8463-682b-2605-f1be-394dc29f5fb6.png)


最後に、Streamlitはあくまでダッシュボードやプロトタイピングのためのツールで、ほどほどの柔軟性と素晴らしい開発体験が得られる一方、認証周りを含め本格的なアプリには向いていない側面もあります。

今日は以上です。

ありがとうございました。
よろしくお願いいたします。
【エンジニアとしてのスキルを向上させるために日頃から大事にしていること】は【アウトプットを可視化】すること

# この記事は何?
2024/02/01〜2024/03/14でQiitaとエンジニア転職「Findy」を提供するファインディ株式会社とのコラボ企画で

* **「自分のエンジニアとしてのキャリアを振り返ろう！」**

が開催されています。

期待している記事のテーマ例に

* **エンジニアとしてのスキルを向上させるために日頃から大事にしていること**

がありました。個人的に意識して行動していることがあったので記事を書くことにしました。

# 大事にしていること　その1. GitHubを使ってアウトプットを可視化すること
以前開催された[2022/7/13開催のLT会【エンジニアの勉強法ハックLT- vol.8】](https://rakus.connpass.com/event/249139/)で
**GitHub草駆動勉強法**というテーマでLTしました。

資料はこちらです。

https://www.docswell.com/s/juraruming/K6DLMZ-2022-07-08-080226


動画はこちらです。

https://youtu.be/qVBcXMYJELw

2022年からこの勉強法でアウトプットを可視化し

* 学びの習慣をつくる
* 技術的な成長

を目的に学んできました。

そんなGitHub草駆動勉強法について簡単に紹介したいと思います。

## GitHub草駆動勉強法とは？
GitHubのコントリビューションのグラフを利用し、勉強を継続するための方法です。
GitHubリポジトリにコントリビューションするとグラフに色がつき（草が生える）自分のアウトプットが可視化されます。
アウトプットされていない日があるとちょっと気になります。
学習のアウトプット習慣をつけるために利用することにしました。

## GitHub草駆動勉強法をはじめたきっかけ
TDD実践者・和田卓人さんの言葉を実践したことです。
引用すると次のとおり（若干意味合い違っていたらすみません）。

* 意味のあるコードを1日1回でも書く。
* 技術書の写経で変更履歴管理ツールにコミットログ(本の章番号・不明点)を書く。
* 座れる駅(始発駅)に住み、通勤中にコードを書く環境をつくる。

個人的にコロナ期間中に時差出勤可能（1時間早く出社）となり通勤で座れるようになりました（通常出社時間だと座れない）。
通勤電車内でコーディング・マイコン基板デバッグが可能となったことも勉強法を進めるきっかけになりました。
片道約1.5hを勉強に当てられるのは大きいです。

## GitHub草駆動勉強法を支える環境
GitHub草駆動勉強法の基本手順は前述したとおりですが、勉強の環境にも少し気をつかっています。

### PC
通勤電車内で技術書の写経をすることが多いですが周りに迷惑をかけたくないと考えています。

下図の写真のようにコンパクトなPCで隣の人に接触しないように気をつけて写経しています。

![pc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/3c687f92-f7df-405c-4b81-d9850c8c00e0.jpeg)


### バッグ
通勤電車内でバッグの中ではマイコン評価基板のデバッグをすることもあります。

![bag.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/887f73c9-f621-a4c1-619c-95014381ae68.jpeg)


### マイコン評価基板の持ち運び
マイコン評価基板を裸で持ち運びするのではなく、マイコン評価基板の外装を改造し、通勤電車内デバッグ可能にしました。

![mcu_board.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/0102c6b8-71ee-0fd7-8a44-1ed78101ef92.jpeg)

## GitHub草駆動勉強法の現状
GitHub草駆動勉強法は2022年から開始し、現在も継続中です。2022年、2023年のGitHubコントリビューションはつぎのようになりました。
GitHub草駆動勉強法開始前の2021年のコントリビューション数は23でした。
GitHub草駆動勉強法を開始することによりアウトプットの習慣化ができてきたと感じています。

![github_2022.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/a8558131-fb84-4ab2-5a47-25b1fdd12652.jpeg)

![github_2023.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/5153a184-19d4-deb4-53d1-bcfed2670265.jpeg)



# 大事にしていること　その2. GitHubコントリビューション以外も可視化すること
GitHubコントリビューション以外にも日々の活動（セミナー参加、記事作成、LT登壇など）を可視化しています。
わたしが利用させてもらっているのは**LAPRAS**です。

https://lapras.com/public/k-abe

各種SNSのアカウントを連携することで日々の活動（セミナー参加、記事作成、LT登壇など）を自動的に可視化してくれて楽です。
イベントの振り返り、自己アピールにも有益と思います。
その他、いろいろと有益な点があると思いますが、私が使わせてもらっている一番の理由は**楽しいから**です。
RPGみたいに日々の活動で経験値を得ている感じが楽しいです。楽しみながらも手間がかからず楽なので個人的に重宝しています。


最後まで読んでいただきありがとうございました🙇‍♀️
【Nuxt3】@nuxtjs/google-fontsでGoogle Fontsを使えるようにする。

Nuxt3で **Google Fonts(Noto Sans Japanese)** を使いたい、[公式ドキュメント](https://nuxt.com/docs/getting-started/seo-meta#external-css)に設定方法があるがnpmパッケージもあるとの事で、今回はそれを使ってGoogle Fontsを設定してみた。

※npmパッケージのインストールはVS codeのターミナルを使用しています。
## @nuxtjs/google-fonts
Nuxt3のプロジェクトディレクトリで以下コマンドを入力してGoogle Fonts用のパッケージをインストールします。
```diff_powershell
npm install -D @nuxtjs/google-fonts
```
インストールが完了したらディレクトリ直下にあるpackage.jsonに以下記述が追加されているはずです。
```diff_json
"@nuxtjs/google-fonts": "^3.1.3"
```
※バージョンは2024年3月14日時点のものです。
## nuxt.config.tsの設定
次に**nuxt.config.ts**の**modules**に以下記述を追加します。
```diff_typescript
modules: ['@nuxtjs/google-fonts'],
```

### 使用したいフォントを設定する
最後に使用したいフォントを設定します。今回の**Noto Sans Japanese**の場合は以下のように設定します。
```diff_typescript
googleFonts: {
    families: {
      'Noto+Sans+JP': '200..700' // もしくはtrueを設定する
    },
    },
```
**「'200..700'」** は使用したいウェイトの範囲になります。
ちなみに「true」だと **normal(400)** と **bold(700)** のみ使用できるみたいです。
設定はこちらで完了。
あとは「npm run dev」コマンドを叩いて、実際のページをデベロッパーツールからNoto Sans JPを読み込まれているはずです。
## まとめ
今回のnuxt.config.tsの中身は以下になります（他の設定は省略しています）。
```diff_typescript
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  devtools: { enabled: true },
  modules: ['@nuxtjs/google-fonts'],
  googleFonts: {
    families: {
      'Noto+Sans+JP': '200..700',
    },
  },
})
```

## 参考
[Nuxt Google Fonts](https://google-fonts.nuxtjs.org/)
[【Nuxt】@nuxtjs/google-fontsでNoto Sans Japaneseを使う](https://zenn.dev/kibe/articles/5c22c1318c253e)
超端的git clone token入手方法の説明

## 流れ
```
git clone https://<url>.git
```
![スクリーンショット 2024-03-14 232111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661566/454e6f5e-2edd-b98c-4dbd-8c612f2314b0.png)

緑色のボタン→URL copy

```
dk@daisei:~/yasu_py$ git clone https://github.com/daisei0311/yasu_20240314.git
Cloning into 'yasu_20240314'...
Username for 'https://github.com': <user name>
Password for 'https://<url>':token
```
tokenを発行→コピー→貼り付けでgit cloneコマンドが始まる

## トークンの入手方法
1. サイトに入る
https://github.com/settings/apps

2. 画面左側 developer settings
→personal access tokens→Tokens(classic)

3. generate new tokensを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3661566/fdaeaabe-22da-daec-05c4-eb296cb214d3.png)
ネットワーキングメディアの種類

# はじめに
3 minutes Networkingのネットワーキングメディアの記事を読みながら、仕事で触れた時全然分からなかったことを思い出したのでまとめる。

# ネットワーキングメディアとは
ネットワークを作成するときに使用するケーブルのこと。
物理層を担っている。

# ケーブルの種類
- 同軸ケーブル
- ツイストケーブル
- 光ファイバ

それぞれの特徴を以下にまとめていきます。

### 同軸ケーブル
銅線を使用したケーブルで特徴としてはシールドというものがついている。
シールドは外部からの干渉を防ぐ役割を担っている。
干渉が少ないことで遠距離の接続が向いている。
高価である。

### ツイストペアケーブル
８本の銅線を２本ずつ絡ませる。
お互いの磁場が打ち消しあうことで干渉を防ぐことができる。
これをキャンセレーションと呼ぶ。
同軸ケーブルほど緩衝に強くないため遠距離は向いていない。
安価である。
シールドがあるタイプもあるが一般的にないタイプのが使用されている。
シールドがないタイプをUTP(Unshielded Twisted-Pair cable)と呼ぶ。

### 光ファイバ
銅線は使用しておらずガラスを使用している。
光信号のため電磁的な干渉を一切受けることがない。
高速である。
高価である。

## まとめ
主にネットワーキングメディアには三種類あり、それぞれ用途や値段によって使い分けが行われている。
恥ずかしながら、光ファイバは名前だけ知ってるだけという感じだったので軽くでも知れてよかったです。
自宅のネットワーク環境くらいは名称・用途など完全に把握できるようになりたい。

## 参照記事

https://www5e.biglobe.ne.jp/~aji/3min/09.html
サクラエディタでよく使う正規表現たち【備忘録】

よくお世話になっている正規表現ですが、毎日使うものでもないので、なかなか覚えることができず。。。
使う度に検索するものもあるので、自分のための正規表現備忘録を作成することにしました。

データ整理などに使っているだけなので、偏りがあるかもしれません。

# 抽出条件一覧
[全角文字だけ](#全角文字だけ)
[半角文字だけ](#半角文字だけ)
[数字だけ](#数字だけ)
[指定した文字列より後（指定した文字列は含まない）](#指定した文字列より後指定した文字列は含まない)
[指定した文字列より前（指定した文字列は含まない）](#指定した文字列より前指定した文字列は含まない)
[指定した文字列以外](#指定した文字列以外)
[指定した文字列が含まれる行](#指定した文字列が含まれる行)
[指定した文字列で始まる行](#指定した文字列で始まる行)
[指定した文字列が含まれる行以外](#指定した文字列が含まれる行以外)
[指定した文字列で始まる行以外](#指定した文字列で始まる行以外)


# 全角文字だけ
```
[^\x00-\x7F]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/4cd806e1-6142-75ac-67a2-1425f27318c7.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 半角文字だけ
```
[\x00-\x7F]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/5ae97328-251a-5c19-513a-3ead83d8f9b9.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 数字だけ
```
\d
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/839b0ea3-a3fd-a6b2-c89f-f2d3030a2970.png)

桁数指定したい時はこっち
```
\d\d
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/c523e17e-1bc3-cf4d-d514-670b9e3c6e1c.png)

数字指定したい時はこっち
```
[2-3][2-4]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/cd7a3d0a-1f89-7344-94f0-b98e055092d7.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列より後（指定した文字列は含まない）
```
(?<=指定文字列).*$
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/b1f0ab35-af22-f134-57cc-96fb5a49bd21.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列より前（指定した文字列は含まない）
```
^.*?(?=指定文字列)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/e0d760eb-880f-7d8f-e4f6-8ddb721bdc1b.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列以外
```
[^"指定文字列","指定文字列"]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/34cd56a9-fbd3-bc4e-0cc9-6b8b68b06ab2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/425030d9-3de9-2441-d2ed-fcaef68cc124.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列が含まれる行
```
.*指定文字列.*
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/3bb18651-59f6-2f17-3c58-2fd79ede6afa.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列で始まる行
```
^指定した文字列.*
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/0d0ac45f-a71d-9946-36e9-c3ec9b1d9ab5.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列が含まれる行以外
```
^((?!指定文字列).)*\r\n
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/59be5130-e08c-85b1-8950-851e486db980.png)

[抽出条件一覧へ戻る](#抽出条件一覧)

# 指定した文字列で始まる行以外
```
^((?!^指定文字列).)*\r\n
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726019/ed704309-f93e-07b7-a935-367534f7b43a.png)

[抽出条件一覧へ戻る](#抽出条件一覧)
・
・
・
正規表現の検索が増えてきたら、また更新したいと思います！
【Lambda】S3イベント通知におけるオブジェクトキーのエンコードに関する注意点と解決策

# 背景
AWSを使用している開発者にとって、S3のイベント通知を処理するLambda関数は一般的なユースケースです。

しかし、オブジェクトキーに半角スペースが含まれている場合、S3はこれをプラス記号にエンコードして通知を送信します。
このエンコードにより、Lambda関数にはエンコード後のオブジェクトキー名が渡され、開発者が予期しない問題に直面する可能性があります。

例えば、S3のイベント通知で起動するLambda関数で、イベントデータからオブジェクトキーを取得し、何らかの処理をするとします。
Lambdaにはエンコード後のキー名が渡されるので、開発者はオブジェクトキーを適切に処理することができず、期待しない動作が発生する可能性があります。
（S3から当該キーのダウンロードに失敗する、など）


# 解決策
この問題を解決するためには、オブジェクトキーをLambda関数内で適切にデコードする必要があります。

Pythonの場合、urllib.parse.unquote_plus関数を使用して、エンコードされたオブジェクトキーをデコードすることができます。
このメソッドを使用することで、プラス記号が半角スペースに変換されます。

以下は、解決策の実装例です。
```lamder_handler.py
import urllib.parse

def lambda_handler(event, context):
    # オブジェクトキーを取得
    object_key = event['Records'][0]['s3']['object']['key']

    # URLエンコードされたオブジェクトキーをデコード
    decoded_object_key = urllib.parse.unquote_plus(object_key)

    print(decoded_object_key)

    # ここに他の処理を追加する

    return {
        'statusCode': 200,
        'body': decoded_object_key
    }
```

# 参考資料
https://note.nkmk.me/python-urllib-parse-quote-unquote/
Railsで画像を保存するメモ

## ソースコード
```ruby
  def create
    # 登録用
    product = Product.new(name:  params['name'], price: params['price'], description: params['description'])

    if params[:image]
      extension = File.extname(params[:image].original_filename)
      random_filename = SecureRandom.uuid + extension
      product.image = random_filename
      image = params[:image]
      File.binwrite("public/products/#{product.image}", image.read)
    else
      product.image = "defalut.png"
    end

    if product.save
      flash[:notice] = "出品に成功しました"
      redirect_to('/home/top')
    else
      flash[:notice] = "出品に失敗しました"
      render("products/sell")
    end
  end
```

## 説明
```ruby
extension = File.extname(params[:image].original_filename)
random_filename = SecureRandom.uuid + extension
```
- `extension = File.extname(params[:image].original_filename)`
    - params[:image].original_filenameで元画像の拡張子を保存
    - File.extnameでファイル名から拡張子を取得
- `random_filename = SecureRandom.uuid + extension`
    - ランダムな一意のファイル名を生成する
    - SecureRandom.uuidで一意のランダムな文字列を生成