OmniAICreator/Qiita-1.07M huggingface dataset:
[Unity]タッチ情報のdeltaPositionを使って動かしたオブジェクトの移動量が合わなかった場合の対応

## はじめに
Unityのタッチ操作で画面上のオブジェクトなどを動かす場合、オブジェクトの動かし方としては大きく

- タッチの位置を使ってオブジェクトの位置を変える
- タッチの移動量を使ってオブジェクトを移動させる

があるかと思います。
後者の場合、[Touch.deltaPosition](https://docs.unity3d.com/jp/460/ScriptReference/Touch-deltaPosition.html)を取得して毎フレームの移動量を決めたりすると思いますが、最初思ったように動かなかったので備忘録です。

## 結論
- タッチ情報の更新頻度と毎フレーム処理(`Update()`)のタイミングが異なると移動量がずれる
- `Touch.deltaPosition`は`Touch.deltaTime`で割って速度にして使う


##詳細
### 起きたこと
以下のようなスクリプトを作成し、アタッチしたオブジェクトがタッチ移動に合わせて動くようにしました。

```csharp:移動量がずれるサンプル
// Update関数
private void Update()
{
    OnTouchOperation();
}

// タッチ移動量に応じてオブジェクトを移動させる処理
private void OnTouchOperation()
{
    // 移動用タッチの情報取得(とりあえずシングルタッチのみ対応)
    if (0 < Input.touchCount)
    {
        Touch touchPos = Input.GetTouch(0);

        // 移動していたら移動量を取得してオブジェクトを移動
        if (TouchPhase.Moved == touchPos.phase)
        {
            this.transform.Translate(touchPos.deltaPosition.x, touchPos.deltaPosition.y, 0.0f);

            // これでもよい
            // this.transform.position = (Vector2)this.transform.position + touchPos.deltaPosition;
        }
    }

    return;
}
```
※余談：上記コードで各種`this`は省略できますが、個人的には明示的に書いておきたい人です

結果：
実際にタッチで移動した距離よりも、オブジェクト(ポインタ)が移動した距離の方が長くなりました。
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474074/37186d52-7d56-4524-dccc-f5ad59213650.jpeg)


### 原因
タッチ情報が取得されるタイミングとUpdate関数のタイミングが違い、Update関数の方が高頻度でTouch.deltaPositionを取得してオブジェクトを動かしていたためです。
逆にUpdate関数の方が頻度が低い場合は、タッチの移動量よりもオブジェクトの移動量の方が少なくなっていたはずです。
大した内容ではないですが一応図解を…
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474074/2c52d0bb-b1d5-557e-6bb6-5dde0d2630b1.jpeg)

### 対策
タッチ情報の取得タイミングは`Touch.deltaTime`として取得できるので
タッチ情報の移動量を扱う際は`Touch.deltaPosition`を`Touch.deltaTime`で割ることで、移動速度を計算します。
それに`Update()`で`Time.deltaTime`を乗算することで、そのフレームとしての移動距離が出せます。
(少しは誤差あるかもしれませんが…)


```csharp:移動量がずれないサンプル
// Update関数
private void Update()
{
    OnTouchOperation();
}

// タッチ移動量に応じてオブジェクトを移動させる処理
private void OnTouchOperation()
{
    // 移動用タッチの情報取得(とりあえずシングルタッチのみ対応)
    if (0 < Input.touchCount)
    {
        Touch touchPos = Input.GetTouch(0);

        // 移動していたら移動量を取得してオブジェクトを移動
        if (TouchPhase.Moved == touchPos.phase)
        {
            Vector2 moveDist = (touchPos.deltaPosition / touchPos.deltaTime) * Time.deltaTime;
            this.transform.Translate(moveDist.x, moveDist.y, 0.0f);

            // これでもよい
            // this.transform.position = (Vector2)this.transform.position + moveDist;
        }
    }

    return;
}
```

## 最後に
今回のような手間を加えなくとも、オブジェクト移動量などは何らかの係数をかけてチューニングすれば解決することも多いと思います。
が、タッチ情報取得や`Update()`のフレームレートは環境やその時の負荷で変わることもあると思うので、思わぬ動作に繋がる可能性があると考えています。
気になる動作はできる限り原因を把握して手を打っておきたいものです。

以上です。
【エラー備忘録】translation data {:title=>"タイトル", :◯◯=>"△△"} can not be used with :count => 1. key 'other' is missing.

# エラーの内容
<img width="1011" alt="image.png (39.6 kB)" src="https://img.esa.io/uploads/production/attachments/14883/2020/04/16/76576/293de3e6-8c69-4afc-a58d-d457b1200cd3.png">

>I18n::InvalidPluralizationData in ReviewsController#create
>translation data {:title=>"タイトル", :body=>"本文"} can not be used with :count => 1. key 'other' is missing.

と表示される。

# やりたかったこと
rails-i18nを用いて、reviewモデル内の下記の属性を日本語表示させようとした。

- title
- body


# 手順
1. ```gem "rails-i18n"```を```bundle install```
2. application.rbに```config.i18n.default_locale = :ja```を設定
3. ja.yamlを作成

# 原因
ja.yamlの記述に問題があった。

## 誤った書き方
```ja.yaml
ja:
  activerecord:
    models:
      review:
        title: タイトル
        body: 本文
```
```models: ```の次の項目のmodel名```review```に続けて属性名を記述していた。
## 正しい書き方
```ja.yaml
ja:
  activerecord:
    models:
      review:
    attributes:
      review:
        title: タイトル
        body: 本文
```

リロードを行うと、正しく日本語で表示された。（バリデーションを日本語で表示できた）

<img width="1103" alt="image.png (33.0 kB)" src="https://img.esa.io/uploads/production/attachments/14883/2020/04/16/76576/0806ba7e-9c4d-4766-8f55-a83ab6ec01ac.png">

# 参考
[[初学者]Railsのi18nによる日本語化対応](https://qiita.com/shimadama/items/7e5c3d75c9a9f51abdd5)
「うちでヨガしよう」ヨガポーズがあっているか判定してくれるLINE bot～Node.jsに読み込む～

#概要
　コロナウイルの影響で外出自粛、在宅ワークをされている方が多いと思います。普段よりも運動量が減ってしまうので、何かできないかと考えてヨガを支援するLINE botを開発しています。

#やりたいこと
　機能としては、お題のポーズ写真がbotから送られてくるので、そのポーズをとった自分の写真をbotに送ると正しいポーズか判定してくれます。

　まずはLINE botアカウントだけ作りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/d3eca936-fa3e-5b97-8acb-ff95963d7390.png)


#実装
　学習モデルの作成は次の２つの記事で解説しています。
・ [Googleが提供しているサービス「Teachable Machine」でヨガポーズの学習モデルを作って遊んでみた](https://qiita.com/kmaepu/items/1350464b3940dfe6957e)
・[Teachable Machineで作成したモデルを使用する方法（webブラウザ編）](https://qiita.com/kmaepu/items/5adfa1532eaa66e9acc5)

　この記事ではNode.jsにTeachable Machineのライブラリをインストールする方法を記載してます。

必要なライブラリは次のコマンドでインストールできます。

```javascript

npm i @tensorflow/tfjs
npm i @teachablemachine/pose
```

で、次のようにrequireします。

```javascript
const tmPose = require('@teachablemachine/pose');
```

この記事のメイン部分はおわりにします。

が、このライブラりを使うにあたり、node-fetchが必要なようで、インストールしてrequireしてもNot defineエラーが出てしまいます....。

学習モデルをネットからロードする処理になっていそうなので、ローカルに落としてくると改善されそうです。
PytorchでDataFrameのデータをNNで訓練できる形に整形する

# 概要
ニューラルネットワークでデータを訓練させたり検証したりする際に、データを（データ群，ラベル）の形に整形する必要がありますよね。
MNISTやCIFAR10だと事前にこの形になっているんですが、手持ちのデータを使うときには自分でこの形にする必要があります。が、理解するまで時間がかかったのでこちらにしたためておきます。

尚、基本的な方法については英語でどんぴしゃ回答してる方々がいたため、英語が行ける方は以下の一連のやりとりをご覧ください。
[Convert Pandas dataframe to PyTorch tensor?](https://stackoverflow.com/questions/50307707/convert-pandas-dataframe-to-pytorch-tensor)

このQiitaでは文脈を補って少し詳しく解説しているので、適宜必要なところだけサク読みして下さい。

##データとラベルに手持ちのデータを分ける

###DataFrameからラベルを引っ張って来る(ラベルのコラム名：target)

```python
train_label = torch.tensor(train['target'].values)
```
引用：[Convert Pandas dataframe to PyTorch tensor?](https://stackoverflow.com/questions/50307707/convert-pandas-dataframe-to-pytorch-tensor)


###ラベル入りのDataFrameからデータ群を引っ張ってくる

```python
train_data = torch.tensor(X.drop('target', axis = 1).values)
```
引用：[Convert Pandas dataframe to PyTorch tensor?](https://stackoverflow.com/questions/50307707/convert-pandas-dataframe-to-pytorch-tensor)

ただしこれは、以下のようなエラーになることがあります。

```html:Error
TypeError: can't convert np.ndarray of type numpy.object_. The only supported types are: float64, float32, float16, int64, int32, int16, int8, uint8, and bool.
```

上に書いてある通り、np.ndarrayのタイプがobjectだと、torch.tensorに変換できません。
objectとは複数のデータタイプが混ざっているデータ型のことです。
そのためデータを、tensorの対応する上記のデータ型のいずれかに変換する必要があります。
例えば、float32の型に統一してみます。以下のように書き換えるとうまく作動します。

```python
train_data = torch.tensor(X.drop('target', axis = 1).values.astype(np.float32)) 
```
引用：[Convert Pandas dataframe to PyTorch tensor?](https://stackoverflow.com/questions/50307707/convert-pandas-dataframe-to-pytorch-tensor)

このデータ型は自分のデータに適したものを使って下さい。
データ型については[NumPyのデータ型dtype一覧とastypeによる変換（キャスト）](https://note.nkmk.me/python-numpy-dtype-astype/)が詳しいです。

###データ群のみのデータフレームをtensorに変換
あるいは最初からラベルの入っていないDataFrame(X_train)があれば，それをnp.arrayに変換してからtensorに変換します。

```python
train_data = torch.tensor(np.array(X_train.astype('f')))
```

##データを（データ群、ラベル）の形に整形

```python
from torch.utils import data
train_tensor = data.TensorDataset(train_data, train_label) 
```

torch.utils.data.TensorDatasetのクラスはtensorsを引数として、最初の次元に合わせてtensorsにindexをつけることによって、データをまとめてくれます。
そのため、当然ですがトレーニングさせるデータとラベルの数があっていないとエラーになります。
参考：[pytorch公式チュートリアル](https://pytorch.org/docs/stable/data.html?highlight=data%20tensordataset#torch.utils.data.TensorDataset)


以上です。
Vagrantで作成したVMのディスクを移動

Vagrantで作成したVirtural BoxのVMが使っているディスクを移動してみました。

容量が足りなくてディスクを別の物理ドライブに移す必要がありました。

VMの操作はVBoxManageコマンドを使います。

# Virutal BoxのVMのIDを調査

```
$ VBoxManage list vms
...
"multilib_default_1586961145829_6521" {a5eafd23-ea4a-4524-a29a-591078209f10}
```
IDとしては、multilib_default_1586961145829_6521 と a5eafd23-ea4a-4524-a29a-591078209f10 のどちらも使える。

# ディスクの場所を調べる。

```
$ VBoxManage showvminfo a5eafd23-ea4a-4524-a29a-591078209f10 |grep vmdk
SCSI Controller (0, 0): /home/hoge/VirtualBox VMs/multilib_default_1586961145829_6521/ubuntu-bionic-18.04-cloudimg.vmdk (UUID: eb3bae9a-08bb-4f9b-8ca4-c5eff8ceb335)
SCSI Controller (1, 0): /home/hoge/VirtualBox VMs/multilib_default_1586961145829_6521/ubuntu-bionic-18.04-cloudimg-configdrive.vmdk (UUID: 01dd4a7e-b70d-4f40-b61f-2d64fcc30333)
```

# 空ディスクを接続する

ディスクを取り外すコマンドは無いので、その代わりに空ディスクを接続すると、とりはずしたことになる。

```
VBoxManage storageattach a5eafd23-ea4a-4524-a29a-591078209f10 --storagectl "SCSI Controller" --port 0 --device 0 --type hdd --medium emptydrive
```

# ディスクの登録を解除する

```
VBoxManage closemedium disk "/home/hoge/VirtualBox VMs/multilib_default_1586961145829_6521/ubuntu-bionic-18.04-cloudimg.vmdk"
```

# ディスクを移動する。

```
mv "/home/hoge/VirtualBox VMs/multilib_default_1586961145829_6521/ubuntu-bionic-18.04-cloudimg.vmdk" to_dir
```

# 移動したディスクを登録して接続する

```
 VBoxManage storageattach a5eafd23-ea4a-4524-a29a-591078209f10 --storagectl "SCSI Controller" --port 0 --device 0 --type hdd --medium /mnt/hoge/vms/multilib/ubuntu-bionic-18.04-cloudimg.vmdk
```

# 確認する。

```
$ VBoxManage showvminfo a5eafd23-ea4a-4524-a29a-591078209f10 |grep vmdk
SCSI Controller (0, 0): /mnt/hoge/vms/multilib/ubuntu-bionic-18.04-cloudimg.vmdk (UUID: eb3bae9a-08bb-4f9b-8ca4-c5eff8ceb335)
SCSI Controller (1, 0): /home/hogeVirtualBox VMs/multilib_default_1586961145829_6521/ubuntu-bionic-18.04-cloudimg-configdrive.vmdk (UUID: 01dd4a7e-b70d-```

```
ネットワーク通信量をできるだけ減らす（Windows 10編）

### はじめに
新型コロナの影響でリモートワークを始めることになった方も多いかと思いますが、仕事中のネットワーク接続がなんだか遅いな、ということを感じる方もいるかもしれません。

NTT東日本が4月14日に掲載した [トラフィックデータ](https://www.ntt-east.co.jp/aboutus/COVID-19.html#traffic) では、

> 新型コロナウィルス感染拡大による影響が出る前の2月25日（火）週と比較し、平日は、昼間帯（9時～17時）で最大33%増加
> 同様に影響が出る前の2月29日（土）、3月1日（日）と比較し、土日は、昼間帯で最大9%増加

なのだそうです。グラフを見ると、平日の場合はもともとは朝4時から5時にかけてトラフィックが最小になった後、徐々に増加していき夜の21時(午後9時)から22時(午後10時)に最大になる動きをしていたようですね。最近は朝8時前から夕方の19時頃(午後7時)までトラフィックが上乗せされているようです。

土日は、もともとは朝5時に最小になった後、夕方の17時(午後5時)まで徐々に増加し、夕方19時まで若干下がった後、夜22時(午後10時)まで増加して最大になる、という動きをしていたようですね。最近は、昼11時頃から夜22時ごろまで若干上乗せされているようです。

まずはじめに、通信量を減らすために手軽にできる手段を３つ列挙してみます。

### ライブタイルをオフにする
スタートに天気やニュース、メールなどのアプリをピン止めしてライブタイルをオンにしていませんか？もし表示内容が自動で切り替わっていく必要がなければ、ライブタイルをオフにしてしまいましょう[^1]。もしアプリそのものが不要なら、アンインストールしてしまいましょう[^2]。
![00live_tile_off.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/21786d82-b9da-f84f-0c60-087a0ae965cf.png)

[^1]: アプリごとにオン・オフの切り替えが必要です。ライブタイルのオン・オフの設定がないアプリもあります。
[^2]: アンインストールできないアプリもあります。

### 自動更新や自動再生を止める
Microsoft Store でアプリの自動更新やビデオの自動再生を止めてしまいましょう。使う予定のないアプリが勝手にインストールされたりすることも防止できます。

![01store_auto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/d5f69ff6-2fe3-7da9-3dad-af015f3a5f7c.png)

アプリの更新は、席を外してほかのことをする際に Microsoft Store の「ダウンロードと更新」画面で行うことができます。
それぞれのアプリごとに画面右端の下向き矢印を選ぶこともできますし、「すべて更新」を選ぶこともできます。

![store_manual.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/69798904-b1f3-f55f-27c2-0b8170a77d38.png)

### 低い解像度を優先する
Windows の設定（歯車記号）の画面にて「アプリ」メニューを選び、ビデオの再生を低い解像度優先にしましょう。

![02video.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/74e3c263-ef89-4125-67fa-8586628d5f6f.png)

### そのほか、Windows 10 で可能な手段
セキュリティーおよび診断に関わってくるため、ここまですべきかどうかの判断は難しくなってきますが、下記のような手段もあります。

#### Windows のアップデートで使用される帯域幅を制限する
Windows 自身のアップデートでネットワーク回線が一時的に占有されてしまうことがあります。それを避けるためにWindows のアップデートで使用される帯域幅を制限することもできます。
その設定を行うには、Windows の設定画面にて「更新とセキュリティー」メニューを選びます。「配信の最適化」画面の下のほうにある「詳細オプション」リンクをクリックして、制限を設定するための画面を表示させます。
![haisin_saitekika.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/146f3cf6-733a-1caf-9a47-7a95957aba60.png)

![haisin_saitekika_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/a96646e1-b075-ad3a-6f77-20c95c89f8bf.png)

#### アクティビティ履歴をオフにする
Windows の設定画面にて「プライバシー」メニューを選びます。Microsoft へのアクティビティ履歴の送信をオフにします。

![activity_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/dd3cda12-ac99-1e5c-f767-876b9963c3b6.png)

#### 診断＆フィードバックのために送信するデータを限定する
同様に、診断データの種類を「完全」ではなく、「基本」にすることができます。

![activity_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27855/508cba14-fbd1-1367-363c-98857448c5fb.png)

### さらに減らすには
さらにトラフィックを減らすには、アプリケーションごとに設定していく必要があります。動画、音声および画像データを使用するアプリケーションが狙いです。[次の記事ではブラウザ](https://qiita.com/strysd/items/a10bfbe135f9a718c408)を取り上げてみます。
個人用と会社用のgithubのアカウントを統合した話

# はじめに
アカウント統合など何度も行う作業ではない。忘れたことにやってくるので書き置きしておく。

githubのアカウントを会社用と個人用に分けて使っていた。
会社用をメイン、個人用をサブという位置付けで、sshキーを２種類用意して

```bash:~/.ssh/config
Host github.com # メインアカウント(会社用)
  HostName github.com
  User git
  Port 22
  IdentityFile ~/.ssh/id_rsa
  TCPKeepAlive yes
  IdentitiesOnly yes

Host github.com.sub # サブアカウント(個人用)
  HostName github.com
  User git
  Port 22
  IdentityFile ~/.ssh/id_rsa_sub
  TCPKeepAlive yes
  IdentitiesOnly yes
```

基本的に会社用は意識せずに使って、サブのときだけ切り替えるという感じで運用していた。

しかし、GitHub利用規約には
>1 人の個人または 1 つの法人が複数の無料「アカウント」を保持することはできません (コンピュータアカウントも制御することを選択した場合、それは問題ありませんが、それはコンピュータの実行にのみ使用できます)。

と書いてあったりする。
何よりも切り替えが無い方が（精神的に）楽である。

やりたいことは、**会社アカウントに紐づいているorganizationやリポジトリを個人アカウントに紐付けし直して、個人アカウントのみで完結する**、ということだ。


# 切り替え作業
ココは会社にお願いしましょう。
その間に個別のリポジトリがあれば、個人アカウントにtransferしておきましょう。

# 切り替え作業後
全て紐付けし直された！さてローカルのリポジトリを確認してみよーとなると、

```bash
ERROR: Repository not found.
fatal: Could not read from remote repository.
```
となって、分かってはいるけど少し焦る。
.ssh/configに書いてあれば、あーそうか、と思い出すが、configに書いてない場合、デフォルトのid_rsaを読みに行ってしまうので要注意。

```bash
$ ssh -T git@github.com
Hi xxxx! You've successfully authenticated, but GitHub does not provide shell access.
```
確認すると会社アカウントの名前が表示される。

先ほどのconfigを修正。

```bash:~/.ssh/config
Host github.com # メインアカウント
  HostName github.com
  User git
  Port 22
  IdentityFile ~/.ssh/id_rsa_sub　←ココを書き換え
  TCPKeepAlive yes
  IdentitiesOnly yes
```
サブのHostを削除して、サブで使っていたIdentityFileをメインのところに上書きすればOK!(これはサブを残してメイン削除でもよい)

```bash
$ ssh -T git@github.com
Hi oooo! You've successfully authenticated, but GitHub does not provide shell access.
```
個人アカウント用のSSHキーに切り替わった。

あとは、

```text
remote.origin.url=git@github.com.sub:[organization]/[repository].git
↓
remote.origin.url=git@github.com:[organization]/[repository].git
```
.subが付いているところを削除すればOK。

もう一度ローカルのリポジトリを確認すると

```bash
ERROR: The xxx organization has enabled or enforced SAML SSO. To access this repository,
you must use the HTTPS remote with a personal access token or SSH with an SSH key and passphrase...
```
と表示される場合がある。
そうしたら、githubのSettingsから
![capture 2020-04-15 14.51.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286879/ae2a97d5-79d3-bb70-a222-7240695ab30c.png)
左メニューのSSH and GPG keysを選択し、Enable SSOまたはDisable SSOを押すと
![capture 2020-04-15 14.51.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286879/fc3ccda5-1d33-db2f-8d62-5a3838842544.png)
と表示されるのでAuthorizeを押す。

以上でエラーも消えて、何事もなかったかのように以前のままローカルリポジトリを使うことができた。

# 最後に
あとはgit configでnameとemailを適当に設定してください。
crypto-jsを使って簡単に暗号化・ハッシュ化する方法

# はじめに

今回、JavaScriptの暗号化用ライブラリ、「crypto-js」を使う機会があったので、

その際に得た知見をメモ、共有するために記事を執筆します！

それでは行きましょう！

# crypto-jsとは何か

crypto-jsのGitHubリポジトリは[こちら](https://github.com/brix/crypto-js)です。

用途としては、パスワードや何かしらの値を暗号化、ハッシュ化したいときに使用します。

READMEに書いてあるとおり、非常にシンプルに使用することができます。

。。が、ここで落とし穴が！

ドキュメントの通り、AESで暗号化しようとしたところ、いくつかハマりどころがあったので以下で解説します。

これからcrypto-jsを使おうとしている方、それから未来の自分に対してメモを残します笑

# AESで簡単に暗号化する

AESで暗号化するためには、まずcrypto-jsのパッケージをインストールしましょう。

```
npm i crypto-js
```

package.jsonにcrypto-jsが追加されていることを確認したら、

任意のファイルでインポートします。

```javascript
import crypto from 'crypto-js'
```

インポートできたら、あとは暗号化したい値にcryptoの暗号化メソッドを使うだけです。

```javascript
const encrypted = crypto.AES.encrypt('hogehoge', 'key')
console.log(encrypted.toString())
```

上記のコードでhogehogeという文字列を暗号化でき、最終的にtoString()で文字列として出力しています。

toString()しないとオブジェクトで出力されるので注意してください。

AESを使用した暗号化でハマるポイントとしては、

- 公式ドキュメントに書いてあるメソッドの使い方だと暗号化できない
- 暗号化したモノは、toString（）してやらないと暗号化した値を表示できない

などがありました。

また、encryptメソッドの引数は、第一引数に暗号化したい文字列、第二引数に鍵にする文字列を渡します。

keyに渡す値はdot.envなどで保持して使用する。。などの使用法が考えられますね！

ちなみに、復号化は下記のようなコードになります。

```javascript
const decrypted = crypto.AES.decrypt(encrypted, 'key')
console.log(decrypted.toString())
```

decryptメソッドで復号できます。

decryptメソッドの引数は、第一引数に暗号化した値（オブジェクトでも文字列でも良い）、第二引数に暗号化の際に使用したのと同じ鍵（文字列）です。

# （余談）暗号化とハッシュ化の違いとは？

今回は、AESで暗号化してみましたが、SHAを使えばハッシュ化も同じ要領で行うことができます。

では、暗号化とハッシュ化の違いとはなんなのでしょうか？

一言で言うと、

暗号化　→　復号する必要がある

ハッシュ化　→　復号する必要がない

と言うことです。

今回僕は暗号化した値を後々復号化する必要があったのでAESを使用して暗号化しましたが、

ユーザー登録に使うパスワードなどに関しては、ハッシュ化してDBに保存するのが一般的かと思います。

（パスワードはハッシュとして保存しておき、ログインする際などは入力した値を同じようにハッシュ化して、ハッシュ化した値同士で照合を行うため）

# あとがき

暗号化に関しては初めて触れました。

かなり難しい内容が盛り沢山な分野な気がするので、少しずつ勉強していきたいなあ。。
Git Hub ターミナル操作の流れ 〜チーム開発〜

チーム開発の時のGitHubでのターミナル操作の流れです。

#GitHubに自分の作業したデータを上げたい時

ローカルの作業ブランチから、GitHubのmasterブランチにデータを上げたい時に打つコマンド。
以下全てターミナルに入力。

######変更点を確認する

```
git status
```

######変更点をステージにあげる

```
git add -A
```
######""の中にコミットメッセージを記述する

```
git commit -m ""
```
######githubの自分のブランチに上がる

```
git push origin 自分のブランチ名
```

#####GitHubでの作業
プルリクエストを作成をする


###必ず一度自分のリモートブランチに上げる
もし自分のリモートブランチにあげずに、直接masterブランチに上げてしまったら、
プルリクエストを送る前に合体されてしまう。必ず自分のリモートブランチに上げよう。
その後プルリクエストを送って、他の人が確認してマージするようにする。



#GitHubのmasterブランチのデータを自分のローカルに取ってきたい時
######masterのブランチに移動する

```
git checkout master
```
######GitHubのマスターブランチの情報を自分のmasterブランチに落とす

```
git pull origin master
```

######作業するブランチに移動する


```
git checkout 自分のブランチ名
```
######作業するブランチに自分のマスターブランチのデータをマージする
（変更点だけ追加される）

```
git merge master
```

###必ず自分のmasterブランチにGitHubのデータを落とす
自分のmasterブランチにGitHubのデータを落としたあと、
自分の作業するブランチにマージする。
そうすることで、どこのブランチでコンフリクトが発生したかが分かる。

※商品ブランチ、注文ブランチなど機能ごとにブランチを分けて作業している時、どこのブランチでコンフリクトが起きたのかが分かるので修正しやすい。


####自分のブランチに直接pullしたら？？
コンフリクトが起きた時に自分が作成したデータを消さないといけなくなるかも。
pullする時は必ずローカルのmasterブランチにしよう。


#プルした時にコンフリクトが起きた時
テキストエディタでコンフリクトが起きた箇所を修正する。
その後自分の作業ブランチでaddとコミットをすること


```
git add -A
```

```
git commit -m "コンフリクト解消"
```

次にpushした時に変更点が反映されてないといけないため


#何か間違えて戻りたい時

######ローカルリポジトリでのブランチの切り替え、新たに加えられた変更のプル、履歴の書き換え、コミットの履歴を表示する

```
git reflog
```

すると下のように表示される。

```
aefb89f HEAD@{0}: commit: Add a.txt
af3cfdd HEAD@{1}: commit: Add b.txt
d8441e3 HEAD@{2}: commit: Add c.txt
```
a,b,cのファイルを追加した情報が一回ずつコミットされている。

※qを入力してlogを終了する

######1番上に表示されている作業を無かったことにする

```
git reset --hard HEAD@{1}
```

これでbを追加した時の状態に戻れる



#addやコミットで上げたものを消したいとき

######addで上げたものを消す　※マージも無かったことにできる

```
git reset --hard HEAD^
```
######addで上げたものは消さずに、コミットだけを消す

```
git reset --soft HEAD^
```
######addで上げたものだけを消す

```
git reset --hard HEAD
```

#参考にさせて頂きました
https://gist.github.com/kymmt90/9c997726b638b316f9be07aa4e3eea5e
https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe
https://techacademy.jp/magazine/10264
windows10HomeEditionでdockerでMySQL,Redisの使い方

## 概要

dockerを仕事で使っていたので、自宅のPCでも使えるようにしようかなと考えたときに
windows10がHomeEditionだったので、そのままでできないかとやってみてうまくできたので
その内容をまとめました。


## Docker導入

基本的に[こちら](https://qiita.com/idani/items/fb7681d79eeb48c05144)の記事を参考にインストールを行いました。

[こちら](https://github.com/docker/toolbox/releases)から最新のDockerToolBoxを入手しました。
ちょっと容量が大きいのでダウンロードに時間がかかりますがのんびり待ちましょう。
インストール後 __Docker Quickstart Terminal__ を起動します。
起動するといろいろ読み込んだ後以下のような表示になります。
![docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108811/95da90c7-a33d-fbfe-0f43-94eb8c1f6f76.png)


ここに表示されているIPを接続の際に使用するのでメモするなどしましょう。
PCシャットダウンするなどdockerを止めた後もう一度起動するときは __Docker Quickstart Terminal__ を起動するか、コマンドプロンプトなどで `docker-machine start` を実行します。

## MySQLのコンテナ取得～起動
1. Dockerのコンテナを[公式ページ](https://hub.docker.com/_/mysql)からほしいバージョンを取得します。
特にバージョンを気にしないならば
`docker pull mysql`
で取得できます。
バージョンを指定する場合は以下のように`:`のあとにバージョンを指定します。
`docker pull mysql:5.6`
1. コンテナ起動は以下のように起動します。
`docker run --name testdb -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql:5.6`
各引数にについては以下の通りです。
    *  `--name testdb` でコンテナ名ここで指定した名前で以後起動、停止を行います。
    * `-p 3306:3306` で外部から接続するポートを指定 ここではMySQLのポートを指定しています。(ここの指定がないと自分のPCから接続できません)
    * `MYSQL_ROOT_PASSWORD=password` はrootのパスワード
    * `-d mysql:5.6` は取得したコンテナを指定バージョンなどに注意
1. 一度 run で起動した場合は以後パソコンをシャットダウンしたり、してdockerを止めた後再び起動したいときには以下のようなコマンドで起動できます。
`docker start testdb`
__testdb__ の部分は作成したコンテナ名を指定します。

## MySQL接続確認
1. 起動後docker内で接続したい場合は以下のコマンドでdockerに接続してmysqlコマンドでDBへの接続などを行います。
`docker exec -it testdb bash -p`
![mysqlcon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108811/aaac7376-da77-b7d0-9622-a1b309eb4f1c.png)

1. 自分のPCからA5Mk2などで接続したいときはIPアドレスを`Docker Toolbox`を起動したときに表示されるIPアドレスを指定します。
具体的には以下のような感じになります。
![mysql_a5mk2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108811/9ded3bf3-ea0d-16f3-0139-b031765fea13.png)

## Redisのコンテナ取得～起動
1. Dockerのコンテナを[公式ページ](https://hub.docker.com/_/redis/)からほしいバージョンを取得します。
特にバージョンを気にしないならば<br>
`docker pull redis`<br>
で取得できます。<br>
バージョンを指定する場合は以下のように`:`のあとにバージョンを指定します。<br>
`docker pull redis:6.0-rc`
1. コンテナ起動は以下のように起動します。<br>
`docker run --name testredis  -p 6379:6379 -d redis`<br>
各引数にについてはmysqlと同じなので割愛 ポートについては変更しています。
1. 一度 run で起動した場合は以後パソコンをシャットダウンを行うなどして、dockerを止めた後再び起動したいときには以下のようなコマンドで起動できます。<br>
`docker start testredis`<br>
__testredis__ の部分は作成したコンテナ名を指定します。

## Redis接続確認
1. 起動後docker内で接続したい場合は以下のコマンドでdockerに接続してredis-cliコマンドでDBへの接続などを行います。
`docker exec -it testredis bash -p`

![redis.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108811/31df0c06-4de6-d527-355e-720c21c21dfc.png)

## 終了方法
1. コンテナの削除は以下でコンテナIDを確認します。
    * 起動中のコンテナの確認
        * `docker ps`
    * 停止中のコンテナを含む確認
        * `docker ps -a`
1. 以下のコマンドでコンテナを削除します。
    * `docker rm コンテナID`
1. パソコンをシャットダウンするときにはdockerを停止させる必要があるので以下のコマンドで停止します。
`docker-machine stop`
あなたは絵を書くために、ペンを欲しがっています。

しかし、どれだけ探してもペンは見つかりません。

そこに、小さな炭のかけらを見つけます。

しかし、あなたはペンを探していたので、炭のかけらを無視して、さらにペンを探します。

長い時間が過ぎ、あなたはようやくペンを見つけ出しました。

そして、とうとう絵を描くことができたのです。

しかし、あなたが絵を描きたかった理由は、だるまに目を描き足したかったからです。きっと小さな炭のかけらでも問題なかったかもしれません。

これは僕たちの身の回りによくある話です。
---

手段が目的になり、それによって合理的ではない決断をしてしまうことがあります。

僕たちは自分の行動に対して、常に明確な理由を持てている訳ではありません。

人には自動モードのような、感覚で物事をこなせる脳の状態があります。

そのおかげでたくさんの取り組みに対処できる訳です。

しかし、仕事などの大事な場面でこれをしてしまうと、重要なチャンスを逃したり、仕事をうまく進めることができなくなります。

大事な場面では、自分の行動に対しての理由が明確になってなければいけません。

そして、明確なだけではなく、その理由が自身の本心とリンクしている必要があります。

すこしわかりづらいかもしれませんが、"なぜ〇〇をするのか"、"なぜ〇〇だと思うのか"という問いに対して、最終的にはそれが自分の目指す理想に繋がっていることが、より良い成果を出すコツのひとつです。

さて、あなたはいま自分が行っている行動について、明確な理由が言えるでしょうか？

テストしてみましょう
---
![スクリーンショット 2020-04-15 23.00.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603301/dd495734-ee89-7e6d-119d-6b3a009614a6.png)

step1.
上記の QR コードで、LINE に "WHY から始めよ！ Bot" をお友達登録しましょう。

step2.
Bot の質問に答えていきましょう。

step3.
大体5回くらいやりとりできれば、新しい発見ができます。

<img width="375" alt="IMG_3379.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603301/64468e98-16a3-a127-42a8-60d58dd8507b.png">


それでは楽しんで！(最後まで読んでいただきありがとうございました。)
プロダクト思考の勉強メモ: 顧客価値方程式の考察

# はじめに
プロダクトマネジメントをもっと極めたいので、「プロダクト思考講座３０回」という、プロダクトマネージャー向けのオンライン音声講座（中国語）を使って、PdMとしての論理武装と基礎体力を鍛えています。講師は「北京中関村の一番才能のある女性」といわれるほど、プロジェクトマネジメント界隈で有名な名な梁寧氏です。

テンセント社やアリババ社など、数億人規模のユーザーが利用するサービスをたくさん擁する企業の事例を交えながらプロダクトマネージャーとして、備えるべき思考パターン、物事を見る目線を本質を突っ込んでわかりやすく説明しています。

個人的には学びと気づきが多いので、これから面白いと思った内容をここで不定期にメモします。

# 顧客価値の方程式
> **顧客価値の方程式：**
> 顧客価値　＝　（新体験－旧体験）－サービス乗り換えコスト　
※　「体験」はいわゆるCX、つまりCustomer Experienceのこと

実はこの方程式は梁寧氏の発想ではなく、元Baiduのトッププロダクトマネージャー兪軍氏が考えた方程式です。講座の中で梁氏がわかりやすく説明してれました。

この方程式のポイントは「サービス乗り換えコスト」です。具体的には、新サービス・プロダクトのブランド認知度、学習コスト、入手のためにかかるコスト、サポートなど。
簡単に言うと、新CXが旧CXより優れても、乗り換えのコストが絶対発生するから、顧客が必ず価値を感じるわけではありません。サービス乗り換えコストは、顧客価値を考える際に結構重要です。これは学びの一つです。

一方、これは直感でシンプルでわかりやすいですが、実際にサービス設計に使おうとすると、どこかものなりたいなと引っかかっています。

例えば、よりよい睡眠体験ができるマットレスの買い換えを例として考えてみましょう。
今まで使っているスプリングマットレスは体に合わず、翌朝起きると疲れが溜まって、すっきりにはなりません。
自分の体形にあう、体圧分散できるマットレスに出会って、横になって試してみたところ、新マットレスの体験が良く、非常にほしくなりました。一方、買い替えのコストは数万や十数万であるため、購入の判断は普通難しいでしょう。
※単純化するため、乗り換えコストは入手コストだけにします

このマットレスのプロダクトマネージャーとして、この悩みはどうやって解消できるでしょうか？

それでは、時間もひとつの要素として、この方程式に加えてをさらに深堀してみたらどうなるでしょうか？
下記の方程式に書き換えることができると思います。

> **方程式１**　
> 顧客価値＝　単位時間あたりの（新体験ー旧体験）＊時間　－サービス乗り換えコスト

さらに、下記の方程式にも書き換えてみます。

> **方程式2**
> 単位時間の顧客価値　＝　単位時間あたりの（新体験ー旧体験）ー単位時間のサービス乗り換えコスト

経験上サービス乗り換えコストは大体最初しか発生しなく、一過性のものが多いです。
なので、乗り換えコストが膨大であっても、「新旧体験のギャップ＊時間」で掛けて考えれば、結果的には顧客価値を提供できることです。

上記の新マットレスの例でもう一度考えてみましょう。
例え価格は10万で、最低5年使える、単位時間は「日」にしましょう。

> 単位時間のサービス乗り換えコスト　＝　10万/(365日＊5) = 55円/日

つまり、品質のよい睡眠による翌朝疲れを解消しすっきりになる体験に1日55円払いたいかどうかだけを判断すればよいです。

さらに、分割払いや、現在のマットレスを無料で引き取りなど、乗り換えコストをさらに下げるサービス設計もできます。

# 学びのまとめ
・　顧客価値のあるサービスはかならず乗り換えコストを考慮すること。
・　時間軸を加えて考えると、顧客価値説明する新しい観点が得られること。
【Java入門】変数(Variable)について

# 変数(Variable)とは？

様々な言語には変数というのが存在する。
変数は一言で言うとお皿❓箱❓みたいな⁉️つまり、何らかのデータを入れておく領域。
変数を宣言するとメモリ上の決まった領域を確保し、そこにデータが格納される。

# 変数(Variable)の型

前回の記事を参考してください〜💕（030）
[型を見に行こう](https://qiita.com/Lovely_030_Dong/items/d8d425350f7896d07405#methodの書き方)
※ByteからStringまで

# 変数(Variable)のアクセス修飾子

前回の記事を参考してください〜💕（030）
[修飾子を見に行こう](https://qiita.com/Lovely_030_Dong/items/d8d425350f7896d07405#methodの書き方)
※ public・protected・privateが対象
❣️アクセス修飾子がなし😮：現在のクラスと同じパッケージのクラスからアクセスできる。

# 変数(Variable)の書き方
- 宣言

```
型 変数名;
--------
int a;
```
  
- 代入

```
変数名 = 値;
※変数に値を代入するには「=」を使うよ❣️
----------
a = 10;
※変数に値を代入する時は型に気を付けてね❣️
```

- 様々な宣言方法

① 宣言と同時に値を代入する。（初期化）

```
int a = 10;
```

② 同じ型に複数を宣言する。

```
int a, b, c;
```

③ ①+②

```
int a = 1, b = 2, c = 3;
```

# 変数(Variable)名のルール？

変数名を決めるのに👇のルールを守ろう‼️

- 「英字」か「_」か「$」で始まる。
 - 注‼️「$」は使うべきでない、また「_」で始めるべきでない

```
例）
・_value
・get$Money
```

- 大文字、小文字が区別される。

```
例）
dong
Dong
dOng
doNg
donG
DOng
dONg
doNG
DonG
DONG

```

- 予約語は使えない‼️

||||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| abstract | boolean | break | byte | case |
| const | continue | default | do | double |
| goto | if | implements | import | instanceof |
| package| private | protected | public | return |
| this | throw | throws | transient | try 
| catch | char | class | extends | final 
| int | interface | long | native | new |
| short | static | super| switch | synchronized |
| void | volatile | white |finally | float |
| for |
[Unity&Blender]Blenderで作ったキャラをUnityに導入する

#概要
Blenderで作ったキャラクターをUnityに導入する。
そのまま入れただけではテクスチャーが適用されないので貼り付ける作業もする。


#本文
##まずBlenderから３Dモデルとテクスチャを書き出す
![スクリーンショット_(74).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617265/9af7de91-4749-1168-bf67-914b50b47780.png)
FBXファイルとして書き出します。
![スクリーンショット_(75).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617265/d43aaa9c-d95d-d997-2bd7-e899810f16ed.png)
右側のArmatureとMeshだけ（Shiftキーを押しながら）選択した状態にして、ExportFBXを押します。
![スクリーンショット_(78).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617265/15538fa5-a045-b263-d2bf-a69d7ee7841a.png)
テクスチャも書き出します。
##Unityに取り込む
![スクリーンショット_(76).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617265/48b7a18e-59f0-3cd0-c992-deb1281d0488.png)
①まずはAssetsに先ほど作ったFBXファイルをドラッグアンドドロップする。

③登録されたFBXファイルをクリックするとInspectorが写真のようになるので、【赤】のMaterialsを選び、その下のExtractMaterialsをクリックする。

④「パソコンがマテリアルを用意してくれるフォルダ」を選ぶ画面になるので、好みの場所を指定する（Assetsのままがわかりやすい）

⑤【青】のようにAssetsにMaterialが表示される。

⑥Material【青】に貼り付けるテクスチャファイルをAssets上にドラッグアンドドロップで登録する【緑】

![スクリーンショット_(77).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617265/a5991af3-5698-a00f-61f3-93fd61856f87.png)
⑦Material【青】を選択した状態でInspector（写真）のAlbedo左の枠にテクスチャ【緑】をドラッグアンドドロップ

#追記
ここまででキャラクターの登録ができたので、使いたいときはAssetsの中のキャラクターをゲーム画面にドラッグアンドドロップすればテクスチャーが適用された状態で表示されます。
ユーザー管理機能の実装（１）

今回は、**ユーザー管理機能の実装**を行なっていきたいと思います！ 
1~7の手順で進めていきます 
（１）では1. deviseのインストール、2. Userモデルの作成を行うところまでやっていきます

#目次
1. deviseのインストール

2. Userモデルの作成

3. deviseのviewファイルを追加

4. サインアップ機能の追加

5. ユーザー情報編集機能の追加

6. ログアウト機能の追加

7. フラッシュメッセージの表示機能の実装
#1. deviseのインストール
ユーザー管理機能のの実装には、”devise”を使用します 
まず初めにGemfileを開きましょう！ 
Gemをインストールするために、Gemfileに追記を行います

```Gemfile
（略）
Gem ‘devise’
（略）
```

Gemfileのgroup内に記述しないように注意しましょう！ 
＊group内に記述した場合、特定の環境のみで使用される設定になってしまします

次に、deviseのインストールを行います ターミナルでbundle installを行い、続いてdevise用のインストールコマンドの実行を行います！

```ターミナル
% bundle install
% rails g devise:install
```

これでdeviseのインストールは完了です！

#2. Userモデルの作成
ユーザーの管理を目的とするモデルファイルやテーブルを作成していきます！ 
次の2つの点に注意して進めていきましょう！

**・deviseで使用するモデルを作成する際は、rails g modelといった通常のモデル作成とはコマンドが異なる**
**・どんなカラムを追加するか、カラムにどんな制約をかけるかを事前にDB設計を確認する**

それでは、Userモデルを作成していきましょう！ まず、ターミナルで以下のコマンドを実行します

```
% rails g devise user
```
これにより`user.rb`や`マイグレーションファイル`などが作成されました 
`routes.rb`にも`devise_for :users`の記述が追加されました

次に、マイグレーションファイルに追記を行います migrateディレクトリにマイグレーションファイルが作成されているので確認してみましょう！ `xxxxxxxxxxxxxx_devise_create_users.rb`といったファイルが確認できると思います 
ファイルが確認できたら、nameカラムに**NUT NULL制約**をかけるために、changeアクションの中に以下の追記を行います

```ruby:xxxxxxxxxxxxxx_devise_create_users.rb
t.string :name, null: false
```
また、同じくnameカラムに**一意性制約**をかけるために、changeアクション外には次の追記をしていきましょう

```ruby:xxxxxxxxxxxxxx_devise_create_users.rb
add_index :users, :name, unique: true
```
しかしこのままでは空文字も保存できてしまうため、user.rbに以下の追記を行います

```ruby:user.rb
validates :name, presence: true, uniqueness: true
```
最後にマイグレーションの実行を忘れずに行いましょう！

```
% rails db:migrate
```
今回はここまで！
ユーザー管理機能の実装（２）では、deviseのviewファイルを追加などを行なっていきます 
お疲れさまでした！
Dockerfile + docker-compose.ymlでFlask環境構築

手軽にflask環境を構築したいときの備忘録として。

# 実行環境
* Mac OSX　10.15.4
* Docker version 19.03.8

# ディレクトリ構成

```
test/
   |- Dockerfile
   |- docker-compose.yml
   |- app/
      |- app.py
```

# 各ファイルの記述

## Dockerfile
* 以下の記事と同様に記述
    * [Docker+Flaskによるお手軽Webアプリ開発](https://qiita.com/phorizon20/items/57277fab1fd7aa994502)
* コンテナ内に `app` ディレクトリを作成するコマンドを追加
    * ローカルの `app` ディレクトリをマウントするため

```dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask

RUN mkdir /app
```

## docker-compose.yml

このファイルで以下の手続きを定義。

*  `build` ： 指定したパスにあるDockerfileをもとにimage作成
*  `command` : コンテナ起動後、コンテナ内で実行するコマンド
*  `volumes` : ローカルの `./app` をコンテナ内の `/app` にマウント
*  `ports` : ポート指定


```docker
version: '3'
services: 
    web:
        build: .
        command: python3 app/app.py
        volumes:
            - ./app:/app
        ports:
            - 5000:5000
```

## app/app.py
* こちらも以下の記事と同様に記述
    * [Docker+Flaskによるお手軽Webアプリ開発](https://qiita.com/phorizon20/items/57277fab1fd7aa994502#web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E6%BA%96%E5%82%99) 「Webアプリの準備」の項

以上で準備完了。

# ビルド→コンテナ起動→Webサーバ起動
* 以下のコマンド一発！

```
$ docker-compose up -d
```

* コンテナが起動しているか確認
    * Stateが「Up」ならOK

```
$ docker-compose ps
   Name           Command         State           Ports         
----------------------------------------------------------------
test_web_1   python3 app/app.py   Up      0.0.0.0:5000->5000/tcp
```

* http://localhost:5000/ にアクセスして「Hello world!!」が表示されれば構築成功！
Railsのコントローラー(モデルでも)では他とメソッドを共有したい時に継承ではなくConcernsを使おう

railsでプログラミングをしていると、同じようなメソッドを別々のコントローラーで使いたい時があったりします。また、肥大化して可読性がさがったコードを分割して、別ファイルにしたい時とかもあります。そんなときは継承を使わずに、Concernsを使いましょう

## 継承を使うと、改修が起きた時にバグに気が付かない

例えば、`HogeController`からCSV出力の所だけ切り出した`HogeCsVController`を作りたい場合

```ruby:HogeController.rb
class HogeController < ApplicationController
  def index
  end

  def show
  end

protected
  def set_hoge
  end
end
```

```ruby:HogeCsvConntroller.rb
class HogeCsvController < HogeController
  def csv
    hoge = set_hoge
  end
end
```

みたいな書き方でも動くといえば動きます。が、`HogeController`側に改修が入れる際、わざわざset_hogeメソッドが他のCSV用のコントローラーに影響があるなんて意識しないでしょう。で、気が付かずset_hogeメソッドをいじった結果、CSVがバグっているのに気が付かない…なんてことが発生します。

## Concernsを使って明示的に「共通の物」としておく

こんな感じで書きましょう。

```ruby:app/controllers/concerns/hoge_article.rb
module HogeArticle
  extend ActiveSupport::Concern

  def set_hoge
  end
end 
```

```ruby:HogeController.rb
class HogeController < ApplicationController
  include HogeArticle

  def index
  end

  def show
  end
end
```

```ruby:HogeCsvConntroller.rb
class HogeCsVController < ApplicationController
  include HogeArticle

  def csv
    hoge = set_hoge
  end
end
```

そうすることで、改修の際に継承されていることに気が付かずバグるというリスクが下がります。
CentOS 7のpython 2.7にgsutilを入れて動かす

# 前提

- OSデフォルトのpythonを使う
- 極力最小限の手間で実現出来る
    - pyenvとか使わない
- システム側には手を入れず、ユーザー側だけでどうにかする
    - 端的に表現すると `sudo` なしで実現可能である
- Dockerは使わない
- EPELだけはOK牧場
    - `pip` を使うため。これだけは許して
- **`gsutil`でproxyが使えることが必須**
    - これのせいで、こんな記事を書く羽目に

# 結論

```bash
sudo yum install python-pip

pip install --user --upgrade "setuptools<45"
pip install --user wheel
pip install --user gsutil
```

# 何にハマったのか

まず、python2でどうにかしようとした。gsutilはpython2を未だサポートしているから。しかし、単に `pip install --user gsutil` するとうまくいかない。結論から言うとCentOS 7のpython2の setuptoolsが古すぎてconfigparser/zippパッケージの最新版をうまくインストール出来ていなかったからだった。これに気がつくのに時間がかかった。

python2で躓いたので、当然のことながらpython3でやってやろうじゃないか、ということに。今度は、すんなりいくように見えた。yumでpython3を入れて、pipでgsutilを入れるだけ。実際インストールはうまく出来て、gsutilも叩けるようになった。しかし今度はproxyの設定をするとTraceが出る(正確に言うと、環境変数でproxyを設定していたのだが、それが原因だと気がつくのに相当時間を要した)。たどり着いたのはこのIssue:

https://github.com/GoogleCloudPlatform/gsutil/issues/880

Openだった。絶望した。クラウド使っている人がproxyなんて小学生までだよねー、っていう感じ？はい、わかりますよ。

python2に戻り、ちゃんとエラーメッセージと向き合って、解決策にたどり着いた。このために4時間くらい消し飛んだ。


# 何故こんな縛りプレイをするのか

- 実際の運用環境では古いバージョンを使わざるをえないことが多々あるわけです
    - 最新のものとかバンバン入れるとサーバー自体の運用・管理がしんどくなるわけです
    - yumコマンドで入るものオンリー、かつrepoはbase/updates/EPEL以外は許容しないのです
- DockerはCentOS 6では厳しいのです
    - 先にCentOS 7のサーバーでやっているが、同じことをCentOS 6のサーバーでもやらなきゃいけない
gqlgen httprouter recipe

###gqlgenとhttprouteの組み方

```
import (
     "net/http"
     ...
     "github.com/99designs/gqlgen/graphql/handler"
     "github.com/99designs/gqlgen/graphql/playground"
     "github.com/julienschmidt/httprouter"
)

// Defining the Playground handler
func PlaygroundHandler() httprouter.Handle {
	h := playground.Handler("GraphQL", "/query")

	return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
		h.ServeHTTP(w, req)
	}
}

// Defining the Graphql handler
func GraphqlHandler() httprouter.Handle {
      // NewExecutableSchema and Config are in the generated.go file
     // Resolver is in the resolver.go file
	h := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

	return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
		h.ServeHTTP(w, req)
	}
}

func main() {
	// Setting up httprouter
	r := httprouter.New()
	r.POST("/query", GraphqlHandler())
	r.GET("/", PlaygroundHandler())
	...
}
C# ラムダ式とジェネリックのまとめ

C# ラムダ式とジェネリックのまとめ
````
using System.Linq;
using System;

public class Hello{
    public static void Main(){
       
        //ラムダ式　xxが引数　xxにレンジの結果が入る
        foreach(int i in Enumerable.Range(0,5).Select(xx => xx * 10))
        {
            System.Console.WriteLine(i);
        }

        Random random = new Random();
        
         //ラムダ式　xxが引数　xxにレンジの結果が入る
        foreach(int i in Enumerable.Range(0,5).Select(vv => random.Next(0, 100)))
        {
            System.Console.WriteLine(i);
        }
        
        string s="a";
        string ss="aa";
        
        int ii=10000;
        int iii=100;
        
        int sss=Hello.IsNull(ii,iii);
        
        System.Console.WriteLine(sss);
        
        
        Test(s,ii);
        
        Test(ii,s);
    }
        //ヌルならアウトプットを返す関数　ジェネリック型なので型はなんでも大丈夫
        //Tは１つなので１つの型しか扱えない
        public static T IsNull<T>(T input, T output)
        {
            if (input == null)
            {
                return output;
            }
            return input;
        }
        
      
      //    public static string MyCheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> )
        //{
          //  return "test";
        //}
        
        //２つ指定があるので、intとstring型でもオッケー
        static void Test<T1, T2>(T1 x, T2 y)
        {
            Console.WriteLine(x);
            Console.WriteLine(y);
        }
        
}
EntityFramework 6.4.0 日本語リソースがまだ無い。

![20200415fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132059/1aee71f5-5a77-4e30-5bbe-5f26ddc60175.png)

EntityFrameworkを、6.2から6.4へアップデートしようとすると、次のエラー。

依存関係を解決できません。'EntityFramework 6.4.0' は 'EntityFramework.ja 6.2.0 制約: EntityFramework (= 6.2.0)' と互換性がありません。

確か、ずいぶん前に同じことをしてこのエラーが出るので、アップデートを諦めたのだけど、まだ解決していない。日本語のリソースを削除すればOKか、あるいは依存関係を無視する、にすれば良い気もする。
Hyper-VでCentos7をインストールする方法

#Hyper-Vで仮想マシンを作成
Hyper-Vで様々なOSの仮想マシンを作成することができますが、今回はLinuxの一つのディストリビューションであるCentosをインストールする方法をご紹介します。
↓↓Youtubeで解説した動画も公開してるのでぜひ見てみてください
[【ゆっくり】Hyper-VでCentos7の仮想マシンを作成する方法](https://www.youtube.com/watch?v=gRQNdXsOYJs&t=222s)
※この動画にはゆっくり音声で解説していますので苦手な方はご注意ください

#仮想マシンを作成するメリット（Linuxの場合）
Hyper-Vで環境で仮想マシンを作成することによって、WindowsOSではできなかったことができます。Linuxを導入することでCUI操作に慣れることができますし、開発環境を自分で準備することができます。開発環境を自分で準備できない場合は有料サーバを借りた場合お金がかかりますが自分でHyper-Vで構築した場合は無料です。またLinuxはWindowsやMacよりも軽く、ストレスなく操作することができます。

#それではさっそくインストール
+ 1.「スタートメニュー」-「Windows管理ツール」-「Hyper-Vマネージャー」をクリックします。
※「Hyper-Vマネージャー」がない場合は、以下の記事から「Hyper-V」機能を追加してください。
[Windows10ProでHyper-Vを導入する方法](https://qiita.com/kahluaandmilk450/items/6dc8c692eb915640d8d7)

+ 2.「Hyper-Vマネージャー」起動後、「操作」-「新規」-「仮想マシン」をクリックします
+ 3.「次へ」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/27aa98f7-8100-1bda-c6c4-6a60710bb3dd.png)

+ 4.以下の画面で任意の名前を入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/9f054005-ac10-7ba7-e2cc-0ed3d533c1dd.png)
仮想マシンを作成したい場所を変更したい場合は「仮想マシンを別の場所に作成する」にチェックを入れてください。※Cドライブ等のシステム領域に仮想マシンを作成するのはお勧めしません。Dドライブ等、システム領域とは別のドライブに作成することをお勧めします


+ 5.以下の画面で世代を指定します。今回は第2世代を選択しました。なお、後ほど説明しますが第2世代にする場合は「セキュアブートの無効化」をすることをおススメします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/5e8e1fe8-a41f-3133-83b6-97a3f0cfb055.png)


+ 6.仮想マシンに割り当てるメモリを変更します。
メモリは2GBを指定しました。デフォルトでは1GBですが少なすぎるので2GB以上をおススメします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/0de5c47a-a74e-81e8-ad99-8af922472471.png)


+ 7.接続する仮想スイッチを指定します。今回は「接続しない」のまま「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/bb9c7cbf-665a-ea21-7c8a-23d6ced0be94.png)

+ 8.仮想ハードディスクの接続です。ここは特に要件がなければデフォルトのまま「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/a27df08a-fe3f-70b1-3ea3-13f49e8bbd94.png)

+ 9.インストールするOSのISOを選択し「次へ」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/6799b216-8ad0-c068-5553-810cec572acc.png)

+ 10.設定が間違っていないことを確認し「完了」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/c9d634c4-2e32-d966-85d0-ba0942c9d51d.png)

+ 11.仮想マシンが作成されたことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/3f9b0ef5-c6ef-a000-22d7-7b82e3303b58.png)

+ 12.仮想マシンを右クリックし、「設定」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/7a954301-9d83-b59c-f444-f1bbf6f98d04.png)

+ 13.「セキュリティ」-「セキュアブートを有効にする」のチェックを外して「OK」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/68ef2b71-f0e4-5105-13db-fe3d70bf8909.png)


+ 14.仮想マシンを選択し、「操作」-「起動」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617706/74aaf7fc-73b4-c4d3-2b17-e38db79999fc.png)


#仮想マシンの登録完了
仮想マシンの登録はこれにて完了です。次回Centos7をインストールする方法を紹介します！
Google Apps Scriptを使ってメールを定時送信する

## 目的

* 毎日、ほぼ同じ時刻にメールを送信すること。

## 手順

* Googleドライブから「新規」→「その他」→「Google Apps Script」を選択し、スクリプトエディタを開く。
* メール送信関数を実装する。
 
```
function sendEmail() {
  const to = 'hoge1@gmail.com';
  const cc = 'hoge2@gmail.com,hoge3@gmail.com';
  const bcc = 'hoge4@gmail.com';
  const objArgs = {cc:cc, bcc:bcc};
  const subject = '件名';
  const body = '本文';
  
  GmailApp.sendEmail(to, subject, body, objArgs);
}
```

*  トリガー関数を実装する。例として８時45分~8時55分の間のランダムな時刻に送信されるようにする。

```
function setTrigger(){
  const time = new Date();
  time.setHours(8);
  time.setMinutes(45 + Math.random() * 10);
  ScriptApp.newTrigger('sendEmail').timeBased().at(time).create();
}
```
- トリガーを設定する。 
  - メニューの「編集」→「現在のプロジェクトのトリガー」を選択する。
  - 右下の「トリガーを追加」を選択する。
  - 以下のトリガーを設定する。
     -  実行する関数: setTrigger
     -  イベントのソース: 時間主導型
     -  時間ベーストリガーのタイプ: 日付ベースのタイマー
     -  時刻を選択: setTriggerの時刻よりも前の時間帯

## 注意

* 曜日に関わらず**毎日**送信されます。
* 送信する曜日を指定する場合は時間ベーストリガーのタイプを週ベースのタイマーに設定してください。

## 参考

* [Google Apps Script で毎日決まった時刻にスクリプトを実行するトリガー設定](https://tonari-it.com/gas-trigger-set/)
モノクロImageEffectをベースに、スクリーン合成の話。　〜月曜日のたわわのようなトーンを目指して〜

一つ前の記事
[シンプルなImageEffectその２。モノクロ。](https://qiita.com/ScreenPocket/items/4e33b8f4a2925f109e97)
のメインカラー_Colorとの合成部分。

```glsl:Monochrome.shader
col.rgb = Luminance(col.rgb) * _Color.rgb;
```
ですが、コチラいわゆる**乗算合成**を行っています。
２つの色を掛け合わせる色合成であり、0.0〜1.0の範囲同士の色をかけ合わせるという事で、

どうしても2つの色どちらかの色の明るさ以上になることはない　＝　**暗く見えてしまう**

という特性があります。
![スクリーンショット 2020-04-15 22.08.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/aee61db5-77f2-76d0-4172-d1cb4f760265.png)
暗くしたいなら、まぁそれで良いのですが今回はそういうわけでもない。
というわけで別の色合成方法が欲しくなるわけですが、乗算合成の他にも**加算合成**というブレンド方法があります。
こちら、読んで字の如く、掛け算(乗算)を足し算(加算)にするだけで対応可能です。

```glsl:Monochrome.shader
col.rgb = Luminance(col.rgb) + _Color.rgb;
```
![スクリーンショット 2020-04-15 22.12.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/3c6092ea-09f7-eb6e-38a2-97f508c14881.png)
明るくなりました！
…ただこれだと白飛びして、眩しい印象をユーザに与えてしまいます。
光とか炎ならこれでも良いかもしれませんが、どうにも、もうちょっと目に優しい色が良い・・・。

そんな時に使えるのが**スクリーン合成**です。
計算式としては
**結果の色 = 1-(1-元の色)*(1-加える色)**
なので、合成部分を下記のように書き換えます

```glsl:Monochrome.shader
col.rgb = Luminance(col.rgb);
col.rgb = 1 - (1 - col.rgb) * (1 - _Color.rgb);
```
![スクリーンショット 2020-04-15 22.19.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/f8c526a8-5571-8ffb-6afa-d4210bf7c537.png)

暗くもないし、色もしっかり乗っている印象になりました。
個人的には「月曜日のたわわ」がこんな色使いだなぁと思っているので、Tawawa.shaderという名前で下記においておきます。
※前記事のモノクロと、色合成部分のみ違います

```glsl:Tawawa.shader
Shader "ScreenPocket/ImageEffect/Tawawa"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ( "Main Color", Color ) = (0,0,0,1)
    }
    
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            fixed4 _Color;

            fixed4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D( _MainTex, i.uv );
                col.rgb = Luminance(col.rgb);
                col.rgb = 1 - ( 1 - col.rgb ) * (1 - _Color.rgb);
                return col;
            }
            ENDCG
        }
    }
}
```
世の中には他にも色々な色合成の計算が有るので、調べてみると面白いかと。
個人的には乗算・加算・スクリーンを覚えておけば、ある程度は仕事をする上で困らないかなぁ、と思います。
Javascript for ... in...の学習【個人メモ】

※下のコメントもお読みください
@netebakariさんにfor ...in...　の問題指摘して頂いています。



[徹底マスター JavaScriptの教科書](https://www.sbcr.jp/product/4797388640/) p158を参照


```js
                getRoomData.then(function(dbData){
                    for(let i in dbData){
                        eventInfo = getEventInfo(dbData[i].room_id);
                        // console.log(eventInfo);
                        console.log(roomInfo.child(i))
                        setRoomInfo(eventInfo,i,roomInfo);
                    }
                })
```
Scrapyでクローリングを開始するまで

#Scrapyとは
ScrapyはPythonのクローリング・スクレイピングフレームワークです。
これを利用することで自分の書いたコードにライブラリをインポートするというようなやり方でなくフレームワークの作法に従ってコーディングができます。

#Scrapyをインストールする
```
$pip install scrapy
```

#プロジェクトを作成する
プロジェクトを作成するには以下のコマンドを実行します。

```
$scrapy startproject (プロジェクト名)
```

プロジェクト名は任意のもので構いません。
実行すると何やらたくさんディレクトリができると思います。

#ダウンロードの間隔を設定しましょう
ダウンロードの間隔をあけないとクローリング先のシステムに負荷をかけることになると思いますので、注視する必要があります。

プロジェクト名フォルダにからsetting.pyに以下の文を追加します。

```Python
DOWNLOAD_DERAY = 1
```

#itemを作成する
クローリングでとってきたものを貯めておくところです。
items.pyにクラスを定義します。

```Python
class [　クラス名　](scrapy.Item):
    [　取ってくるものの名前　] = scrapy.Field()

item = [クラス名]()
item['取ってくるものの名前'] = 'Examples'
```

#Spiderの作成

クローリグやスクレイピングの詳細の記述は主にspiderに記述します。
以下のコマンドを入力してspiderを作成します。

```Python
$scrapy genspider [ spiderの名前 ] [ 取ってくるサイトのドメイン ]
```

これでspiderフォルダに[ spiderの名前 ].pyファイルが作成されます。

この後はクローリングを行うサイトに合わせてspiderを記述していくことになります。

間違いがあれば指摘していただけますと幸いです。
【python】 pythonでgmail送信：複数imageファイルを添付し一件ずつ送信

# はじめに
pythonでgmailを送信したいなということで調べていたときのまとめです。あくまでメモ。

以下の参考より大まかは同じですが、

特定ディレクトリにある写真を繰り返し読み込んで送信してくれるようにしました。

参考：https://qiita.com/numaC/items/076474934a3bb45647bd

参考サイト、感謝です。

# 内容

## 結論

結論は以下です。
ただ、「SMTPAuthenticationError」が出る場合がほとんどだと思います。
それは以下の「内容」の部分より参照ください。

```python:sendmail.py
import os
import glob
from smtplib import SMTP
from email.mime.text import MIMEText
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart

def sendGmailAttach():
    sender, password = "hogehoge@gmail.com", "testtest" # 送信元メールアドレスとgmailへのログイン情報 
    to = 'hogehoge@gmail.com'  # 送信先メールアドレス
    sub = 'テストメール送信中' #メール件名
    body = '画像添付しています。'  # メール本文
    host, port = 'smtp.gmail.com', 587

    # メールヘッダー
    msg = MIMEMultipart()
    msg['Subject'] = sub
    msg['From'] = sender
    msg['To'] = to

    # メール本文
    body = MIMEText(body)
    msg.attach(body)

    # 追加部分
    fetch_from_dir = glob.glob(os.getcwd() + "\img/*")
    fetch_from_dir_len = len(fetch_from_dir)
    len_cnt = 0
    for target_list in fetch_from_dir:
        print(target_list)

        # 添付ファイルの設定
        attach_file = {
            'name': os.path.basename(target_list),
            'path': target_list
        } # nameは添付ファイル名。pathは添付ファイルの位置を指定
        attachment = MIMEBase('image', 'png')
        file = open(attach_file['path'], 'rb+')
        attachment.set_payload(file.read())
        file.close()
        encoders.encode_base64(attachment)
        attachment.add_header("Content-Disposition", "attachment", filename=attach_file['name'])
        msg.attach(attachment)

        # gmailへ接続(SMTPサーバーとして使用)
        gmail=SMTP("smtp.gmail.com", 587)
        gmail.starttls() # SMTP通信のコマンドを暗号化し、サーバーアクセスの認証を通す
        gmail.login(sender, password)
        gmail.send_message(msg)
        len_cnt += 1
        print('メールが' + str(len_cnt) + '/' + str(fetch_from_dir_len) + '件送信されました')


if __name__ == '__main__':
    sendGmailAttach()
    print('メールが全件送信されました')
```

```txt:実行結果
$ python sendmail.py
C:\Users\hogehoge\img\1.jpg
メールが1/3件送信されました
C:\Users\hogehoge\img\2.jpg
メールが2/3件送信されました
C:\Users\hogehoge\img\3.jpg
メールが3/3件送信されました
メールが全件送信されました
```

## 内容
### 送信時のエラーについて
おそらくプログラムを実行したときにほとんど以下のエラーが出ると思われます。

```txt
smtplib.SMTPAuthenticationError: (535, ‘5.7.8 Username and Password not accepted. Learn more at\n5.7.8 https://support.google.com/mail/?p=BadCredentials
```

これはgmail側で「アプリパスワードを使用する」と解決します。

以下が参考サイトです。ありがとうございます。

参考：https://www.gocca.work/python-mailerror/

大まかな手順を書きますが、詳しくは参考サイトより。

```txt 
- 手順１：googleアカウントにログインした後
- 手順2：「2段階認証プロセス」の設定
- 手順3：「アプリパスワード」の生成
```


### プログラムについて

ひとまず方法としてメモしているので、エラー処理などが足りませんので適当に対応していただければ。
describe-task-definitionの結果をregister-task-definitionで使うために

※この記事は以下の記事に触発されて書きました。
https://dev.classmethod.jp/articles/describe-task-definition-to-register-task-definition/

上記記事の以下の部分をRubyでやってみた。
> 既存のタスク定義をdescribe-task-definitionで取得して、register-task-definitionで登録するには、 taskDefinition の中身を取り出して、 status, compatibilities, taskDefinitionArn, requiresAttributes, revision を削除することで、登録する事ができます！

- `describe-task-definition`の標準出力をRubyで処理する。そしてファイル(`taskDef.json`)に出力する。

```shell
$ aws ecs describe-task-definition --task-definition sampleTask:1 | ruby -r JSON -e 'json_obj = JSON.parse($stdin.read);json_obj["taskDefinition"].delete("revision");json_obj["taskDefinition"].delete("requiresAttributes");json_obj["taskDefinition"].delete("taskDefinitionArn");json_obj["taskDefinition"].delete("compatibilities");json_obj["taskDefinition"].delete("status");puts json_obj.to_json' >> taskDef.json
```

- 結果を出力したファイルを指定して`register-task-definition`を実施。

```shell
$ aws ecs register-task-definition --cli-input-json fileb://taskDef.json
```

おしまい！