OmniAICreator/Qiita-1.07M huggingface dataset:
Dockerインストール【CentOS8】

# Docker インストール

何番煎じでもいい
これはメモなんだ、、、

[公式手順](https://docs.docker.com/engine/install/centos/)

- 気を付ける点
    - usermod コマンド で 一般ユーザでdockerコマンドを使えるようにする
    - ↑の変更後、いったんログアウトしないと設定反映されない

## リポジトリセットアップ

```
# sudo dnf -y install yum-utils

$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
```

## インストール

```
$ sudo dnf install docker-ce docker-ce-cli containerd.io
```

## 起動

```
$ sudo systemctl start docker
$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Fri 2021-12-03 06:16:34 UTC; 20s ago
```

## Hello-world確認

```
$ sudo usermod -aG docker $USER
→いったんログアウト
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
```
CSSでスクロールバーを常時表示する

# 概要
下記のようなX方向のスクロールバーを実装したとき、トラックパッドがない人は←→キーを押さないとスクロールができない。

![](https://storage.googleapis.com/zenn-user-upload/fd5d62ad91cdaf780c7448b8.gif)

# 実装
以下のようにCSSを当てればスクロールバーを常に表示できる。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="src/styles.css" />
  </head>

  <body>
    <div class="container">
      <div 
	class="
	  content
	  scrollbar
	  scrollbar-thumb-rounded
	  scrollbar-thumb-black
	"
      >
        <div class="color">
          color
        </div>
      </div>
    </div>
  </body>
</html>
```

```css:src/style.css
body {
  font-family: sans-serif;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}

.content {
  position: relative;
  width: 200px;
  height: 200px;
  overflow-x: scroll;
}

.color {
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  top: 0px;
  left: 0;
  width: 800px;
  height: 190px;
  color: white;
  background: linear-gradient(0.25turn, red, 50%, blue);
}

/* スクロールバーの幅と高さを設定する */
.scrollbar::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
/* スクロールバーの形を設定する */
.scrollbar-thumb-rounded::-webkit-scrollbar-thumb {
  border-radius: 0.25rem;
}
/* スクロールバーの色を設定する */
.scrollbar-thumb-black::-webkit-scrollbar-thumb {
  --bg-opacity: 1;
  background-color: black;
}
```
![](https://storage.googleapis.com/zenn-user-upload/4b9197dbd3fdf361474ffdba.gif)

ポイントはこちらのCSSです。

```css
/* スクロールバーの幅と高さを設定する */
.scrollbar::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
/* スクロールバーの形を設定する */
.scrollbar-thumb-rounded::-webkit-scrollbar-thumb {
  border-radius: 0.25rem;
}
/* スクロールバーの色を設定する */
.scrollbar-thumb-black::-webkit-scrollbar-thumb {
  --bg-opacity: 1;
  background-color: black;
}
```

`-webkit-scrollbar`でスクロールバーに比較的自由にCSSを当てることがきます。ただ`-webkit-scrollbar`はwebkit系のブラウザにしか対応していないためIEやfirefoxでは適用できない点に注意してください。
TypeScriptでdate-fnsのlocaleを使っても曜日を日本語化できない

# 問題
date-fnsで日本語の曜日を取得したいと思い下記のようなコードを書きました。

```ts:date.ts
import { format } from 'date-fns'
import ja from 'date-fns/locale/ja'

const date = format(new Date(), 'yyyy年MM月dd日(E)', {locale: ja})
console.log(date)
```

しかしこの書き方だと何故か曜日が日本語に変換されず、英語のまま出力されてしまいました。

```terminal:output
期待する結果: 2021年11月14日(日)
実際の結果: 2021年11月14日(Sun)
```

# 原因
JavaScriptにトランスパイルされた`date.js`を確認したところ以下のようなコードになっていました。

```js:date.js
"use strict";
exports.__esModule = true;
var date_fns_1 = require("date-fns");
var ja_1 = require("date-fns/locale/ja");
var date = (0, date_fns_1.format)(new Date(), 'yyyy年MM月dd日(E)', { locale: ja_1["default"] }); // ポイント
console.log(date);
```

ポイントは`{ locale: ja_1["default"] }`の部分で`locale/ja`がdefault exportされてしまっているのが原因のようです。

# 解決策
ライブラリのimportの仕方を変えることで解決できました。

```diff_typescript:date.ts
import { format } from 'date-fns'
- import ja from 'date-fns/locale/ja'
+ import { ja } from 'date-fns/locale' // 変更

const date = format(new Date(), 'yyyy年MM月dd日(E)', {locale: ja})
console.log(date)
```

```terminal:output
2021年11月14日(日)
```

トランスパイルされたコードは下記のようになります。

```js:date.js
"use strict";
exports.__esModule = true;
var date_fns_1 = require("date-fns");
var locale_1 = require("date-fns/locale");
var date = (0, date_fns_1.format)(new Date(), 'yyyy年MM月dd日(E)', { locale: locale_1.ja });
console.log(date);
```
Reviewする犬にDartAnalyzeを食わせる

# Reviewする犬 is 何
Linterの実行結果をPullRequestのコメントとして出力してくれるワンちゃんです。
サポートしているCIはGithubActionsだったり、みんな大好き、CircleCIやTravisCI
他にもJenkinsなどもサポートされています。（名前はないですが、Bitriseなどでも可能です。）

![](https://storage.googleapis.com/zenn-user-upload/782887b723c798631e38d983.png)

今回はCircleCiとBitriseで動かしてみました。
DartAnalyzeが吐き出すログの形式は独自なものしか出さないっぽいので、
efmを使って出力できるようにします。

# 準備

(Github token)

まず、Botを運用するために、専用のアカウントを用意します。

Review Dogを動かすために、コメントができる権限を付与した、Github Token
が必要なので下記手順で作ります。

GithubのマイページへからDeveloper settingsを選択します。

![](https://storage.googleapis.com/zenn-user-upload/fd3fb3e84b6d28aa417a71ac.png)

OAuth　Appsに切り変わり、Personal access tokensというのが出てくるので選択します。

![](https://storage.googleapis.com/zenn-user-upload/83b98bdd136ae7ca136a0c25.png)

そうすると、トークン一覧が表示され、タイトルから見て右側に「Generate new token」というボタンがあるのでクリックします。

次に権限を設定します。

公開リポジトリであれば、「public_repo」、プライベートリポジトリなら「repo」
を設定してください。（※公開リポジトリの場合は権限が最小限のほうが問題が起きにくいです。）

![](https://storage.googleapis.com/zenn-user-upload/6707f6ad60d35743205540ce.png)


# ErrorForamt
dart analyzeを実行すると以下のように出力されます。

```
info • Unused import: ‘’ at lib/mitsukoshi_urls.dart:3:8 • (unused_import)
```

Error Format についていは公式のREADMEやVimのQuickfixなどに書いてありますが今回使うものは以下のようになっています。


|  |  |
| ---- | ---- | 
| %f | ファイル名 |
| %l | 行番号 | 
| %c | 列番号 |
| %m | エラーメッセージ |

※完全に一致しなくても、ほしい形で取れるっぽい
※errorformatについて(https://vim-jp.org/vimdoc-en/quickfix.html#error-file-format)
※[playground](https://reviewdog.github.io/errorformat-playground/)なるものが存在し、記述が合っているかどうか確認できる。

# CircleCi編
雑にサンプル　

```yaml
  review-dog:
    executor:
      name: hogehoge
    steps:
      - checkout
      - flutter-install
      - run:
          name: Intasll ReviewDog
          command: |
           curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh| sh -s -- -b ./bin
           chmod +x $HOME/project/bin/reviewdog
      - run:
          name: Run reviewdog
          command: flutter analyze | $HOME/project/bin/reviewdog -efm="%m • %f:%l:%c •%r" -reporter=github-pr-review
```


# Bitrise編
ScriptのStepを追加して下記のように記述。

```shell
#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x

export CI_REPO_OWNER=$BITRISEIO_GIT_REPOSITORY_OWNER
export CI_COMMIT=$BITRISE_GIT_COMMIT
export CI_PULL_REQUEST=$BITRISE_PULL_REQUEST

curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh| sh -s -- -b ./bin
chmod +x $HOME/git/bin/reviewdog
export REVIEWDOG_LOG="$(flutter analyze | $HOME/git/bin/reviewdog -efm="%m • %f:%l:%c •%r" -reporter=github-pr-review)"

if [ -n "$REVIEWDOG_LOG" ]; then
exit 1
fi
```
※Bitriseの場合そのままだとFaildになってくれないので、明示的にexitしてあげる必要があります。
【Amazon Personalize】Part.3 最新アップデート情報

# はじめに
先日 AWS re:Invent 2021 セッションにて発表された Amazon Personalize の新機能についてまとめていきます。

https://virtual.reinvent.awsevents.com/session-virtual/?v2477da705118cc74fd14460db021e1784e2eed5a7982c6482ec95cb2e86d259644b8741959f52a49e0e6908b82a9d860=C7BC82E22351B565134461242CB84AE0DF5A3F734798BF704940A7026A8C9708931821CBA5B0EA7DF222C038F3D80A94

アップデート履歴は以下公式ドキュメントにあります。
https://docs.aws.amazon.com/personalize/latest/dg/document-history.html

https://virtual.reinvent.awsevents.com/session-virtual/?v2477da705118cc74fd14460db021e1784e2eed5a7982c6482ec95cb2e86d259644b8741959f52a49e0e6908b82a9d860=C7BC82E22351B565134461242CB84AE0DF5A3F734798BF704940A7026A8C9708931821CBA5B0EA7DF222C038F3D80A94

関連記事
 
- [【Amazon Personalize】Part.1 概要、データセットの要件](https://qiita.com/omiyu/private/d984dd1ce1d2ae7df899)
- [【Amazon Personalize】Part.2 映画レコメンデーション作成のデモ](https://qiita.com/omiyu/private/cd24449a6f5e33b2ffe2)

# 1. 非構造化テキストデータに対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/94dd6a25-a26c-8fbd-f5eb-40299bb463fb.png)
製品データセットにおいて、商品の説明や商品レビューなどの非構造化テキストデータから情報を抽出できるようになりました。（英語のみ）
詳細：https://docs.aws.amazon.com/personalize/latest/dg/items-datasets

# 2. 類似アイテムレシピの推奨
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/52e4d8f9-bea9-2605-9d2b-3c3e4034a146.png)
アイテム間の類似性をより正確に識別するために、新しい類似性アルゴリズムが追加されました。
インタラクションデータとアイテムメタデータの両方に基づいて、指定したアイテムに類似するアイテムのレコメンドが生成されます。
詳細：https://docs.aws.amazon.com/personalize/latest/dg/native-recipe-similar-items.html

# 3. ビジネス指標のための最適化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/976a670b-16ab-edeb-4e72-0f1c2408661e.png)
新たにビジネス指標の設定が可能になり、収益や利益率などの指標を考慮しながら推奨項目をレコメンドすることができるようになりました。
詳細：https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/optimizing-solution-for-objective.html

# 4. ユーザーセグメンテーションレシピ（今回初公開の機能）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/b6450b6b-f94b-d7f1-5cf2-f06589e00d06.png)
ユーザーの行動データを分析し、機械学習を使用してよりインテリジェントなユーザーセグメントを構築できるようになりました。
この機能を使用すると、特定のアイテムまたはジャンル、製品カテゴリ、ブランドなどのアイテム属性に対するユーザーの親和性に基づいてユーザーをセグメント化できます。
特定の映画を視聴したり、ブランドごとに特定の製品を購入したりする可能性が最も高いユーザーを対象としたマーケティングキャンペーンを作成したい場合などに使用することができます。
詳細：https://docs.aws.amazon.com/personalize/latest/dg/user-segmentation-recipes.html

# 5. ドメインデータセットグループ（今回初公開の機能）
メディア・eコマースなどの一般的なユースケースに最適化されたレコメンダーを選択できるようになりました。
詳細：https://docs.aws.amazon.com/personalize/latest/dg/domain-dataset-groups.html

## 5-1. メディア・エンターテインメントに最適化されたレコメンド
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/e6b763ef-c640-cbc6-737a-7f5581b8203a.png)

- あなたにおすすめ
- 過去に視聴した X からおすすめ
- より Y に類似に似たもの
- Hot picks

## 5-2. 小売りに最適化されたユースケース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655635/9ffaed89-c134-988a-7eec-963e1c2d5c02.png)

- あなたにおすすめ
- 同じ商品を見たユーザー
- よく一緒に購入されるもの
- 最も見られているもの
- ベストセラー
Vonage Messages API Version 1.0リリース


2021年11月10日より、Vonage Messages APIのVersion 1.0(以降はv1)がリリースされました。メッセージAPIは現在まだベータ版ですが、v1ではAPIがより簡素化された重要な改善が行われました！また多くの付加価値を提供する新機能が追加されています。

v1の新機能とは？
---------------------------------------
* より簡素化されたAPIデザイン

v0.1と比較したv1の主な違いは、APIデザインがより簡素化された点です。これは、APIエンドポイントへの呼び出しやWebhookへのコールバックのリクエストデータの構造において一目瞭然な改善点です。以下に、両バージョンでSMSメッセージの送信に必要なデータペイロードの比較を例として挙げます。

v0.1でのSMSメッセージデータペイロード例：

```
{
  "to": {
    "type": "sms",
    "number": "447700900000"
  },
  "from": {
    "type": "sms",
    "number": "447700900001"
  },
  "message": {
    "content": {
      "type": "text",
      "text": "Hello From Vonage!"
    }
  }
}`
```

v1で上記と同じ構文は以下の通り簡素化を実現：

```
{
  "to": "447700900000",
  "from": "447700900001",
  "channel": "sms",
  "message_type": "text",
  "text": "Hello From Vonage!"
}
```

その他の機能
---------------------------------------
世界で多くの方が利用している[WhatsApp](https://www.whatsapp.com/?lang=ja)に対してv1では新機能が追加されました。v0.1では以下の機能に対してのサポートは対応していませんでしたが、v1では実装済みです。

* WhatsAppインタラクティブメッセージのフル機能サポート
* WhatsAppのリプライボタンと、リストメッセージ表示
* WhatsAppのユーザプロファイル名表示

その中でもWhatsAppの[新機能](https://developer.vonage.com/messages/overview#additional-v1-features)である「インタラクティブメッセージ」に対してもv1では利用可能になりました。。この機能では、WhatsAppのチャットにリストメッセージや返信ボタンを実装が可能です。

リストメッセージの例：
![list-messages-whatsapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1880931/66561e04-0046-aab5-4b34-3f11a44e60a3.png)

リプライボタンの例
![reply-buttons-whatsapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1880931/7759bc29-254e-3cd7-3f4b-a29c22c2d698.png)


Ver1への移行方法
---------------------------------------
すでにMessage API v0.1をお使いで、v1への移行を検討されている方は、[移行ガイド](https://developer.vonage.com/messages/concepts/migration-guide)に移行上の注意点が記載されていますので参考にして下さい。

また、Message APIのご利用をご検討されている方は、以下の連絡先までご連絡頂ければ日本のVonageテクニカルチームがサポート致します！

参照記事
---------------------------------------
https://learn.vonage.com/blog/2021/11/16/announcing-vonage-messages-api-version-1-0/

連絡先
---------------------------------------
Vonage Japan合同会社
Mail: sales-japan@vonage.com
URL: https://www.vonagebusiness.jp/communications-apis/
DockerでPython公式イメージ + MeCab + NEologd辞書

# MeCabについて
## mecab-ipadic-neologd
　`形態素解析`がなんぞや？`MeCab`とはなんぞや？という疑問に関しては他の方のブログでも詳しく解説されていると思いますので、ここでは省きます。
　MeCabの辞書ですが、デフォルトのもの以外では、おそらく多くの人が使っているものとして`mecab-ipadic-neologd`が挙げられるかと思います。LINE株式会社さんが開発しているOSSで新語・固有表現に強いMeCabの辞書で、今尚頻繁に更新されています。ちなみに2021年の新語・流行語大賞に輝いた**リアル二刀流**もNEologdにかかればこんな感じ

```shell
$ mecab -d {デフォルト辞書のパス}
リアル二刀流
リアル	名詞,固有名詞,一般,*,*,*,リアル,リアル,リアル
二刀流	名詞,一般,*,*,*,*,二刀流,ニトウリュウ,ニトーリュー

$ mecab -d {mecab-ipadic-neologdのパス}
リアル二刀流
リアル二刀流	名詞,固有名詞,一般,*,*,*,リアル二刀流,リアルニトウリュウ,リアルニトウリュー
```

## 辞書の設定
　MeCabでどの辞書を使うのか設定する方法は以下の通りです

### 明示的に指定する方法
#### Pythonでは

```python
# python
import Mecab

mecab = MeCab.Tagger(f'-d {使用する辞書のパス}')
```

#### ターミナルからは

```shell
# shell
$ mecab -d {使用する辞書のパス}
```
### 予めデフォルトの辞書を変更しておく方法

```shell
# デフォルトの辞書が設定されているmecabrcというファイルを探す
$ mecab-config --sysconfdir

# 例えば自分のMacでは以下のディレクトリ直下でした
/opt/homebrew/etc

# 今回使用するPython公式イメージ3.9(Debian GNU/Linux 11)では以下でした
/etc

# 一先ずmecabrcがどうなっているか確認すると辞書のパスを指定している行を確認できる
dicdir = /var/lib/mecab/dic/debian  # こんな感じだったり
dicdir =  /opt/homebrew/lib/mecab/dic/ipadic  # こんな感じだったり

# 上の定義を新しく mecab-ipadic-neologdのパスにすることでデフォルト辞書が変更される
```

なおインストールした`mecab-ipadic-neologd`のパスですが、以下のコマンドで分かリます。（[公式レポジトリ](https://github.com/neologd/mecab-ipadic-neologd)にも記載されているので参照してください）

```
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
```

# Docker環境の構築

　Dockerfile、docker-compose.ymlについては

## Python公式イメージ

　DockerHubにあるPythonの公式イメージです。[https://hub.docker.com/_/python/](https://hub.docker.com/_/python/)
　別にUbuntuイメージでもなんでもいいのですが、自分はPython環境を構築するときはPython公式イメージを引っ張ってくることが多いので今回はこちらを使用します。

## Dockerfile

　Dockerfileで書くべきは以下のような記述です。色々やり方はあると思います。

```dockerfile
FROM python:3.9

# mecabの導入
RUN apt-get -y update && \
  apt-get -y upgrade && \
  apt-get install -y mecab && \
  apt-get install -y libmecab-dev && \
  apt-get install -y mecab-ipadic-utf8 && \
  apt-get install -y git && \
  apt-get install -y make && \
  apt-get install -y curl && \
  apt-get install -y xz-utils && \
  apt-get install -y file && \
  apt-get install -y sudo

# mecab-ipadic-NEologdのインストール
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git && \
  cd mecab-ipadic-neologd && \
  ./bin/install-mecab-ipadic-neologd -n -y && \
  echo dicdir = `mecab-config --dicdir`"/mecab-ipadic-neologd">/etc/mecabrc && \
  sudo cp /etc/mecabrc /usr/local/etc && \
  cd ..
```

前半はMeCabの導入、後半でmecab-ipadic-NEologd辞書をインストールしています。また先ほど、

```
echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
```
でインストールした辞書のディレクトリパスを標準出力できると書きましたが、その結果を`/etc/mecabrc`にリダイレクトすることでMeCabのデフォルト辞書を再定義しています。丁度下記の部分ですね。

```
echo dicdir = `mecab-config --dicdir`"/mecab-ipadic-neologd">/etc/mecabrc
```
あと、これは結構ハマりポイントだと思うのですが、以下のように`mecabrc`を指定の場所にコピーしてあげ無いと動きません。ターミナルからは使えるんですが`python-mecab`で使おうとするとパスが通って無いってエラーが出てしまいます。。。

```
sudo cp /etc/mecabrc /usr/local/etc && \
```

一応、今までの流れ含め [GitHub](https://github.com/spider-man-tm/docker-python-mecab-neologd-base) にあげておきました。それにしても早速`リアル二刀流`が反映されているNEologdは優秀ですね。
多言語対応時に注意しておきたいこと

この記事は[モチベーションクラウドシリーズアドベントカレンダー2021](https://qiita.com/advent-calendar/2021/mcs)の4日目の記事です。

はじめまして。リンクアンドモチベーションの野田です。
弊社で開発しているモチベーションクラウドは現在「日本語、英語、中国語（簡体字・繁体字）、インドネシア語」に対応しています。
多言語対応を行う上で、考慮が抜けがちな点をまとめたので参考にしていただければと思います。

## 多言語対応とは？
本記事が指す多言語対応とは、**特定の国の言語や文化に依存しないようにプロダクトを対応させる**ことを指します。
多言語対応と言っても、ただ表示する文言を翻訳して表示すればいいのではありません。
ユーザーが利用する国の文化を理解した上で、顧客に価値が届くようにプロダクトを作る必要があります。

## 前提
本記事で扱うプロダクトはWebブラウザ上で動作するアプリケーションを対象とします。

## 地域によって変化するデータの持ち方
物の名前や金額など、地域によって表示が変わるデータをDBに保存する場合、単一の言語を扱う場合とは異なる構造でDBに保持する必要があります。
例えば、商品の名前や金額をDBに保存する場合、単一言語だと下記のように一つのテーブルに全ての情報をまとめることができますが、多言語対応する場合は下記のように**言語毎に変化するデータを切り出してテーブルにする**のが一般的です。
 
![Screen Shot 2021-12-03 at 5.53.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887131/dccec5a0-b293-0782-4eb1-40104abcc2c9.png)

言語を特定できるカラム（上記ではlanguage）を持たせてリクエストされた言語のデータを取得できるようにします。
関連するテーブルが多くなり当然パフォーマンスは落ちてしまうので、適切にインデックスを貼りましょう。


## 句読点などの表現
![Screen Shot 2021-12-03 at 5.58.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887131/0ae018a8-3341-8c59-0c56-4c41306a31f4.png)
上記の画像は中国語（繁体字）の文言なのですが、「。」や「、」などの句読点が上下真ん中の位置に表示されます。
このように、同じ文字を扱っていても言語によって表示を変える必要があるケースが存在します。
例で挙げた繁体字の句読点などはブラウザ側で自動的に表示をしてくれる場合も多いので、**HTMLのlang属性に表示したい言語を正しく指定する**ようにしましょう。

## ブラウザの自動翻訳
Chrome等のブラウザでは表示されているページの言語を認識し、ユーザーが普段利用している言語に自動翻訳してくれる機能が存在します。
この機能を利用するためには、HTMLのlang属性が正しく設定されていることが前提になります。
逆にlang属性を設定していないと日本語のページなのに他の言語と判定されてしまう場合もあるので注意しましょう。
![Screen Shot 2021-12-03 at 6.35.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887131/a2577a31-e361-3b12-7045-0b194670e29b.png)

## RTL言語
アラビア語やヘブライ語など文章を右から左に読む言語をRTL（right-to-left）言語と言います。
RTL言語の場合文章の表示だけでなく、画面全体のレイアウト自体も右から左に変更する必要があります。


![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887131/5899b90b-1027-d0c1-7552-a6167a80935e.png)
CSSのdirectionプロパティ等を利用すると文章を右から左に表示することができます。

```css
.rtl-text {
    direction: rtl;
}
```

## タイムゾーン

## 終わりに
多言語対応は一度行ってしまえば言語を追加するだけで済む場合もありますが、そうでない場合も多くあります。
言葉や文化を理由にしてプロダクトを通して顧客へ届ける価値が増減してしまうような事態は生みたくないので、
言葉や文化に関わらず顧客に寄り添った開発をすることが大事だと改めて認識することができました。
【Vue.js】v-bindによる双方向バインディングの仕組み

# case

v-bindで双方向のバインディングを行う場合,
入力された値が格納されている`$event.target.value`へvue側が参照している状態になる。
これをシンプルに記述できるようにしているのがv-modelディレクティブ

```vue
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
   
    <title>Document</title>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
    <div id="app">
        <input :value="test" @input="test = $event.target.value"> //この部分
    </div>

    <script>
        let app = new Vue({
            el:'#app',
            data(){
                return {
                    test:'aaa'

                }
            }
        })
    </script>
</body>
</html>
```
Web系エンジニア、35歳を過ぎたら危ない◯◯◯の話

こんにちは。
フルリモートでアジャイル開発をやっています。
僕は22歳で大手SIerのシステムエンジニアになったものの、上流だけじゃなく全部の工程をやりたくなって（他にも理由はあったけど）、26歳で小さなシステム開発会社へプログラマとして転職し、それから色々あって今45歳です。

これまでの人生でいろんなエンジニアの人と関わってきたのですが、幸せそうな人と不幸せそうな人の両方を見てきました。若いうちはその両方の時代を経験することも良いと思います。でも、**35歳を過ぎてエンジニアを続けるのであれば、不幸せな働き方は避けたほうが良い**と思います。自分もエンジニアとして不幸せな時代があったのですが（ほんとに辛かった🥵）、今はとても幸せに働けています🥳。

僕らの組織では週に3人ほどのペースで、エンジニアの方の応募をいただいています🙇‍♂️。その中でたくさんの方のプロフィールを見たり面談でお話を伺ってますが、35歳を過ぎた方の場合、幸せそうな経歴😆とそうでない方の経歴😰、その明暗がはっきり別れているように感じています。

**特に、上司や同僚に恵まれてなさそうな方々がとても多い**印象です。

そんなわけであくまで持論ですが、35歳を過ぎたエンジニアが一緒に仕事したいと思える仲間について、綴ってみたいと思います。

# アンチパターンから
僕が見てきた、こんな仲間とは一緒に働きたくないな、それって不幸せだよね、という例をいくつかあげてみます。
その裏を返すことで、35歳を過ぎてから付き合うべき仲間の定義を浮かび上がらせてみたいと思います。

## ❌見下してくるPM🤬→🎊エンジニアを尊重してくれるPM🙌
自身もエンジニア歴が長かったりして、エンジニアとして自信があるタイプのPMに多いです。チームのエンジニアの実力が自分以上であるとは決して思ってなくて、自分のほうがわかっているからとにかく従え・・・そんな感じのマネジメントをしてくる人です。
実際にはその人に実力が全然ない、エッ？な場合もありますが、実力云々によらず、そんな人のもとで仕事をするのは、35歳を過ぎてからは避けたほうが良いと思います。
こんなPMの下で仕事を続けていたら、**やらされ感という毒の感情**にむしばまれて、自身を向上させようとするモチベーションが枯れてしまうと思うのです。
実際に手を動かして作業するエンジニアの意見や考えは、ちゃんと大切にされないとダメです。そのうえで、あなたのやる気をしっかりと高めてくれたり、あなたの提案をちゃんとクライアントにとどけ、そのフィードバックを直接もらえる機会を作ってくれる。そんな働きかけをしてくれるPMと一緒に仕事をすることが大切だと思います。エンジニアの仕事はとてもクリエイティブなものなので、自律的な開発体験ができる環境に身を置くことが、そのクリエイティビティを高め続けるための必要条件じゃないでしょうか。

## ❌仕事に後ろ向きな同僚🥱→🎊仕事に意欲的な仲間たち👏
できれば仕事なんてしたくない。仕事は少ないほど良い。そんな感じの、常にモチベーションが低い人たちです。そんな人がいる職場にいつづけたら、自身が望んでいなくてもいつの間にか感化されて同化したり、そうでなくとも違和感や嫌悪から来る苦痛のストレスで、大きな負の影響を受けたりするものです。
顧客やユーザーの課題に対して前向きに、責任とやりがいを持って取り組む情熱的な仲間と過ごせることが最高です。いいものを一緒に作って盛り上がれる仲間、大切だと思います。
人に誇れる仕事をしていることが、35歳を過ぎたこれからの人生を、より安定した良いものにしてくれると思いませんか。

## ❌嘘や隠し事をするチームメンバー🤐→🎊透明性の高い、清いチームメンバー😆
「言わなきゃわからない」「知らなかったことにすればいい」「嘘も方便」「どうせ言っても無駄」なんて言葉をよく使う人です。本人はそれで万事スムーズに進むと考えているのかもしれませんが、チームの透明性が重要視される今日のWeb開発業界で、そんな人が所属するチームにいるのは、あなたのエンジニアとしての開発体験の価値をどんどん低下させてしまうでしょう。
チーム活動の透明性を高く保つためには、メンバーひとりひとりの清き心がけと、心理的安全性の確保された組織文化が不可欠です。幸い、しっかりと経験を積んだエンジニアの転職や、独立は追い風の吹いている時代です。もしも現在のメンバーや組織文化に違和感をおぼえるのであれば、10年後を見据えて、あまたにとってより適した開発組織を探してみませんか。

## ❌スキルと適性が低いエンジニア🤯→🎊ハイスキルで経験豊富なエンジニア🧐
これは書くまでもないかな、と思ったんですが網羅性を大切にしたいのでやっぱり書いておきます。
それなりに経験を積んでいるのに、コードが稚拙だったり、設計がダメダメだったり。そんな人と一緒に仕事をしたらつらみしかありません。その人の埋め込んだ不具合を直したり、その人のおそまつな設計に呪われた数々の問題にあたるために、35歳を過ぎたあなたの大切な時間を使うべきではないと思います。
自身のスキルやモチベーションを高めつづけるためにも、仲間のエンジニアのスキルと適性は高い必要があるはずです。若い人たちの尻拭いなんかより、よほど価値のある仕事をしたいと思いませんか？

# 最後に
エンジニアにとって、日々の開発業務が幸せであること。これが、システム開発を通してクライアントや社会に利益をもたらす最適な方法だと信じています。僕たちPRIME ORDERはこのことを、**エンジニア駆動**、と呼んでいます。
35歳をすぎると、大切な家庭への責任が増したり、一方で身体は若さが衰えて健康に時間とお金がかかるようになってきます。そんな時代を迎えているのに、日々の開発業務が不幸なもので、かつ将来に影を落とすような仲間に囲まていることは、あなた史上もっともリスキーなことかもしれません。
周りの仲間を見て違和感を感じているのであれば、ぜひエンジニア駆動な組織でお仕事をすることを強くおすすめします。
PHPでNoSQLデータベース(簡易)を作る

# はじめに
こんにちは。初投稿のまろという者です。
Qiitaでは主にPHP系の投稿をしていこうと思ってます。
今回は、PHPでNoSQLのDBMSを作るわけです。
XMLじゃなくてJSONを使います。

# 概要
- NoSQLのDBMS
- JSONを用いる
- データをそのまま保存

# コード
いきなりコードです。

```php:dbms.php
<?php
// クラス「DBMS」の定義
class DBMS{
 // ファイルパス変数の定義
 private $path;
 // コンストラクタの起動
 function __construct($path){
  $this->path = $path;
 }
 // ファイルパスの変更関数
 function PATH($path){
  $this->path = $path;
 }
 // ファイル内容を変換して取得
 function GET(){
  return json_decode(file_get_contents($this->path), true);
 }
 // ファイル内容を変換して書き換え
 function POST($data){
  $fp = fopen($this->path, 'cb');
  if($fp){
   if(flock($fp, LOCK_SH)){
    if(fwrite($fp, json_encode($data))){
     if(flock($fp, LOCK_UN)){
      if(fclose($fp)){
       return true;
      }
     }
    }
   }
  }
  return false;
 }
}
?>
```

てな感じです。JSONで、テキストをデータ形式で返してくれて、データをテキスト(JSON)で書き込む感じで、さらにどんなデータでも対応。
こんなDBMSが欲しかった...
まあ、普通に使えると思います！少し処理系は置いといて。
また実際にこれを使用すると、

```php:使用例
$db = new DBMS('/db/age.json');
$age = $db->GET();
echo '現在田中さんは'.$age['田中さん'].'歳です';
// 現在田中さんは38歳です

$age['田中さん'] = 39;
if($db->POST($age)){
 $age = $db->GET();
 echo '無事田中さんが39歳の誕生日を迎えました';
 // 無事田中さんが39歳の誕生日を迎えました
}else{
 echo '残念田中さんもう一度38歳で頑張りましょう';
 // 残念田中さんもう一度38歳で頑張りましょう
}
```

はい。田中さんごめんなさい(誰や)

# 終わりに
今回はPHPでNoSQL DBMSを作ってみました。
思ってたんとちゃう…と思ったそこの君！ごめんなさい(?)
使ってみてね〜
[AWS][SmartHR] AWS Single Sign-On での SmartHR への SAML 認証によるログイン設定

弊社では、人事・労務管理に [SmartHR](https://smarthr.jp/) というサービスを使用しています。
SmartHR も SAML 2.0 による Single Sign On に対応しているようなので、AWS SSO から接続できるように設定してみました。

# AWS SSO へのアプリケーション登録

Applications から New Application　として `Add a Custom SAML 2.0 application` を選択します。
次に表示される画面で `AWS SSO SAML metadata file` をダウンロードしておきます。
他の箇所は特に変更する必要はないです。
`Display name` と `Description` だけ、お好みで変更してください。

# SmartHR でのシングルサインオン設定

SmartHR の公式ドキュメントを読んで設定しましょう。
[SAML認証（SSO）を使ってログインする](https://knowledge.smarthr.jp/hc/ja/articles/360037010093)

サービスプロバイダ情報から metadata をエクスポートしておくと、AWS SSO のアプリケーション設定が楽になります。
![スクリーンショット 2021-12-03 14.38.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25964/ab13e085-c192-0788-bc74-efe39961e556.png)

# AWS SSO アプリケーションの設定変更

## Application metadata の設定

SmartHR からエクスポートした metadata を `Application metadata` としてインポートします。
![スクリーンショット 2021-12-03 14.40.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25964/b6924b99-bd04-6692-4b8a-a7331f7f05b4.png)

## Attribute mappings の設定

以下のように設定します。

User attribute in the application|Maps to this string value or user attribute in AWS SSO|Format
---|---|---
Subject|${user:email}|emailAddress
email|${user:email}|unspecified
![スクリーンショット 2021-12-03 14.46.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25964/5860be69-81d7-467d-53af-81367fb153d1.png)


# SmartHR のアカウント情報の変更

SmartHR の公式ドキュメントを読んで設定しましょう。
`連携IdPアカウント` は、AWS SSO のユーザに設定されているメールアドレスを登録します。
[SAML認証（SSO）を使ってログインする](https://knowledge.smarthr.jp/hc/ja/articles/360037085933)

これで、設定完了です。

AWS SSO から SmartHR にログインできるようになります。
私の基本情報対策

#はじめに
私は2021年の秋季基本情報技術者試験を受けてきました。
スコアは午前、午後共に６０点を超え合格ラインは超えることが出来ました。
そこで、今回は私の学習の記録として記事を書かせていただきます。
参考になれば幸いです。

#意識していたこと
今回の試験対策を通して意識していたことが大きく2つあります。

一つ目はモチベーションを維持し続けることです。
基本情報は試験範囲が広く、対策することが多いのでなかなかやる気になるまでに時間がかかります。
私も始めのうちは過去問をやっても全然正解できなくて乗り気になるませんでした。

そこで参考書を読んでその後すぐ過去問に取り組むようにしました。そうすることで、正解する感覚が出てきてモチベの維持につながりました。
当然ながら１回で覚えることができるものは多くはないので、間違えたものはチェックしておき、回数をこなして覚えます。

二つ目は問題の予測です。
基本情報技術者試験は過去問からの流用率が約５０％とかなり高く、非常に対策しやすいです。
逆に、残りの５０％は過去問外から出題される可能性が高いと考えられるので、対策を進めていく中で見たことのない単語を特に覚えていくことが重要と考えました。
特に、基本情報ドットコムの解説は有効活用するように意識していました。

#具体的な対策

ここでは実際の対策を簡単に記していきたいと思います。
個人的に重要度の高い分野順に記していきます。

１　セキュリティ、データベース、ネットワーク
　　この３つは出題量も多く、覚えることも多いと思います。
　　私はどれも過去問を１周し、そこからできないものをつぶしていきました。
　　難しいのであとに回したくなりますが、早いうちにやるべきです。

２　アルゴリズムとプログラミング
　　ここは午前問ではそこまで難しくありませんが、午後問のためにはしっかり勉強しておくべきです。
　　他の授業で深く学ぶ時間があり、その学習が生きていました。

３　マネジメント・ストラテジ
　　この二つは覚えることが中心になっており、過去問を１０回分取り組んだ後、間違いを復習することで対応できました。
　　得点を取るには落としてはいけない分野だと思います。

４　その他の分野
　　どの分野も参考書と過去問５～１０回でなんとかできました。
　　最終的にはこの部分をどれだけ覚えられるかだと思います。
　　ちなみに私はここの取り組みは甘かったです。
　　数をこなせば安定した点数を取ることができたと思います。

続いて午後問についてです。

午後問はどの分野で問題文を読めばわかる問題があります。
そこを落とさずにどれだけ得点できるかだと思いました。

具体的な対策を挙げていきます。
私の対策のほとんどはアルゴリズムとプログラム開発でした。
この二つは過去問にじっくり取り組み問題に慣れる必要があると感じました。
幸い授業の中で、丁寧に取り組み解説までされたので、非常に助かりました。


他の午後問については、得意な分野を中心に取り組みました（ソフトウエア、ソフトウエア設計、マネジメント）。
午前問の知識を磨いていればスムーズ取り組めるようになると思います。

どちらにしても長文を読む体力をつけておくべきです。

#最後に

この試験は、過去問を解いた量と時間が多ければ多いほど合格できる試験です。
不安要素をとことん無くし、自信を持てるようになれば大丈夫でしょう。
私も今回の対策を参考にさらに上の資格を目指していきたいと思います。
IBMCloud ICLB/VPXのXFF(X-Forwarded-For)

##ICLB
ICLBではXFFはデフォルト有効化されている。
IBMCloud Docsでは、下記のページに使えそうなことが記載されているが、有効化の方法が不明だった為、確認したところ、<font color="red">デフォルトで有効化されているため設定不要</font>だった。

[エンドクライアント IP アドレスの保存](https://cloud.ibm.com/docs/loadbalancer-service?topic=loadbalancer-service-advanced-traffic-management-with-ibm-cloud-load-balancer&locale=ja#preserving-end-client-ip-address)

##VPX
VPXでは有効化が必要
ServiceGroupのSettingsにて、Client IPをチェックしHeaderに「X-Forwarded-For」を入力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703043/76b33827-db16-0bc0-cefb-88ab03ac31a5.png)

###確認結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703043/92d21c26-6286-03dc-6ed9-08fe625920c9.png)

[参考：How to Forward Client IP to Back-End Server Using "x-forwarded-for" Header on Citrix ADC](https://support.citrix.com/article/CTX205455)
[参考：How to Enable Client IP in TCP/IP Option of NetScaler](https://support.citrix.com/article/CTX205670)
【CentOS】Minecraft MODサーバ セットアップ【Java 1.18】

# 1.18にアップデートする

お久しぶりです。
最近は教本の写経ばかりしていてアウトプット少なめでした。
ひと段落したのでメモ書きを増やしていきたい所存。

今回は以前導入したMinecraftのMODサーバを1.18にアップデートします。

## サーバ内作業

- とりあえず

```
# dnf -y update
```

### OpenJDK17 インストール

1.17ではOpenJDK16が必要ということでしたが、
1.18ではさらにOpenJDK17が必要になりました。
ソースからインストールしていきます。

```
ソースDL
# wget https://download.java.net/java/GA/jdk17.0.1/2a2082e5a09d4267845be086888add4f/12/GPL/openjdk-17.0.1_linux-x64_bin.tar.gz

解凍
# tar xzvf openjdk-17.0.1_linux-x64_bin.tar.gz

前のJDKはリネームする
# mv /usr/local/jdk /usr/local/jdk_16

1.17を置き換える
# mv jdk-17.0.1 /usr/local/jdk

確認
# java -version
openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-39)
OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing)
```

### 最新版 Fabric インストール

```
1.18用のディレクトリを作成
# mkdir /home/minecraft/v1_18
# cd /home/minecraft/v1_18
# wget -O fabric-installer-0.10.1.jar https://maven.fabricmc.net/net/fabricmc/fabric-installer/0.10.1/fabric-installer-0.10.1.jar

# java -jar fabric-installer-0.10.1.jar server -mcversion 1.18 -downloadMinecraft
# java -jar -Xms6G -Xmx10G fabric-server-launch.jar nogui
# sed -i -e 's/false/true/g' eula.txt

確認
# java -jar -Xms6G -Xmx10G fabric-server-launch.jar nogui
→ワールドが作成されればOK
```

### MODを入れる

```
# cd mods
# gsutil cp gs://XXXXX-bucket/optifabric-1.12.7.jar ./
# gsutil cp gs://XXXXX-bucket/preview_OptiFine_1.18_HD_U_H3_pre5.jar ./
```

### スクリプト修正

```
$ cat backup-fabric.sh
#!/bin/bash
screen -r mcs -X stuff '/save-all\nsave-off\n'
/usr/bin/gsutil cp -R /home/minecraft/world gs://バケット名/$(date "+%Y%m%d-%H%M%S")-world
screen -r mcs -X stuff '/save-on\n'

$ cat startup-fabric-mcs.sh 
#!/bin/bash
(crontab -l | grep -v -F "/home/minecraft/backup-fabric.sh" ; echo "0 */1 * * * /home/minecraft/backup-fabric.sh")| crontab -
cd /home/minecraft
screen -d -m -S mcs java -jar -Xms6G -Xmx10G fabric-server-launch.jar nogui
```
→v1_18ディレクトリのを実行するように変更

- アップロード

前回のやつに上書きする

```
$ gsutil cp backup-fabric.sh gs://バケット名/
$ gsutil cp startup-fabric-mcs.sh gs://バケット名/
```

# 接続、影MODが使えたら完了

Minecraftはサーバ構築の練習ができて非常にいいですね。
Minecraftサーバ構築を通じてもっと応用したサービスを作ってみたいものです。
Visual Studio Code で Azureサインインしても no subscriptions were found

##きっかけ
VSCodeでAzure Funcionsを書いて動いていたのに、しばらくして再度そのコードをデプロイしようとしたら、拡張機能からAzureにログインできてもデプロイ（拡張機能からアップロード）ができなかったのでメモ。

```ruby:通知のエラーメッセージ
 ... no subscriptions were found
```
になる。

##解決策
VSCodeの拡張機能のテナントIDが`common`になってたので、それを`AzurePortalで表示されるテナントIDの文字列`に変更するとログインできた。

##経緯とかやったこととか
<dt>VSCodeでAzure Funcionsを書いて動いていた</dt>
<dd>１ヶ月無料だったのでアカウントAを作って嬉しげに動作確認。60日後？に使わないと思ってアカウント内のデータ削除（された）</dd>
<dt>90日後くらいに</dt>
<dd>あ、やっぱりあのFunctionいるわー</dd>
<dt>Azureアカウント忘れる</dt>
<dd>忘れたから新しいメールアドレスでAzureアカウントB作成。電話番号が同じだったのか、最初に作ったアカウントとみなされず、$200のクレジットはついてこなかったし、Subscription買え、みたいな、前回のアカウントが引き継がれたような状態に。
なのでsubscriptionを買った（ことになる？）
</dd>
<dt>VSCodeを久しぶりに開く</dt>
<dd>サインインが前回のアカウントAだったのでBに切り替え。あれ？no subscriptions were found？
ぐぐったら、再インストールでは解決しないやら、キャッシュ消したらit works for meだの、英語ばかり。でも私はうまくいかず。
最終的に`テナントID`に行き着いた。

:::note warn
テナントIDの項目にIDではなく、テナントの名称`既定のディレクトリ`を設定したりして、迷走した経緯あり。
:::
</dd>
LCP改善施策

# Lighthouse計測方法
* 計測方法1：[PageSpeedInsights](https://developers.google.com/speed/pagespeed/insights/?hl=ja)でテストする
* 計測方法2：ChromeのDevToolのLighthouseタブ
    * アドオンを無効化する為シークレットウインドウで計測する
    * ローカル環境でも計測可能
    * PCスペックにより結果が異なる

## 改善前のスコア
スコアは５６点で詳細は以下でした....
LCP(Largest Contentful Paint)がPOORになっている

![スクリーンショット 2021-11-11 17.23.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348159/601c906a-8812-90b4-f24a-6ee4ddc01321.png)

### Lighthouse v8のスコアウェイト
|Audit | Weight|
|--- | --- |
|First Contentful Paint | 10% |
|Speed Index |10% |
|Largest Contentful Paint | 25% |
|Time to Interactive | 10% |
|Total Blocking Time | 30% |
|Cumulative Layout Shift | 15% |

※ 参照：[web.dev](https://web.dev/performance-scoring/?utm_source=lighthouse&utm_medium=lr#lighthouse-8)

### LCPとは
> Largest Contentful Paint (LCP) 指標は、ビューポート内に表示される最も大きい画像またはテキスト ブロックのレンダリング時間を、ページの初期読み込み開始タイミングと比較してレポートします。

※参照：[web.dev](https://web.dev/lcp/#lcp)

## 改善後のシミュレーションする
[Lighthouse Scoring Calculator](https://googlechrome.github.io/lighthouse/scorecalc/)の画面でLCPが何秒改善したらスコアが何点アップするのか確認できます。
今回目標が75点だったのでLCPを6.0secから2.5secに改善出来れば75点に到達する事がわかります。

## LCPが遅い原因を調査する
ChromeのDevToolのPerformanceタブで確認するとLCPが遅延読込している画像になっている。
![スクリーンショット 2021-12-03 10.22.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348159/8e3feda9-7952-9989-b7f8-02e877c0adec.png)
![スクリーンショット 2021-11-11 18.05.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348159/c43f3dd0-df82-cfe7-435e-4b31e7cd9027.png)

対象のプロダクトはWordPress環境で構築されており、[lazysizes](https://github.com/aFarkas/lazysizes)を使用して画像を全て遅延読込していた。
基本的にはファーストビューは遅延読込されないがHTML上では以下の記述になっておりlazysizes読込後に描画される為、描画が遅れてしまっていることが原因の可能性大。。。

```html
<img data-src="https://example.com/path/sample-image.png" alt="タイトル" class="lazyload">
```

## 改善後
lazysizesではなく別の手法にて遅延読込を実装した結果。
LCPの表示がだいぶ早くなりました。
![スクリーンショット 2021-12-03 10.31.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348159/fb799103-a552-03e9-903c-f1b7c32aeb86.png)
数値流体力学2～スカラー移流方程式(cの符号がわからない場合)～

##はじめに
　数値流体解析(CFD)の基礎シリーズの第2回です。前回は、スカラー移流方程式について数値計算をしてみました。今回はその続きです。よかったら、[前回の記事](https://qiita.com/vardia/items/3bc2b21e75a4b7557897)も見てみてください。
　何か間違いなどありましたら気軽にコメントしてください！

##シリーズ構成
####1. スカラー移流方程式
　1.1 [輸送速度が正の場合](https://qiita.com/vardia/items/3bc2b21e75a4b7557897)
　1.2 [輸送速度の符号が不明の時の線形問題](https://qiita.com/vardia/items/0c20be629343492d0cb8)
　1.3 [数値流束を利用した方法](https://qiita.com/vardia/items/a48b7dc9a6431e0847ed)
　1.4 [輸送速度が未知量であるとき(Burgers方程式)](https://qiita.com/vardia/items/2c2c6471deea37e11a98)
　1.5 [多次元への拡張(2次元スカラー移流方程式)](https://qiita.com/vardia/items/473192fadac80baefa89)
　1.6 [実践的な計算法(TVD方程式)](https://qiita.com/vardia/items/26d01a78adcdf2df125a)
####2. 移流方程式の時間積分法
　2.1 時間陽解法について
　2.2 時間陰解法について(Crank-Nicholson法、近似LU分解)
####3. 2階偏微分方程式
　3.1 1次元熱伝導方程式の数値計算法
　3.2 楕円方程式の数値計算法
####4. 圧縮性流れの数値計算法
　4.1 オイラー方程式の数値計算法
　4.2 MacCormack法による数値計算法
　4.3 TVD法による数値計算法

##今回扱う内容
　今回は、輸送速度の符号がわからない場合のスカラー移流方程式の数値計算法について考えていきます。基本的な考え方を扱った後、具体的にプログラムに落とし込んでいきます。

##基本的な考え方
　輸送方程式の輸送速度があらかじめわからないとき、以下の分岐をプログラムに組み込む必要があります。

```math
\left \{
\begin{array}{l}
q^{n+1}_{j}=q^{n}_{j}-c\Delta{t}\frac{q^{n}_{j}-q^{n}_{j-1}}{\Delta{x}}　(c>0) \\
q^{n+1}_{j}=q^{n}_{j}-c\Delta{t}\frac{q^{n}_{j+1}-q^{n}_{j}}{\Delta{x}}　(c<0) \\
\end{array}
\right.
```
これをプログラムに落とし込むにあたって、If分の分岐を考えると思います。しかし、If文を使ってしまうと計算の過程でコンピュータに負担がかかってしまうのです。なので、以下のような評価式を使うことで解決します。ここでは、一次精度風上法を用いています。

```math
q^{n+1}_{j}=q^{n}_{j}-\Delta{t}\Big(\frac{c+|c|}{2}\frac{q^{n}_{j}-q^{n}_{j-1}}{\Delta{x}}+\frac{c-|c|}{2}\frac{q^{n}_{j+1}-q^{n}_{j}}{\Delta{x}}\Big)
```

##プログラムへの実装

```python
import numpy as np
import matplotlib.pyplot as plt

#初期値の設定、空間初期分布の設定
c = -1
dt = 0.05
dx = 0.1
jmax = 21
nmax = 6

x = np.linspace(0, dx*(jmax-1), jmax)

q = np.zeros(jmax)
for j in range(jmax):
    if (j < jmax/2):
        q[j] = 0
    else:
        q[j] = 1

def do_computing(x, q, c, dt, namax, dx):
 plt.figure(figsize=(7,7), dpi=100)
 plt.rcParams["font.size"] = 22

#初期分布の可視化
 plt.plot(x, q, marker='o', lw=2, label='n=0')

 for n in range(1, nmax+1):
    qold = q.copy()
    for j in range(1, jmax-1):
        c2 = (c + abs(c)) / 2
        c3 = (c - abs(c)) / 2
        q[j] = qold[j] - dt * (c2 * (qold[j] - qold[j-1]) / dx + c3 * (qold[j+1] - qold[j]) / dx)

    #各ステップの可視化
    if n % 2 == 0:
        plt.plot(x, q, marker='o', lw=2, label=f'n={n}')

#グラフの後処理
 plt.grid(color="black", linestyle='dashed', linewidth='0.5')
 plt.xlim([0,2.0])
 plt.ylim([0,1.2])
 plt.xlabel('x')
 plt.ylabel('q')
 plt.legend()
 plt.show()

do_computing(x, q, c, dt, nmax, dx)
```
プログラム結果は、

<img width="65%" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329221/2cca5583-91fb-986e-93fd-9c19a11ea4c4.png">
しっかり計算結果が表れていることがわかりますね。なお、一次精度風上法における数値計算は[前回の記事](https://qiita.com/vardia/items/3bc2b21e75a4b7557897)で詳しく解説しています。

##まとめ
　今回は、輸送速度の符号がわからないときの移流方程式の数値計算法を考えました。**If文を使うのではなくて、絶対値を使った処理をすることでコンピュータに負担をかけずに計算を行うことができます。**

##参考文献
藤井孝蔵、立川智章、Pythonで学ぶ流体力学の数値計
form内でbuttonのtype属性を書いていなかったら、挙動ではまった話

この記事は[ビビッドガーデン Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vivid-garden)の7日目です。
こんにちは、食べチョクを開発している遠藤です。

今回はbuttonのtype属性を書いていないおかげで、謎の挙動にハマりました。

## 発生していた事象

何が起きていたかを話します。

弊社では、Reactを利用して管理画面を作成しています。
サンプルコードは、以下の通りです。


```js
const handleOnChange = (e) => {
　　...
}

const handleOnClick = (e) => {
  alert("Enter押すとこいつが反応するんだよな")
}

...
...

<form>
  <input type="text" ... onChange={handleOnChange} />

  ...
  ...
  ...

  <button ... onClick={handleOnClick}>
    追加する
  </button>
</form>
 
```

text型でEnterキーを押すとなぜか、`handleOnClick`呼ばれてしまいます🤔
正直全然見当違いの場所でイベントが発火しているために、`e.preventDefault()`で対応する方針で行っていましたが、どうもうまくいかなかったです。
調べていくと、本家のReactでもIssueが報告がされていました。

https://github.com/facebook/react/issues/3907

## 原因

さて、原因です。

buttonのtype属性を書いていないために起こる挙動が原因です。
ここからはリファレンスを参照します。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/button

>type
このボタンの既定の動作です。以下の値が指定可能です。
submit: このボタンはフォームのデータをサーバーへ送信します。これはこの属性が <form> に関連付けられたボタンに指定されていない場合、またはこの属性が空であったり不正な値であったりした場合の既定値です。
reset: このボタンはすべてのコントロールを初期値に初期化します。 <input type="reset"> と同様です。 (この動作はユーザーを困らせる傾向があります。)
button: ボタンには既定の動作がなく、既定では押されても何も行いません。この要素のイベントを待ち受けし、イベントが発生すると起動されるクライアント側スクリプトを設定することができます。

そう、この挙動です。

>submit: このボタンはフォームのデータをサーバーへ送信します。これはこの属性が <form> に関連付けられたボタンに指定されていない場合、またはこの属性が空であったり不正な値であったりした場合の既定値です。

つまり、form内の場合は、Enterを押した場合の送信ボタンになります。

>ボタンがサーバーにデータを送信するためのものでない場合は、 type 属性を button に設定することを忘れないでください。さもないと、フォームデータを送信して (存在しない) レスポンスを読み込み、文書の現在の状態を破棄してしまうおそれがあります。

注意文があるぐらい、罠な挙動になっています。

## 対策

弊社では同じ過ちを繰り返さないためにも、対策を行います。
今回はeslintでbuttonのtype属性がない場合は、指摘するルールがあったので、それを適用しました。

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89075/ac609ac7-5d08-c89e-8160-fa9a39528c47.png)


https://github.com/yannickcr/eslint-plugin-react/blob/v7.27.0/docs/rules/button-has-type.md

これでこの話は終わりです。

## 最後に

弊社では一度問題が発生したら、対策していきます。
問題が発生したら、解決策を練ることが好きな人のための求人があります。
よろしくお願いします！！！

https://www.wantedly.com/projects/636843
AirtableのデータをGatsbyJSへ引っ張りGraphQLスキーマとして処理できるようにする

Airtableを簡単にGraphQLで扱えたらなーと考えていたのですがGatsbyJSを使えば簡単にできました。

## gatsby-source-airtable を使う
gatsby-source-airtable という便利なプラグインがあるのでそれをインスト。

```
yarn add -D gatsby-source-airtable gatsby-source-filesystem
```

gatsby-source-filesystem も入っていないと怒られるので入れておきます。

プラグイン詳細：https://www.gatsbyjs.com/plugins/gatsby-source-airtable/?=airtable

### 成果物
まずは成果物から。下は２つのシート（テーブル）を引っ張るオプション指定。

```gatsby-config.js
plugins: [
// 省略
    {
      resolve: `gatsby-source-airtable`,
      options: {
        apiKey: process.env.AIRTABLE_API_KEY,  
        concurrency: 5, 
        tables: [
          {
            baseId: process.env.AIRTABLE_SHEET_ID, // テーブル専用ドキュメントページのURLの一部がID名となっている
            tableName: `TestData`, // 対象のシート名
            queryName: `TestGraph`, // 複数のシートを引っ張るかつキーが被るなら欲しい
            separateNodeType: true, 
          },
          {
            baseId: process.env.AIRTABLE_SHEET_ID,
            tableName: `HogeData`,
            queryName: `HogeGraph`,
            separateNodeType: true, 
          }
        ]
      }
    }
// 省略
]
```
上のようにプラグインのオプションを設定してあげるとこのようにAirtableのスキーマが生成されています。

![スクリーンショット 2021-12-03 12.15.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/5ea1667e-5e27-dbdf-4666-454d758fdbcd.png)


### 最小構成

```gatsby-config.js
plugins: [
// 省略
    {
      resolve: `gatsby-source-airtable`,
      options: {
        apiKey: process.env.AIRTABLE_API_KEY,  
        concurrency: 5, 
        tables: [
          {
            baseId: process.env.AIRTABLE_SHEET_ID, // テーブル専用ドキュメントページのURLの一部がID名となっている
            tableName: `TestData`, // 対象のシート名
          }
        ]
      }
    }
// 省略
]
```
これだけでも引っ張ってこれます。
allAirtable, airtable が GraphiQL上で生成されているので確認できるかと。できていない場合は設定ミスなどが考えられます。

```
          {
            baseId: process.env.AIRTABLE_SHEET_ID,
            tableName: `TestData`,
            queryName: `TestGraph`, // +追加
            separateNodeType: true,  // +追加
          },
```
ここでオプションを追加してみました。

queryNameはGraphQL上に edges/node/queryName というディレクトリで設定した名前を引っ張ってこれます。複数テーブルが混在する場合にqueryName別に引っ張ってこれるオプション。

separateNodeTypeは "airtableAll任意の名前(queryNameで指定した文字)" でスキーマをそれぞれ生成してくれます。
それぞれのテーブル名の間でデータを完全に分けて扱う場合は一緒に使うと便利です。
Databricksのdisplayメソッドでデータプロファイリングをサポートしました

Databricksノートブックでデータフレームを表示する際に利用する`display`メソッドで、簡単にデータの傾向を把握できるデータプロファイリングをサポートしました。

:::note
**注意**
本機能はDatabricksランタイム9.1LTS以降が必要となります。
:::

Databricksランタイム9.1以降が稼働しているクラスターでdisplayメソッドを実行してデータフレームを表示すると**Data Profile**タブが表示されます。

```py:Python
import pyspark.pandas as ps
df = ps.read_csv("/databricks-datasets/Rdatasets/data-001/csv/ggplot2/diamonds.csv")
display(df)
```

**Data Profile**タブをクリックすると、対象データフレームの傾向や統計情報をクイックに確認することができます。
![](https://docs.databricks.com/_images/data-profile.png)

- 数値カラムとカテゴリ変数の特徴量は別々のテーブルに表示されます。
- タブの上の**Sort by**で特徴量をソートすることができます。
- 一番右のChartカラムの**Chart to show**でヒストグラム(**Standard**)あるいはQuantilesを選択することができます。
- チャート拡大するには**expand**をチェックします。
- 対数表示にするには**log**をチェックします。
- チャートの上にマウスカーソルを移動すると詳細情報を確認することができます。

:::note
**注意**
本機能は`display(df)`で動作します。`df.display()`ではサポートされていません。
:::

詳細は[マニュアル(英語)](https://docs.databricks.com/notebooks/visualizations/index.html#df-profile)を参照ください。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)
AWS認定 デベロッパーアソシエイト(DVA)に合格した

#受験動機
* 2019/10にSAA、2021/4にSOAを取得していたのでアソシエイト三冠を達成するため

#使用した機材について
* [AWS WEB問題集で学習しよう](https://aws.koiwaclub.com/)
    * おなじみkoiwaclubのオンライン問題集
    * SAA、SOAに比べるとちょっと問題数は少なめ？ですが、本試験との難易度は同じぐらい
    * このサービスの本試験モードで満点が取れれば大抵は合格できる
* ポケットスタディ AWS認定 デベロッパーアソシエイト
    * 内容はとても良い、解説も丁寧で誤植もない
    * 本編パートの理解度クイズはやや細かすぎる感じはあるが、冒頭の参考問題と巻末の模擬試験の内容はよく練られている
    * 模擬試験でほぼ満点がとれれば絶対合格できる
* [whizlabs.com](https://www.whizlabs.com/)
    * 以前SAA取得後にセールでSOA,DVAのオンライン講座を購入していたので、知識範囲を広げるために再開
    * しょっちゅうセールをやっているのでタイミングを狙えば安く入手できる、利用期限もないし問題集だけ買えるのは便利(解説動画は英語出来る人以外は不要)
    * Chromeの翻訳拡張機能オプションを連動させれば、基本日本語化して利用できる
    * 解説パートのクイズはだいぶ難しく、細かいところを聞いてくる問題が多め
    * クイズの問題順番と選択肢順番がシャッフルされないので、視覚的に答えを記憶してしまいがち（この問題だと確か正答はこの一番長いやつ…みたいな）

#受験まで
2020/05~

* SOAに合格したのでひとまず「ポケットスタディ AWS認定 デベロッパーアソシエイト」を購入
* ざっと中身を見てみるが、SAAと重なっている領域(インフラ系)とそれ以外(開発系)で自分の知識量の差が激しい…
* Lambda + APIgateway構成は業務で構築したことがあるので多少分かるけど(Lambdaの環境変数やエイリアス、APIgatewayのステージなど)それ以外はなんとなく概念が分かる程度
* とりあえず時間あるときにパラパラと開いてみる

2020/06~

* 転職イベント対応でしばらく勉強は放置

2020/08~

* 新しい職場で勤務開始
* 前職はほぼリモート勤務でしたが、まさかのリアル出勤勤務で生活リズムが激変
* 通勤時間で「ポケットスタディ AWS認定 デベロッパーアソシエイト」を開いて勉強を始める
* 参考書は読みやすいけど、理解度クイズはどうしてもやりづらい(問題ページと解答ページが分かれているから)なんとなくスルーしていた

2020/09~

* 参考書でそれなりにスコープ範囲のサービスは頭に入った（ような気がした）ので「AWS WEB問題集で学習しよう」の利用を開始
* ライセンス期限から逆算して、試験日ターゲットを11月中旬ぐらいに決める(AWS試験は落ちると二週間再受験できないので、もし落ちても期限内に収まるように調整)

2020/10~

* 引き続き通勤時間でオンライン問題集をひたすらやる
* だいたい自分の苦手なところが分かってくる(IAM権限、Cognito、Cloudformation…) 

2020/11(前半)

* オンライン問題集でほぼ満点が取れるようになったので、模擬試験をやってみる
* なんと**55点**しか取れず衝撃を受ける！
* 模擬試験が妙に難しかったというのもあるが、「間違えた(理解できていなかった)」よりも「そのような機能オプションの存在を知らなかった(知識不足)」なのが点数が低い原因
* オンライン問題集の「出てくる問題」範囲だけだと足りないかも？と思い、以前購入したwhizlabsでのオンライン講座を再開
* そもそもポケットスタディの冒頭参考問題をちゃんとやっても半分ぐらいしか正答出来てないので、本編の再読と理解度クイズにちゃんと取り組む

2020/11(後半)

* ポケットスタディの本編＆理解度クイズがちゃんと頭に入ってくるとだいぶ違う、今まで曖昧だったリソースポリシーとアイデンティティポリシーの違いとかようやく理解できた気がする
* whizlabsの解説パート理解度クイズはちょっと細かすぎるのでさらっと見ておくだけで充分、模擬試験クイズは本番よりやや難しい？レベルなのでわりと実用的
* 合格ラインはこれでクリア出来た？ような気がしたので、本試験を申し込む
* 試験前日はオンライン問題集を全通しで一周して、whizlabsの本試験モードをやっておく

#受験当日
* 前回と同じCBTS歌舞伎座テストセンターで受験、日曜なので受験者は多かった
* フラグ立てつつ65問回答までで約1時間、その後フラグ問題の検討で10分、見直し1回で10分ぐらいの時間消費
* フラグ立てた数も確か3~4個程度、それも二択までは絞り込みができて本文解釈からどちらにすべきか…という感じ
* インフラ寄り(SAA,SOA)の問題が1/3ぐらい、DVAスコープ範囲でも簡単なのが1/3、難易度それなりが1/3という印象
* 多くの人がレポートしているが日本語があやしいところが結構ある、公式では「デプロイ」で用語は統一されているのに、問題解答では「展開」と書かれていたりするところがあるので、疑わしいと思ったら英語に切り替えて確認

#受験結果
* 無事合格(スコア959)
* 模擬試験に比べるとかなり簡単だった

#要点
* 自分のようなインフラエンジニア出身の場合、開発系のサービスはイメージを把握するのが難しい
* 参考書を読んでなんとなく理解したつもりになっても、手を動かした事がないと応用問題では正答が分からない
* たまたま業務でlambda + APIgatewayの構築経験があった事がすごく役立った、ここはハンズオンでも時間かからないので是非自分でやっておいた方が良い
* DynamoDBのRCU/WCUに関する問いは必ず出る、間違えると悔しいのでボーナス問題だな～と思うぐらいまで理解しておくべき
* 模擬試験はやたらと難しいけど、本試験はずっと簡単（これは今でも謎、DOPの問題が混じっているとか？）

#今後
* SAPに挑戦するしかないのか…？
* SAPは合格記を読んでも「一度落ちた」とか「難易度桁違い」とか厳しいワードばかりで尻込みしてます(w
Alamofire+Codableをasync/awaitを使ってリクエストする

Swiftにも5.5でついにSwift Concurrencyが導入されたので一番有用そうなAPI取得周りをasync/awaitで記述できるようにしてみます。
今回はSwiftの通信ライブラリとしてスタンダードであろう[Alamofire](https://github.com/Alamofire/Alamofire)+[Codable](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)を環境として用いて実装しています。

（そのうちAlamofire公式にもasync/awaitで受け取れる口は出来るとは思いますが現時点ではiOS15以降が要求されるのでしばらくは入らないかと思います）

## AlamofireのExtensionを記述する

Alamofireはクロージャで結果を受け取とれるので、これをasync/awaitに変換します。
旧来のクロージャ構文を変換するには[withCheckedThrowingContinuation](https://developer.apple.com/documentation/swift/3814989-withcheckedthrowingcontinuation)を利用します。

```swift:DataRequestExtension.swift
import Foundation
import Alamofire

extension DataRequest {

    func publish<T>(_ type: T.Type) async throws -> T where T : Decodable {
        try await withCheckedThrowingContinuation { continuation in
            self.response { response in
                switch response.result {
                case .success(let element): do {
                    let decodedResponse = try JSONDecoder().decode(type, from: element!)
                    continuation.resume(returning: decodedResponse)
                } catch {
                    continuation.resume(throwing: error)
                }
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
        }
    }
}
```
`continuation.resume`を呼ぶことで呼び出し元へ結果を返して処理をすすめる事ができます。

## 実際に呼び出してみる

あとは生やしたメソッドを呼び出すだけでasync/awaitへの変換は完了します。
普段どおりCodableを実装したstructを用意してください。

```swift:GithubUser.swift
import Foundation

struct GithubUser: Codable, Equatable, Identifiable {
    let id: Int
    let name: String
    let htmlUrl: String
    let avatarUrl: String

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
        case htmlUrl = "html_url"
        case avatarUrl = "avatar_url"
   }
}
```

その上で、Alamofireのリクエストクラスから作成した`publish()`を呼びだしてください。

```swift:GithubApi.swift
struct GithubApi {
    func fetchUser(userName: String) async throws -> GithubUser {
        try await AF.request("https://api.github.com/users/\(userName)").publish(GithubUser.self)
    }
}
```

## withCheckedThrowingContinuationとwithCheckedContinuation

上記ではクロージャの変換用に[withCheckedThrowingContinuation](https://developer.apple.com/documentation/swift/3814989-withcheckedthrowingcontinuation)を紹介しましたが、
これとは別に [withCheckedContinuation](https://developer.apple.com/documentation/swift/3814988-withcheckedcontinuation)という関数もあります。
こちらはErrorをThrowすることがない場合に利用できます。こちらの関数ではtryの指定が不要になるので処理が失敗しうるかどうかで使い分けてください！
Terraform 及びTerragruntをDocker 上で実行する

# なぜDocker上でTerraformを動かしたいのか？
- 環境やバージョンの違いで動かない問題が起きなくなるので保守性があがる。
- Terraformの定義ファイル内でバージョン固定しなくてもよくなる*もちろん固定してもいいが余計なエラーに悩まされることがなくなる。

# 作るもの
1. Dockerfile
2. docker-compose.yml
3. makefileオプション
の3つを用意

## Dockerfile
```dockerfile
FROM python:3.6

ARG pip_installer="https://bootstrap.pypa.io/get-pip.py"
ARG AWSCLI_VERSION
ARG TERRAFORM_VERSION
ARG TERRAGRUNT_VERSION

# install aws-cli
RUN pip install awscli==${AWSCLI_VERSION}

# install command.
RUN apt-get update && apt-get install -y less vim wget unzip jq

# install terraform
RUN wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
  unzip ./terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin/ \
  && rm ./terraform_${TERRAFORM_VERSION}_linux_amd64.zip

# install terargrunt
RUN wget https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64 \
  && chmod +x ./terragrunt_linux_amd64 \
  && mv ./terragrunt_linux_amd64 /usr/local/bin/terragrunt

# COPY terraform related files
COPY ./terraform /terraform

```
## docker-compose.yml
```docker-compose.yml
version: "2"
services:
  terraform:
    container_name: "terraform"
    build:
      context: ../ #Dockerfileのパスを指定する
      dockerfile: Dockerfile
      args:
        AWSCLI_VERSION: "1.16.168"
        TERRAFORM_VERSION: "1.0.1"
        TERRAGRUNT_VERSION: "0.31.0"
    env_file: ./.env
    working_dir: /terraform
    tty: true
```

さらにmakefileを用意

```makefile
.PHONY: build plan apply destroy

default:build

build:
	@docker-compose build

plan: build
	@docker-compose run --rm terraform terragrund plan

apply: build
	@docker-compose run --rm terraform terragrunt apply

destroy: build
	@docker-compose run --rm terraform terragrunt destroy
```

これでmake apply とかでTerraformの適用ができるようになります。

# 参考記事
https://qiita.com/Tocyuki/items/0cb655e6357d9bf0c40f
【typescript, React】react-hook-formでフォームを作るために必要最低限なこと

#概要
react-hook-formを用いた、フォームの作り方を説明します。
validationなどの設定は置いておいて、本当に必要最低限のフォームを作るために必要なことをメモします。
言語はtypescriptを使用しています。
#手順
1. react-hook-formのインストールとimport
1. 取得したいデータの型を定義する
1. registerとhandleSubmitをuseFormを用いて定義する。
1. handleOnSubmitを定義する。（関数名は他の名前でOK）
1. HTMLの記述

順を追って説明していきます。

#具体的な実装
##1.react-hook-formのインストールとimport
ターミナルを立ち上げて以下のコマンドを入力してください。

```terminal:terminal
$ yarn add react-hook-form
```

その後react-hook-formを使いたいファイル内でimport文を書きます。

```typescript
import { SubmitHandler, useForm } from 'react-hook-form'
```
ここでimportしたものは後で使用します。重要なのはuseFormです。

##2. 取得したいデータの型を定義する
（例）

```typescript
type ValuesType = {
  name: string
  age: number
  phone: number
}
```

上記のようにして入力してもらいたデータの型を定義しましょう。
`フォームの値につける変数名:そのデータの型`
のようにして定義していきます。

##3. registerとhandleSubmitをuseFormを用いて定義する。
（例）

```typescript
const { register, handleSubmit } = useForm<ValuesType>({
    mode: "onSubmit",
    reValidateMode: "onChange",
});
```

このようにしてregisterとhandleSubmitを定義します。これらの変数の名前は変更しないようにしましょう。
（もちろんmodeやreValidateModeには様々な変数を割り当てることができますし、それ以外にも多くのオプションを利用可能です。詳しくは公式のドキュメントが詳しいと思います。）

##4. handleOnSubmitを定義します。
（例）

```typescript
const handleOnSubmit: SubmitHandler<ValuesType> = (data) => {
    console.log(data)
}
```

最初にimportしたSubmitHandlerを型宣言に用います。
今回はコンソールに取得したデータを表示させているだけです。
（apiにそのデータを渡したいときは、ここでaxiosなどを用いてPostなどを行うことができます。）

##5. HTMLの記述
（例）

```react
return (
        <form
          onSubmit={handleSubmit(handleOnSubmit)}
        >
            <input
            type="text"
            {...register('name')}
            />
            <input
            type="number"
            {...register('age')}
            />
            <input
            type="number"
            {...register('phone')}
            />
            <input type="submit" />
        </form>
)
```

このようにしてフォームを作成します。inputタグの中の```{...register('name')}```という部分が肝で、これによって入力されたデータがどのデータに対応しているのかを記述しています。

#まとめ
今回は必要最低限のreact-hook-formの使い方を説明しました。
個人的にはここまでくるのが、一番大変だったので記事にしました。
あとは色々なオプションについて公式ドキュメントなどを参照しながら、勉強してみてください！