OmniAICreator/Qiita-1.07M huggingface dataset:
【VSCode】超便利Emmet機能でコードを簡単に作成して時短する方法

個人メモです。

VSCodeでタグを生成するときに、補完機能（タグ名を入力してTABを押す)以外に、タグとクラスを一挙に生成するなど、様々な便利機能がある。

##目次

1. [使い方](#使い方)
2. [開始タグと終了タグの生成](#1-開始タグと終了タグの生成)
3. [クラス名のついたタグを生成](#2-クラス名のついたタグを生成)
4. [タグの中にタグを生成](#3-タグの中にタグを生成)
5. [並列でタグを作成](#4-並列でタグを作成)
6. [掛け算でまとめて生成](#5-掛け算でまとめて生成)
7. [CSSプロパティを作成](#6-cssプロパティを作成)


##使い方
Emmet（エメット）の書式に従って記述した後に、`TAB`キーをクリックする。


##1. 開始タグと終了タグの生成
・`タグ名`

![tag.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/bd438914-66bd-937c-deac-d4d7697f3874.gif)



##2. クラス名のついたタグを生成
・`タグ名.クラス名1.クラス名２....`

divタグの場合はタグ名を省略可能。

![div-emmet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/0e80d67e-9755-2cef-fbf0-b77b45b88dad.gif)


##3. タグの中にタグを生成

・`タグ名>タグ名>タグ名....`

クラス名を付けたい場合は、タグの後ろに`.`ドットマークをつける。

![divdiv.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/f573a0af-b8ad-4206-663f-97d7022262ef.gif)



##4. 並列でタグを作成

・`タグ名+タグ名+...`

![div+div.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/77ef3116-85fd-02fd-5a18-2ffe4d5ccdbb.gif)


##5. 掛け算でまとめて生成
欲しいタグを指定した数だけ生成する。グループ化したい場合はカッコで囲む。

・`タグ名*数値`
・`タグ名.クラス名*数値`
・`(タグ名+タグ名)*数値`
など

![multi.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/580654fb-a72a-299b-2a70-bcc745a48fe6.gif)


##6. CSSプロパティを作成
CSSでもエメットを使うことができる。

![css.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c0dd9418-27e4-0cf9-8c40-afea61966d90.gif)


###backgroundcolor

```:
bg　->　background: #000;
bg#ccc -> background: #ccc;
```


###margin, padding

```
p6 -> padding: 6px;
p20-60-20 -> padding: 20px 60px 20px;
```

```
m8 -> margin: 8px;
m10-20-15-8 -> margin: 10px 20px 15px 8px;
```

###font-weignt, font-size

```
fz -> font-size: ;
fz16 -> font-size: 16px;
```

```
fwb -> font-weight: bold;
fw500 -> font-weight: 500;
```

###display

```
df -> display: flex;
db -> display: block;
di -> display: inline;
dib -> display: inline-block;
```

###position

```
por -> position: relative;
poab -> position: absolute;
```

<br>
いちいち記憶するというよりはそれっぽい頭文字や２語を入力すると候補が表示されるので、絞り込んでいけばOK。
【Javascript】配列から特定の値のインデックスを全部取得する

#1, 目的
以下の配列から、４という要素が何番目にあるかを全部取得したい。

```js:app.js
const counts = [4,4,3,2,1,4,5,5,4,4,4,4];
```
# 2, まず

```js:app.js
counts.indexOf(4)
// ０が取得できる
```
これで先頭から検索して最初の１つは取れる。
一括して取りたいがうまい方法が見つからない。

# 3, 次に

```js:app.js
counts.indexOf(4,2)
// 5が取得できる
```
第二引数で範囲を指定できるので、上記のように書けば２番目以降から検索することができる。
したがって、

```js:app.js
index1 = counts.indexOf(4)
index2 = counts.indexOf(4,index1+1)
// ０、１が取得できる
```
これで頭から順に２つ取得できる。

# 4,というわけで
これらをループさせる。
#### (1) 4の数を数える

```js:app.js
const array4 = counts.filter((count) => {
    return count === 4
}).length
```
filterメソッドで４だけの配列を作ってlengthで数える
#### (2) 最初だけ``counts.indexOf(4)``で取得する
```js:app.js
index_4s = []
index_4 = counts.indexOf(4)
index_4s.push(index_4);
```
#### (3) それ以降は``4の数−１``回ループ
```js:app.js

for (let i = 0 ; i < array4-1; i += 1) {
    index_4 = counts.indexOf(4,index_4+1)
index_4s.push(index_4);
}
// => [0, 1, 5, 8, 9, 10, 11]
```
【Ruby】配列の最大値をとる

例えば

```rb:.rb
numbers = [4,50,19,-12,0]
```
この配列から最大値をとるとき

```rb:.rb
max = numbers[0]
numbers.each do |number|
  max = number if number > max
end
puts max
# => 50
```
みたいな面倒なことをしていたのですが、sortメソッドなるものを知ったので

```rb:.rb
puts numbers.sort[numbers.length-1]
# sortで配列を小さい順に並び替えて、最後の値を取得する
```
とても楽になりました。
【Git,GitHub】デフォルトブランチ名を変更する方法



## はじめに
2020年10月にmasterが差別用語にあたる可能性があるためGitHubのデフォルトブランチがmasterからmainに変更されました。その経緯についての深堀りはここではしません。知りたい方は[こちらのURL](https://www.itmedia.co.jp/news/articles/2007/13/news057.html)がわかりやすかったです。

## 問題点
リモートブランチとしてGitHubを用いる場合デフォルトはmainですが、Git（ローカルブランチ）のデフォルトはmasterのままです。このままでは祖先のブランチ名が異なるためGitHubにpushできません。
一度だけならブランチ名を変更して対応してもいいですが、毎回ブランチ名を変更するのは面倒ですよね、そこで、デフォルトブランチ名を変更して自動で生成されるブランチ名を変更しましょう。
対応としては、Gitのデフォルトブランチを変更するかGitHubのデフォルトブランチを変更するか２種類ですね。その両方の方法について記述してみました！

## 変更方法
### 1. Gitのデフォルトブランチをmaster→mainに変更する  

```
git config --global init.defaultBranch main
```
以後、`git init`した時にmainブランチが生成されます

<details>
<summary>設定が反映されたか確認する方法（知りたい人だけ見てください）</summary>方法は色々あると思いますがここでは２つ紹介します
1つ目 
`$ git config --list` を実行して init.defaultbranch=main 
になっていればOK
2つ目 
/home/.gitconfig ファイルを開く(homeはホームディレクトリ、.gitconfigファイルは隠しファイルです)
`[init]
  defaultBranch = main`
になってればOK
</details>

### 2. GitHubのデフォルトブランチをmain→masterに変更する
下記のリンクをクリックした後、mainからmasterに変更してUpdateしてください
https://github.com/settings/repositories

## さいごに
mainを使うのかmasterを使うのか、developなどのそれ以外の名前を使うのかは会社やチーム事に違うと思いますが、上記の方法で変更できますね。以上です！
「1/3を2進数で表せますか？」を参考にWolframAlphaとSymPy Liveでやってみた。 


(オリジナルポスト)
https://qiita.com/mitsu4310/items/69b0fb23114743b1059d

#WolframAlphaで
1/3の2進数
https://www.wolframalpha.com/input/?i=1%2F3%E3%81%AE2%E9%80%B2%E6%95%B0&lang=ja

入力解釈:
1/3 を 2進数 に変換する
結果:
0.01010101010101..._2


#SymPy Liveで
以下サイトに、print文を貼り付けて下さい。
https://live.sympy.org/

print(bin(1/3))
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: 'Rational' object cannot be interpreted as an index

参考
print(bin(10))
0b1010

bin(10)
衝撃的経験を利用して英語を覚える

#はじめに
どういう契機を利用してもいいが、
英語のレベルをそれなりに使えるレベルに上げたいと思う。

ここでは、**衝撃**を受けた経験を契機として、
その際に出会った英語を（英語の使い方）を覚える。

#衝撃１:collision::sob:
##　場面
https://www.cbinsights.com/research/2020-top-100-ai-startups-where-are-they-now/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342207/1036ee44-f5c6-305b-abd4-6064a0900a85.png)

## 学んだこと

|英文|Please enter a valid business email.|
|:---:|:--|
|日本語訳|有効なビジネス用メールアドレスを入力してください。|
|学んだこと|この場面は、`valid`　なんだ。|

#衝撃２:collision::collision:
##場面
https://wired.jp/

↓　有名な技術サイト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342207/ee9d5c36-c54e-0ccb-96ad-23d9fdeb16ff.png)


## 学んだこと

|英文|wired|
|:---:|:--|
|日本語訳|〈米俗〉興奮した、神経過敏な　　???（どういう意図での命名かは、別途調べるとして。）↓|
|学んだこと|weird（変な）:no_good::no_good:とは違う単語なんだ。


#衝撃３:collision:
##場面
AI関連のYoutubeを見ていて。。。

## 学んだこと

|英文|gigantic|
|:---:|:--|
|日本語訳|	巨大な、ぼう大な、巨人のような|
|学んだこと|適当な発音として、ギガンテック　、ということにしていた。実際は、**じゃイぎャンティク**らしい。|


#衝撃４:collision:
##場面
ちょっと、英語で表現しようとしてて。。。

## 学んだこと

|英文|deprecated,　obsolete|
|:---:|:--|
|日本語訳|非推奨、廃止|
|学んだこと|deprecated： 使えるけど非推奨<br>obsolete： 使えない。|
| 参照した記事|http://var.blog.jp/archives/70794251.html|

#衝撃５:collision:
##場面
Youtubeを聞いてて。。。

## 学んだこと

|英文|（これは、適当に発音確認のために、作った単語列です。）the Doppler effect, the Doppler effect, the Doppler effect, the effect, the effect, the effect, the general effect, the general effect, the general effect, the photoelectric effect, the photoelectric effect, the photoelectric effect, the effect of sharing, the effect of sharing, the effect of sharing|
|:---:|:--|
|日本語訳|（省略）|
|学んだこと|the - effectの並びの場合には、本当に、**”ざっ”が、”ずぃ”になるんだ。**|




#衝撃６:collision:
##場面
Attention is all you needの論文にて。

## 学んだこと

|英文|The third is the path length between long-range dependencies in the network.|
|:---:|:--|
|日本語訳|（省略）|
|学んだこと|between A and Bじゃなくて、**between Cs**の形がある。当たり前ですが。。。|


#衝撃７:collision::collision:
##場面
英語のヒアリングをしていて、pathとpathsは、全く、同じに聞こえるのだが、
以下のサイトの「英語カタカナ」変換をすると、
https://www.sljfaq.org/cgi/e2k_ja.cgi
pathsが、「パーズズ」と出て、
ほんまかいなと思い、以下の文を、Google翻訳で読んでもらうと、**確かに、全然、違っていた！！！**並べて聞くと明らか。ただし、通常の文中では、全く、聞き分けられず。
path path path paths paths paths path path path
（途中3連続のみ、paths)
おそるべし。

## 学んだこと

|英文|path paths|
|:---:|:--|
|日本語訳|（省略）|
|学んだこと|pathsは、パーズズ。ただし、これをどう発音するかは、ちょっと、しばらく無理。pathとは違います。|

#他の無理やり英語のレベルを上げようとしている記事（本人）
[深層学習。自分を過学習させてみて、様子を観察する[英語リスニング編]。（いま、800回ループ中。まだ、過学習未達成。）](https://qiita.com/enoughspacefor/items/9042ebbbb198c0e42922)
Python：PythonでVlookup的な事をやってみた#3

# どんだけVlookUPが好きなのか問い詰めたい
　　　もー3回目のネタ､でもこれが一番シンプル
　　　PandasのSeries.replaceで置き換えしてます｡
　　　ただ､Series.replaceは重いので大量のデータの置き換えは時間がかかるかも

# 目的

　　　在庫情報など日々変わる数値をデータベースから読み込んで指定のデータに流し込んで
　　　指定フォーマットで書き出しする。

　　　使用インタープリタ：Python3.8

　　　---投稿者の作業環境---
　　　Windows10Pro 64Bit
# 参照元データ
　　　以下のようなデータベースがあった場合に､
　　　商品マスタに対して､販売実績を当て込みたいケースを想定

●商品マスタ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244225/ebc42142-8a1e-6dbc-17df-ebd85820806b.png)
　
●販売実績
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244225/bb7be382-8f49-0c97-3ab0-062770c0aafc.png)


# コード
```Using_SeriesReplace.py
import pandas as pd

#商品マスタ取り込み
df_Mst = pd.read_excel('C:/商品マスタ.xlsx',encoding="cp932")
#販売実績取り込み
df_Sales = pd.read_excel('C:/販売実績.xlsx',encoding="cp932")

#商品マスタに対して､同一のキーを持つカラムを作る：カラム名は販売実績
df_Mst.insert(2,'販売実績', df_Mst['JANコード'])

#先程作った､カラム名のキーと同一のキーを持つ｢販売実績｣の行のデータを当て込む
df_Mst['販売実績']= df_Mst['販売実績'].replace(df_Sales ['JANコード'].to_list(), df_Sales ['販売実績'])

print(df_Mst)
```

# 出力結果
あとはCSVやエクセル､SQliteのDBで書き出す等お好きな形で~~
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244225/3b46e2bb-bf7d-7ecb-f286-fbe76b1e6d77.png)
Ubuntu20.04のターミナルでtab補完や上キーが使えない

### 発生した現象
Ubuntuサーバーに新しいユーザーを作り、作業しようとしたのですが、**tabキーを押してもパスが補完されず謎の空白が挿入され**、**十字キーの上を押すと`^[[A`という謎の文字列が挿入されました**

### ググってみると
[こちら](https://stepney141.hatenablog.com/entry/2018/12/30/202712)のサイトがヒット
書いてある通りに試してみましたが、改善せず

### 解決法

```bash:terminal
$ cat /etc/passwd

中略
username:x:1001:1001::/home/username:/bin/sh
```
catで`/etc/passwd`の中を見てみると、下の方にこんな記述があります。

これを下のように変更

```bash:terminal

$ vi /etc/passwd

中略
username:x:1001:1001::/home/username:/bin/bash

```
これで補完されるようになりました
詳しくは[こちらの方のページ](https://qiita.com/_dakc_/items/5a6a07c4dca0c1e14aac)を見てください。
Rails 6.1.3.2以降で追加される予定の機能のピックアップ

## はじめに
Railsの6.1.3.2リリース以降にmainブランチに追加されていて良さそうな機能をピックアップして紹介させていただきたいと思います。
なお、ActionCable、ActionMailbox、ActiouText、ActiveStorageは自分の業務で使っていないので良い機能か判断できないため除いています。

## Actionview

`config.action_view.image_loading`が追加されています。

```ruby
config.action_view.image_loading = "lazy"
```
と設定すると`image_tag`の`:loading`オプションのデフォルト値が`lazy`
となります。
今の業務ではサイト全体に画像のlazyloadingを有効にするために専用のヘルパーを用意していたのですが、これでそれが不要となります。

## ActiveRecord
### ActiveRecord::Relation#excludingメソッドの追加

以下のように指定された1件のレコードやコレクションで除外できます。

```ruby
Post.excluding(post)
Post.excluding(post_one, post_two)
```

associationsでも動作します。

```ruby
post.comments.excluding(comment)
post.comments.excluding(comment_one, comment_two)
```

`Post.where.not(id: post.id)`(1件のレコードの場合)と`Post.where.not(id: [post_one.id, post_two.id])`(コレクションの場合)の短縮表記になります。


### すべてのスコープ条件を反転するinvert_whereメソッドの追加
スコープの呼び出しの後にinvert_whereを呼ぶとスコープの条件が反転します。

#### 例
```ruby
class User
  scope :active, -> { where(accepted: true, locked: false) }
end

User.active
# ... WHERE `accepted` = 1 AND `locked` = 0

User.active.invert_where
# ... WHERE NOT (`accepted` = 1 AND `locked` = 0)
```

## 存在する関連のみを取得するwhere.associatedの追加


### Before:
```ruby
account.users.joins(:contact).where.not(contact_id: nil)
```

### After:
```ruby
account.users.where.associated(:contact)
```

where.missingの反転となります。

## 参考
https://github.com/rails/rails の各パッケージのCHANGELOG.mdを参照しピックアップしました。
Windows Insider Programへの登録からPower Automate Desktopを利用する（Build 21337）


皆さんこんにちは、Microsoftの川端です。

<b>
注意：本内容で提供されているWindows 10ビルドは製品化前の最も早期のDev(開発）チャネルでの提供ですので、Microsoftからのサポートは受けられません。
自己責任でお試しいただくようにお願いいたします。また、本手順はお使いのWindows 10環境によって手順が異なり、動作が異なる可能性があります。
</b>


2021年3月17日にWindows 10 Insider Preview Build 21337が発表されました。
今後のWindows Updateの更新タイミングで、公式にPower Automate DesktopがWindows 10 OSにプリビルドされた形で配信されることが予定されています。

本記事の元になっているのは以下のページ内容です。
・公式（英語）アナウンス：
https://blogs.windows.com/windows-insider/2021/03/17/announcing-windows-10-insider-preview-build-21337/

また、吉田さんの記事も参考にさせて頂きました。
・吉田の備忘録 - Windows 10 Insider Preview Build 21337 で Power Automate Desktop が標準搭載：
https://memo.tyoshida.me/en/power-platform/power-automate/windows-10-insider-preview-build-21337-includes-power-automate-desktop/


###Windows Insider Programへの登録手順

Windowsの設定から[更新とセキュリティ]を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/f0912c02-b59b-93e7-c551-bf400f4f8f23.png)


Windows Insider ProgramでDevチャネルを選択します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/03458b32-d42e-d4a7-5847-561b485bda2d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/e93b95eb-5db4-b4a4-ef92-dbeb6c70ddcc.png)


こちらを設定すると、Windows Update内に利用可能な更新プログラムとして「Windows 10 Insider Preview 21337.1000(rs_prerelease)」が出てくることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/406fc8cf-5d3d-0d9c-33e3-8d8c26863c5b.png)


Updateをかけ、OSを再起動するとスクリーン右下にInsider Previewでの利用状態が示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/fe77b62a-f645-9059-6924-110866756d22.png)

スタートメニューのWindowsアクセサリの中にPower Automateとしてアプリケーションが登録されていることが確認できました。
また、このアップデートで同様に同梱されているWindows Terminalも確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/c865b59f-dc84-1067-c798-096c1c365e90.png)

初回起動時にはPower Automateの初期設定プロセスが走り、その後セットアップされたPower Automate Desktopが利用できるようになっているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/808554/ce30a599-ce70-885f-7261-700cd5423301.png)
Tableau Prepでデータに連番（行番号）を採番する

#はじめに

分析したいデータに、連番となるユニークな番号やIDがふってあるといろいろ便利だったりします。
（エクセルのROW関数のイメージですね）
ですが、それが無かった場合Tableau Prepで**簡単に**採番できる方法をご紹介します。


# Prepにて

連番を採番したいデータをTableau Prepに取り込みます。

クリーニングステップを追加し、[計算フィールドの作成]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/510acc91-f506-4c75-9609-fc6e6ba01f65.png)

フィールド名は[連番]など、分かりやすい名前をつけておいてください。これがあとで列名になります。
計算式の中には{ ORDERBY [順番に並ばせたい列名]:ROWN_NUMBER()}と記載します。この、**ROW_NUMBER関数がミソ**です。
これで、[順番に並ばせたい列名]（今回は[オーダーID]順）に並ばせた上で連番を採番することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/3b2412cd-7a44-c339-4e37-ab8e96193fbf.png)

連番、という列が追加されているのを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/917b5fa9-a84d-cbfc-4849-f97f3587d0ff.png)

今回は[オーダーID]を指定しましたが、さらに降順・昇順を指定できたり、並び順指定の列を追加することもできます。試しにオーダーIDは昇順で、さらに売上の降順で並べる、という風に指定してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/0a23cd20-9ecc-1b05-3eca-f1629340ac98.png)

連番の結果が変わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/ab52e091-1b09-2726-79e1-1e1aee2e40c3.png)


なお、特に並び順は気にせずにとにかく採番したいだけ！であれば、ORDERBBY句の後はどの列を使っていただいても大丈夫です。
以下画像の例では、ORDERBYの後に[地域]を指定しています。（一応文字コード順に並んでいるのだと思います。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/15a45f53-91e5-fe18-0af8-57fce9302fa4.png)

#応用編 - 連番で採番する範囲を指定する

ここまでの計算式では、データ全体に対して一意の行番号を採番していくという方法をご紹介しました。
PARTITION関数と組み合わせれば、採番する範囲を柔軟に変更してあげられます。
例えば、「オーダーID順に連番の番号をふるけれども、オーダー日が変わったらまた1から採番したい」場合、以下のように書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/884972/1b348e3a-df54-3ba8-f6c5-4e069668fe64.png)


以上です。

Tableau PrepではINDEX関数は使えませんが、その代わりROW_NUMBER関数を使うと簡単に連番（行番号）の採番ができました！
ROW_NUMBER関数の使い方は**[公式ドキュメント](https://help.tableau.com/current/prep/ja-jp/prep_calculations.htm)**でも紹介されているので、興味のある方は併せてご参照ください。

おわり
Vagrant + Nuxt.jsでホットリロードが効かないときのnuxt.config.jsへの設定

小ネタ。Vagrantで`config.vm.synced_folder`を使ってWindowsとのフォルダ同期をし、そこにNuxt.jsをインストールしたものの、SSHでnpm run devしながらpages配下のファイルとかいろいろいじってもホットリロードがされなかったのでどうにかしました。

Vagrant、どうやらWindows環境だとシンボリックリンク系の権限の問題でうまくnpm initが走らなかったりして[^1]、結局NuxtのインストールもいったんVagrantからじゃなくてWindowsのPowershellから`package.json`直書き、`npm install`を叩くとかいうゴリ押しをしたので、その辺が原因な気がします。

# TL;DR
`nuxt.config.js`に以下の記述を追加するだけ。

```JavaScript:nuxt.config.js
export default {
  watchers: {
    webpack: {
      poll: true,
    }
  }
}
```

# 詳細
いろいろ調べてる中で、
【Vue.js】Vue CLI 3のホットリロードをWindows+Vagrant環境で動かす | kawadeblog https://kawadev.net/vue-cli3-hmr/
↑
こんな記述を見つけてホットリロードがwebpackのwatcherを使って動いてることに気付いたので、`nuxt.config.js`に明示してあげればいいと考えて実際に書いてみたらうまく動きました。

実際の書き方については
watchers プロパティ - NuxtJS https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-watchers
↑
ここを参照しました。もしうまく動かなかったらリファレンス通り、`poll: 1000`とかで設定してもいいかも。

# まとめ
Windowsはもう少しデベロッパーに優しくなってください。

[^1]: https://tokibito.hatenablog.com/entry/2018/02/28/012014 ←こういうのが原因らしい。いろいろ対策しても結局直らなかったけど……
Reactでaxiosを用いてActiveStorageにデータを保存する

以下のサイトがわかりやすかった。
http://tolarian-academy.net/create-new-rails-react-app-pt2/

S3に画像の投稿…あたりからみていただけると良いかと

## ①画像データをStateに保存する

ここでの注意点は2つ

- file_fieldはvalueを変更できないのでスクリプト内でファイルを取得する別メソッドを使う
- file_readerを使う

```jsx
<FormBlock>
  <label htmlFor="avatar">アバター画像</label>
  <input type="file" name="avatar" id="avatar" accept="image/*,.png,.jpg,.jpeg,.gif" onChange={props.change}/>
</FormBlock>
```

こういうfile_fieldがあったときに画像を選択した瞬間にStateに画像情報を入れる。後でStateの情報をaxiosでサーバーに送るためです。

onChangeイベントではfile_filedに変更があった際に以下のように処理を行う。
ファイルはstateのuserのavatarとして管理され、axiosではuserというキーでuser全体を送信している。

```jsx
const file = e.target.files[0];
const reader = new FileReader()
reader.onload = () => { 
  user.avatar.data = reader.result
  user.avatar.filename = file.name
}
reader.readAsDataURL(file)

this.setState({
  user: user
})

axios
  .post('/api/v1/users', {user: this.state.user} )
```

file情報はchangeイベントのe.target.files[0]で取得できる。
reader.onloadでファイルを読み込む記述を入れる。ここではfileのdataとなる文字列をreader.resultで取得し、
fileの名前をfile.nameで取得する。これらの情報がActiveStorage::Bolbsの保存で必要になる。

## ②コントローラーで暗号化された画像データを復元する

今のままだとファイルの情報は文字列なのでdecodeしてファイル情報に変換する必要がある。

```ruby
def create 
  @user = User.new(sign_up_params)
  if @user.valid?
    if params[:user][:avatar]
      blob = ActiveStorage::Blob.create_after_upload!(
        io: StringIO.new(decode(params[:user][:avatar][:data]) + "\n"),
        filename: params[:user][:avatar][:filename]
        )
      @user.avatar.attach(blob)
    end
    @user.save 
    render json: { user: @user }
  else
    render status: 422, json: { errors: @user.errors.full_messages } #手動でステータス入れないと200になるぽい
  end
end


def decode(str)
  Base64.decode64(str.split(',').last)
end
```

今回は以下の部分で画像データを保存し、ユーザーのデータに添付する

```ruby
if params[:user][:avatar]
  blob = ActiveStorage::Blob.create_after_upload!(
    io: StringIO.new(decode(params[:user][:avatar][:data]) + "\n"),
    filename: params[:user][:avatar][:filename]
    )
  @user.avatar.attach(blob)
```

先程、axiosを使った際のparamsが

```jsx
{user: this.state.user}
```

となっており、この構造がこんな感じ

```jsx
this.state = {
  user: {
    nickname: '',        
    email: '',        
    password: '',        
    password_confirmation: '',        
    avatar: {
      data: '',
      filename: ''
    }  
  },
```

なのでfileのdataは 

```ruby
params[:user][:avatar][:data]
```

fileの名前は

```ruby
params[:user][:avatar][:filename]
```

で取り出せる。

まずはファイル情報を複合する処理を行う。それが以下の処理

```ruby
def decode(str)
  Base64.decode64(str.split(',').last)
end
```

クソ長い文字列を短くしてくれている感じ。

filenameはそのまま取り出す。

```ruby
filename: params[:user][:avatar][:filename]
```

これで画像が保存できる。あとはuserにattachして完了。

```ruby
@user.avatar.attach(blob)
```

## 挙動
こんな感じになります。少しわかりにくいかもですが
![recommended-books-2-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652172/f2a32914-82ac-c258-7007-17406e970db7.gif)
非同期にした割にはあんまり処理が速くなっていないのが気になるところです。


### 調べること

正直コピペでなんとなく実装したのでBase64とかfilereaderの仕組みが理解できていない。これは後で調べる。

とにもかくにもaxiosで画像保存できたので良しとする。
Apple siliconでのrails環境構築記

#目的

* 備忘録
* 体験談を誰かにシェアする時のために
* M1チップを搭載したMacbookを使う場合、ubyonrailsの環境構築に際して特有のつまりを発生させるポイントがあるようで、再び環境構築を行った、もしくは誰かに教える場合にそれを伝えられるように

#対象

M1チップを搭載したMacbookでrailsの環境構築をしようとしている人も、そうでない人も

#環境構築

##homebrewのインストール

参考:https://prog-8.com/docs/ruby-env
詰まったところ->インストールできない

```
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

Homebrew is not (yet) supported on ARM processors!
Rerun the Homebrew installer under Rosetta 2.
If you really know what you are doing and are prepared for a very broken experience you can use another installation option for installing on ARM:
  https://docs.brew.sh/Installation
```

対策:https://qiita.com/shira-shun/items/0f6213f4923cb5544367


##rbenvのインストール

参考:https://prog-8.com/docs/ruby-env

##rubyのインストール

参考:https://prog-8.com/docs/ruby-env

詰まった→rubyのバージョンが正しく反映されていない（ruby --version）

対策:https://qiita.com/opiyo_taku/items/3312a75d5916f6cd32b1
これでちゃんと指定したrubyのバージョンに

##railsのインストール

参考:https://prog-8.com/docs/rails-env

```
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.
```

対策:https://qiita.com/aki319809/items/9de7f14285cd0c69e2d2

##まとめ・その他

私は元々あったプロジェクトをクローンして環境を引き継いだので、この後git cloneとプロジェクトで用いていたpostgresqlのインストールと導入をして終わった。

postgresql参考:https://qiita.com/krtsato/items/4565051608a63f11b316

M1チップ特有の詰まりはhomebrewだけであった。

#参考まとめ

* https://prog-8.com/docs/ruby-env
* https://qiita.com/shira-shun/items/0f6213f4923cb5544367
* https://qiita.com/opiyo_taku/items/3312a75d5916f6cd32b1
* https://prog-8.com/docs/rails-env
* https://qiita.com/aki319809/items/9de7f14285cd0c69e2d2
IPython Notebookを簡単に共有する方法

# はじめに
IPython Notebook（Jupyter Notebook）を報告書に添付するなど、環境がインストールされていない方にシェアする場合の備忘録です。
JupyterLabからpdf生成もありますが、拡張機能のインストールが必要だそうで、HTML出力を選びました。
https://teratail.com/questions/196951

# 参考
こちらを参考にさせていただきました。一気に変換する場合はこの参照先に書かれているコマンドの方が便利です。
https://qiita.com/supersaiakujin/items/1429b7529d858ee4177b

# 準備
報告書に添付する場合、警告を表示したくなかったので、こちらを参考に警告を非表示にしました。
https://note.nkmk.me/python-warnings-ignore-warning/

# 手順
File > Export Notebook As > Export Notebook to HTML
を選び、ファイル名を指定して保存。

# おわりに
これだけ渡せば、結果を見ることはできるようです。
他におすすめの方法、簡単な方法があれば是非教えてください。よろしくお願いします。
【cfn-lint】日本語を含む template.yml を開くと「cfnlint.decode - ERROR - Cannot read file conents:<ファイル名>」が発生する

# 現象

日本語を含む template.yml を開くと「`cfnlint.decode - ERROR - Cannot read file conents:<ファイル名>`」が発生する。

コメント行でもエラーが発生してしまう。  
今回はコメント行と CloudWatchAlerm の AlermDescription に日本語の説明文を入力してしまっていた。

# 環境

- Windows 10 Pro
- Visual Studio Code
- Visual Studio Code 拡張機能 「CloudFormation Linter（vscode-cfn-lint）」

# 解決策

原因は python の UnicodeDecodeError が発生してしまっていたためのようでした。

以下の記事のように Windows10 の設定を変更して回避できるようですが、自分の環境ではこの回避策は使用できなかったので、日本語を削除するしか方法がありませんでした。

[【Windows】PythonでCP932(Shift-JIS)エンコード以外のファイルを開くとエラーになる問題がとりあえずの解決に至った件](https://qiita.com/Nidhog-tm/items/c7e9d759ce1a0f5c85c6)
【4層目：トランスポート層】ポート番号について

#ポート番号とは

OSI参照モデルにおけるトランスポート層では、ネットワーク層から転送されたパケットを受け取って次にどのアプリケーションにパケットを渡せばいいのか識別しなければならない役割がある。

IPからトランスポートプロトコルにデータを渡すときに、IPからのデータには、IPヘッダーがついている。そのヘッダーの中で、どのトランスポートプロトコルにデータを渡したらよいのかが記されてる。

それと同様、ＩＰから受け取ったデータをトランスポートプロトコルであるＴＵＤ，ＵＤＰでも自分が運んでいるデータが次にどのアプリケーションに渡すのか識別するために、番号が定義されている。

それをポート番号と呼ぶ。
このおかげで、コンピュータで複数動いているアプリケーションを識別したり、アプリが同時に行う複数の通信を識別できる。

#それぞれのアドレスのまとめ

それぞれの層ではアドレスを持っている。

|層||データリンク層|ネットワーク層|トランスポート層|
|---|---|---|---|---|
|ｱﾄﾞﾚｽ||MACアドレス|IPアドレス|ポート番号|

#通信の処理

##サーバーとクライアント

ＴＣＰ／ＵＤＰ　においてアプリケーションプログラムは、クライアント/サーバーモデルで作られている。
情報を要求する側が、クライアント。情報を提供する側がサーバーと呼ばれる。
サーバーは、常にプログラムを起動していて、クライアントからの要求に備えていなければいけない。でないと、情報を要求されたときにデータを返すことができない。

##デーモン
ＵＮＩＸでは、サーバープログラムをデーモンと呼ぶ。
例えば、HTTPのサーバープログラムは、httpd（HTTPデーモン）とよばれる。

要求がどのデーモンつまりサーバーに向けられたかは、受信したパケットの宛先ポート番号を調べればわかる。

TCPのコネクションの要求のパケットを受信した場合、ポート番号が23番ならtelnetdで、80番ならhttpdにコネクションを確立する。

そしてそれ以降の通信の処理をデーモンが待つことになる。

TCP,UDPのトランスポートプロトコルは、受信したデータが誰宛てかをこのポート番号から判断している。

![port.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1162957/c7c6017a-5db7-dabc-96f3-25559bf3784b.png)



##ポート番号の割り振り

通信を行う前にポート番号を決定する必要がある。決定方法は二つある。

###標準で決められている
静的に決められていて、あらかじめよく使われるアプリケーション事に割り振られている。（主にサーバー側）
これらの番号をウェルノウンポート番号(well-known Port Number)と呼ばれている。

ウェルノウン番号は、0~1023まで割り当てられているので、この番号を他のアプリケーションで使うことはできない。

詳しくどのように割り当てられているか見たい方は以下をご覧ください。

https://ja.wikipedia.org/wiki/TCP%E3%82%84UDP%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%83%9D%E3%83%BC%E3%83%88%E7%95%AA%E5%8F%B7%E3%81%AE%E4%B8%80%E8%A6%A7#%E7%99%BB%E9%8C%B2%E6%B8%88%E3%81%BF%E3%83%9D%E3%83%BC%E3%83%88%E7%95%AA%E5%8F%B7_(1024%E2%80%9349151)

他にも正式に登録されているポート番号があり、1024~49151まで登録されている。しかしこちらは、ウェルノウンと違い他のアプリケーションに使っても問題ない。



###動的（ダイナミック）に割り当てる方法
サービスを受ける側(クライアント）は、ポート番号が決まっていないので、自分で番号を決定せずに、OSに任せることができる。

OSは、番号が重複しないようにポート番号を割り当てる。

これにより、同じクライアントプログラムから複数のTCPコネクションを確立した場合でも、識別可能になっている。

動的に割り当てるポート番号は、49152~65535までの番号が割り当てられている。しかし、多くのシステムでは、これを無視して1024以上の使われていないポート番号が順番に利用されている。

##ポート番号とプロトコルの依存性

ポート番号はしようされるトランスポートプロトコルごとに決定される。
つまりTCPとUDPでは、同じポート番号をそれぞれ別の目的で使うことが可能。

データがIP層（ネットワーク層）に到達すると、IPヘッダ中のプロトコル番号がチェックされ、それぞれのプロトコルへ渡される。
TCPならTCPモジュール、UDPならUDPモジュールがポート番号を処理することになる。
プロトコルごとに独立した処理を行うため、プロトコルごとに同じポート番号を使用することができるということである。

ウェルノウン番号に関しては、プロトコルに関係なく設定されている。なので、80はHTTPとTCPもUDPでも決まっている。

##まとめ

ポート番号とは、トランスポート層がどのアプリケーションにIP層から受け取ったデータを渡すかを識別するための番号である。

**参考**
マスタリングTCP/IP
フレネル＆セルによるアニメ調シェーダー


影はセル、ハイライトはフレネルで表現することを目的としたShaderです。

![AnimShader.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1146955/646fb725-c76d-bb7a-901e-e168829e23e4.png)

光の方向はDirectional Lightに依存しますが、ハイライトと影の濃さはマテリアルで操作します。

ShadowPower 影の強さ
ShadowCutOff セル影の範囲
ShadowOpacity 影の濃さ

FresnelColor　フレネルの色
FresnelPower　フレネルの強さ
FresnelOpacity フレネルの濃さ

```C#:CelLikeShader
Shader "Anime/CelLikeShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}

        _ShadowPower("Shodow Power",Range(0.0,1.0))=0.1
        _ShadowCutOff("Shadow CutOff",Range(0.0,1.0))=0.25
        _ShadowOpacity("Shadow Opacity",Range(0.0,1.0))=0.5

        [Space]
	_FresnelColor("FresnelColor", Color) = (1,1,1,1)
	_FresnelPower("FresnelPower", Range(1, 20)) = 2
	_FresnelOpacity("FresnelOpacity"     , Range(0, 1)) = 1

    }
    SubShader
    {
       
        LOD 100
        Pass
        {
        Tags { "LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc" 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

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

                float3 viewDir : TEXCOORD1;
                float3 lightDir : TEXCOORD2;
                float3 normal : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            half _ShadowPower;
            half _ShadowCutOff;
            half _ShadowOpacity;

            half4 _FresnelColor;
            half _FresnelPower;
            half _FresnelOpacity;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.viewDir = normalize(UnityWorldSpaceViewDir(v.vertex.xyz));
                o.lightDir = normalize(UnityWorldSpaceLightDir(v.vertex.xyz));
                o.normal = UnityObjectToWorldNormal(v.normal);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                half vdotn = max(0,dot(i.normal, i.viewDir));
                half ldotn = max(0,dot(i.normal, i.lightDir));

                //影部分
                half shadow=pow(vdotn,_ShadowPower)*pow(ldotn,_ShadowPower);
                shadow=saturate(step(_ShadowCutOff,shadow)+(1-_ShadowOpacity));

                col=lerp(col,col*col*shadow,1-shadow);

                //フレネル部分
                half4 fresnel =_FresnelColor* pow((1.0 - vdotn) * 0.5 + 0.5, _FresnelPower);
                col.r=lerp(col.r,min(1,col.r+fresnel.r),_FresnelOpacity);
                col.g=lerp(col.g,min(1,col.g+fresnel.g),_FresnelOpacity);
                col.b=lerp(col.b,min(1,col.b+fresnel.b),_FresnelOpacity);

                return col;
            }
            ENDCG
        }
    }
}







```
nicEdit 入力欄への書き込み

軽い備忘録。

ゆえあってウェブアプリケーション内の `textarea` に自動書き込みをすることになった。ところがこいつは真正の `textarea` ではなく nicEdit でリッチテキストを入力できる textarea もどきだった。件の場所のソースはこうなっている。

```html
<div style="..." class=" nicEdit-main  " contenteditable="true"><br></div>
```

とりあえず ```driver.find_element_by_xpath(...).innerHTML = "..."``` としてみたがスルーされてしまう。

インスペクターを表示させて実際に入力してみると、

```html
<div style="..." class=" nicEdit-main nicEdit-selected " contenteditable="true"><br></div>
```

と変化していた。一度クリックすると入力受付状態へ遷移するらしい。

今回は次の手順で書き込むことができた。

```python
driver.find_element_by_xpath('//div[contains(@class, "nicEdit-main")]').click()
driver.find_element_by_xpath('//div[contains(@class, "nicEdit-main nicEdit-selected")]').send_keys(...)
```
まとめてremoveEventListenerする

# ターゲットにイベントリスナーを登録/解除する

```js:クラス定義
/**
 * イベントマネージャー
 */
class EventManager {
  /**
   * イベントマネージャーのコンストラクター
   * @param {Element|Document|Window} node addEventListenerを持つオブジェクト
   */
  constructor(node) {
    this.node = node;
    this.funcs = [];
  }
  /**
   * イベントリスナー登録
   * @param {string} type イベント名
   * @param {Function} fn リスナー関数
   */
  add(type, fn) {
    this.funcs[type] ??= [];
    this.funcs[type].push(fn);
    this.node.addEventListener(type, fn);
  }
  /**
   * イベントリスナー全解除
   * @param {string} type イベント名
   */
  removeAll(type) {
    if (!this.funcs[type]) return;
    for (let func; func = this.funcs[type].pop();) {
      this.node.removeEventListener(type, func);
    }
  }
  /**
   * イベント発行
   * @param {string} type イベント名
   */
  dispatch(type) {
    const event = new Event(type);
    this.node.dispatchEvent(event);
  }
}
```

```js:使用方法
// window用のイベントマネージャーを作成
const windowEventManager = new EventManager(window);

// イベントリスナー登録
windowEventManager.add('hogehoge', _ => console.log(1));
windowEventManager.add('hogehoge', _ => console.log(2));
windowEventManager.add('hogehoge', _ => console.log(3));

// イベント発行
windowEventManager.dispatch('hogehoge');
// >> 1
// >> 2
// >> 3

// イベントリスナー全解除
windowEventManager.removeAll();

// イベント発行
windowEventManager.dispatch('hogehoge');
// >> 
```
pip install psycopg2がうまくいかなかった時の備忘録

【環境】
Ubuntu(EC2) 20.04
Python 3.8.5
virtualenv 20.0.17

Ubuntu20.04にvirtualenvを作成して、その中でpipパッケージのインストールを行っていました。

【背景】
ローカル環境（Windows）で作ったDjangoのポートフォリオをサーバーに反映していたらrequirements.txtをダウンロードした時に引っかかりました。

コマンドラインを見るとpsycopg2がダウンロードできないらしい...

##早い話wheelが必要みたい

```bash
pip install wheel
```

これをやってからpip install psycopg2を実行したらイケました。
RSpecの導入の流れ

## 初めに
今回RSpecを導入にあたって簡単ですが、メモがわりに記事を残しておきます。
## RSpecの導入
まず初めにGemfileに以下場所にコードを付け加えます。

```
group :development, :test do
#省略
end
```
do~endの中にgem 'rspec-rails', '~>4.0.0'を付け加える。

```
group :development, :test do
＃省略
gem 'rspec-rails', '~>4.0.0'
end
```
今回はバージョンも指定するので'~>4.0.0'を書いています。

```
bundle install
```
をターミナルに打ち込みます。
次にRSpecを読み込みます。ターミナルに以下のコマンドを入力します。

```
rails g rspec:install
```



すると以下のようにファイルが生成されると思います。
![](https://gyazo.com/798065d643d47b54ccd376dbb023ec87.p)

生成された.rspecファイルに以下のコードを記入します。

```
--require spec_helper
```
こちらのコードはターミナル上にテストコードの結果を可視化するためにコードになります。
### テストコードを書くファイルの生成
```
rails g rspec:model #ファイル名
```
ファイルが生成されたと思います。
以上でRSpecの導入は終了です。
生成されたファイルにテストコードを書いていきます。
## 最後に
長くなりましたので次回は単体テストコードについて記事にしていこうと思います。
learnstorybookに貢献しました

[learnstorybook.com](https://github.com/chromaui/learnstorybook.com)に貢献しました。  
learnstorybookは何かと言いますと、[Storybook](https://storybook.js.org/tutorials/)に関するチュートリアルです。

私が出した[PR](https://github.com/chromaui/learnstorybook.com/pull/436)を先に貼ります。

## 背景

今兼任しているチームで新しい機能を作ろうとしています。  
フロントエンドはVueを利用しています。  
あるチームメンバーがStorybookを使おうよと言い出しました。

そもそもStorybookは何なのかすらわからない私は、
ちょっと興味を持ってネットで検索しました。  
ところでlearnstorybookを見つけた。

（自分が知らない技術はいっぱいあるので、全部自分でカバーすることが基本的に不可能ですし、
周りの人の意見やアドバイスを吸収することが大事だなと感じました。
自分が知っていることをどんどん言い出すことも大事。
チームの知識（財産）になるからです。　→　あまり多く言いすぎるとよくないかも。。。

## なぜ貢献したいのか？

正直に言って今子育ての仕事もあるし、
新しい機能を作っているし、
なかなか時間がないのが事実です。

しかし、ずっとコミュニティに貢献したいと思い続けているので、
今回はこういう貢献しやすい形を選びました。

理由：

- 自分の知識を活用できる：中国語に翻訳するなので
- 英語の勉強になれる
- Storybookに対する理解が深められる
- 社会的貢献？

## 実際にやったこと

### Fork

まず[大本](https://github.com/chromaui/learnstorybook.com)からソースコードをフォークする。  
フォークされた[レポジトリ](https://github.com/zydmayday/learnstorybook.com?organization=zydmayday&organization=zydmayday)。

![origin-github-repo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/774617/2009b204-1348-fa78-eeed-3b43da3230ba.png)


### Branch

自分のレポジトリで修正用のブランチを切る。

例えば私の場合に`intro-to-stroybook-vue-zh-cn`というブランチを切りました。  
修正は全部自分のブランチで行います。


![my-github-repo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/774617/1dcc5395-7e59-67ef-abbc-a9771ab3dd75.png)


### Push

自分で修正したい内容を修正して、`git push`します。

Push後、GitHubで確認すると、新しい`Pull Request(PR)`を作成することができます。

PRを作成したら、大本のrepoと紐づけて、大本のrepoの管理者がもしこの修正がほしい場合にマージしてくれます。

P.S. ちょっと今回自分の手順が間違ってました。  
微修正などはPRだけでいいですけど、
今回の大きな修正ならばまずissueが必要らしい。

普通にマージされるまでには元のプロジェクト関係者といろいろコミュニケーションしないといけないと思います。  
それも楽しみの一つである。

### Sync

もし自分のForkと大本と同期したい場合に、
この[doc](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)を参考してください。

## learnstorybookだけの話

### Local Server

翻訳だけだと言ってもローカルで翻訳された文章を確認するために、  
ローカルのセットアップが必要です。

`yarn`でローカルサーバーを立ち上げているので、
事前に自分のPCに`yarn`をインストールする必要があります。

### 差し込み

**自分から問題発見**  
自分が翻訳中にもいくつか問題を見つけて、それをなるはや[報告](https://github.com/chromaui/learnstorybook.com/pull/436#issuecomment-785870527)していきましょう。

**向こうからのリクエスト**  
修正の期間が長い場合に、
途中で大本のソースが変わる場合があります。  
私の場合に、スタートから完成まで大体一ヶ月半をかかってました。  
途中で大本のソースが二回更新され、私もその更新を対応しないといけないことになりました。

これは面倒くさいと思う方はいると思うが、
個人的にはこれはプロジェクトが活発している証だと思って、逆に貢献する甲斐があるのではないかと思いますね。

## 最後

仕事上がりに気分転換でこういった貢献をしていただくこともいいですし、
かっつり何かのプロジェクトを改善したく貢献していただくこともいいですし。

最初は確かに「何かをやらかしたらまずい！」とビビるかもしれませんが、
意外とコミュニティの方が親切なので、
とりあえず第一歩を踏み出しましょう！
Git〜pushしてみよう〜

それでは、前回のGitを実際にPushして共有したいと思います。
前回はリモートリポジトリで作業している状態なので、まだ共有されていません。これを共有する場合はリモートリポジトリに上げる必要があります。
ごっちゃになりやすいので、整理しときましょう。
前回の記事はこちらをどうぞ▶https://qiita.com/akari_0618/items/1b444870e08c7893498e

今回はGit上にある、プロジェクトに入りたいと思います。
リポジトリには大きく2つありました。すでにあるプロジェクトはリモートリポジトリにあります。ではそれを、個人で作業するローカルリポジトリに追加していきたいとおもいます。

#git clone
これは、git上にあるリポジトリをクローンすることができます。
ここでターミナルを開き、入力します。

```
% git clone "SSHコピー"
```
を行います。多分このあたりで秘密鍵を入力することになるかなと思います。
すると自分のパソコンの中にリポジトリを引き入れることができましたね。
そしてここから、cdコマンドを使い、リポジトリの中に入ります。

```
% vim index.html or code .
```
で修正を行います。（個人的に使いやすいなって思ったのはVSコードでした）
中身を確認する場合は、cat index.htmlで確認することができました。

```
% git add index.html
% dit commit -m '修正内容'
% git log
% git push origin ブランチ名
```
リモートリポジトリに上げることができます👍

以上がPushまでの流れです😃
rails非同期いいね機能 Ajax

#前提
認証機能deviseで作成済み（user）
Book投稿機能実装済み

#非同期とは
相手の反応を待たないで、ひょいひょい行動すること
言い換えると、
**画面遷移せずにデータを取得すること**

メリット：レスポンスを待ってる間も他の処理ができる
（通常の同期通信は、ページ遷移してからじゃないと他の処理ができない）

#コントローラー作成

```
$ rails g controller favorites
```

createとdestroy作成
非同期にするので、 redirect_to **** のように実行後の処理は不要

```Ruby:app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController

  def create
    @book = Book.find(params[:book_id])
    favorite = current_user.favorites.new(book_id: @book.id)
    favorite.save
  end

  def destroy
    @book = Book.find(params[:book_id])
    favorite = current_user.favorites.find_by(book_id: @book.id)
    favorite.destroy
  end
end
```
#ルーティング設定

```Ruby:config/routes.rb
resources :books do
  resource :favorite, only: [:create, :destroy]
end
```

ネストさせるとURLが
/books/:book_id/favorite(.:format)
の形になり、パスに親のidが必要(どのbookに対してのいいねなのか判断できるように)

ルーティング確認

```
$ rails routes|grep favorite

book_favorite DELETE /books/:book_id/favorite(.:format)          favorites#destroy
               POST   /books/:book_id/favorite(.:format)          favorites#create
```

#モデル作成

```
$ rails g model Favorite
```

userとbookを外部キーとして持たせる
integer型、_idをつけると外部キーとして判断される

```Ruby:db/migrate/00000000000_create_favorites.rb
class CreateFavorites < ActiveRecord::Migration[5.2]
  def change
    create_table :favorites do |t|
      t.integer :user_id
      t.integer :book_id

      t.timestamps
    end
  end
end
```

```
$ rails db:migrate
```

#関連付け　アソシエーション

```Ruby:app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :book
  belongs_to :user
end

```

```Ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :favorites
end
```

```ruby:app/models/book.rb
class book < ApplicationRecord
  has_many :favorites

  def favorited_by?(user)
    favorites.where(user_id: user.id).exists?
  end
end
```

・favorites.where(user_id: user.id).exists?
（Favoritesテーブルにuser.idが存在するかどうか）

#ビュー作成

部分テンプレート作成

```erb:app/views/favorites/_favorite.html.erb
  <% if book.favorited_by?(current_user) %>
    <p>
      <%= link_to book_favorite_path(book.id), method: :delete, remote: true do %>
        ★(お気に入り<%= book.favorites.count %>件)
      <% end %>
    </p>
  <% else %>
    <p>
      <%= link_to book_favorite_path(book.id), method: :post, remote: true do %>
        ☆(お気に入り<%= book.favorites.count %>件)
      <% end %>
    </p>
  <% end %>
```

・remote: true
（Ajax化非同期通信を可能にする。jsファイルに飛ぶ）

・<%= link_to パス, do %> ~~~~ <% end %>
（~~~~の部分がリンクになる）

```erb:app/views/favorites/create.js.erb
$('#favorites_buttons_<%= @book.id %>').html("<%= j(render partial: 'favorites/favorite', locals: {book: @book}) %>");
```

```erb:app/views/favorites/destroy.js.erb
$('#favorites_buttons_<%= @book.id %>').html("<%= j(render partial: 'favorites/favorite', locals: {book: @book}) %>");
```

呼び出し

```erb:app/views/*****.html.erb
<div id="favorites_buttons_<%= @book.id %>">
  <%= render 'favorites/favorite', book: @book %>
</div>
```

以上復習アウトプットでした。
AppAuthを利用して、HMSなしでHUAWEI IDログインを実現する

# HUAWEI IDログインの実装の振り返り
以前、HUAWEI IDログインの実装について紹介しました。

- [HMS Account Kit実装入門](https://qiita.com/Rei_2020/items/81001ffc6299e56401c8)
- [HMS Account Kit実装入門（Authorization Code編）](https://qiita.com/Rei_2020/items/3eb5cb484be0cea9c640)
- [HMS Account Kitの利用時にHuaweiIdAuthButtonも合わせて使いましょう](https://qiita.com/Rei_2020/items/db1f11c950c3ddb30ad3)

しかし、この実装方法はHMSを必要とします。HMSが入っていない端末だと動作しません。

今回はHMSなしの実装方法を紹介したいと思います。

# 前準備

1. AppGallery Connectでプロジェクトとアプリを登録します。登録後に、<b>APP ID</b>が発行されます。
2. [AppAuth](https://openid.github.io/AppAuth-Android/)をダウンロードします。

```
git clone https://github.com/openid/AppAuth-Android.git
```

# 実装
APP IDが123456789と仮定します。
AppAuthの/res/raw/auth_config.jsonを次のように修正します。

```json:auth_config.json
{
  "client_id": "123456789",
  "redirect_uri": "com.huawei.apps.123456789:/oauth2redirect",
  "end_session_redirect_uri": "net.openid.appauthdemo:/oauth2redirect",
  "authorization_scope": "openid email profile",
  "discovery_uri": "https://oauth-login.cloud.huawei.com/.well-known/openid-configuration",
  "authorization_endpoint_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize",
  "token_endpoint_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
  "registration_endpoint_uri": "",
  "user_info_endpoint_uri": "",
  "https_required": true
}
```

AppAuth-Androidモジュールのbuild.gradleを次のように修正します。

```gradle:buidle.gradle
...
manifestPlaceholders = [
    'appAuthRedirectScheme': 'com.huawei.apps.123456789'
]
...
```

あとはAppAuthをそのまま利用すればよいです。

# ユーザーのプロフィール
ログインしたら、IDトークンがAuthorizationService.TokenResponseCallback.onTokenRequestCompleted()でTokenResponseとして返されます。IDトークンがTokenResponse.idTokenにあります。IDトークンは<b>JWT</b>なので、[JWTDecode.Android](https://github.com/auth0/JWTDecode.Android)などのライブラリを使ってデコードすれば、ユーザーのプロフィールが取れます。

デコードしたIDトークンは次のようなフォーマットになります。

```json
{
  ...
  "aud": "APP ID",
  "sub": "ユーザーID",
  "azp": "APP ID",
  "display_name": "ユーザー名",
  "email": "ユーザーのメールアドレス",
  "picture": "ユーザーの顔写真のリンク"
}
```
VScodeとGitHubを連携する方法

GitHubでクローン作成
↓
VScode開く
↓
VScodeでGitHubにログイン
↓
VScodeで、
左側にあるソース管理アイコンをクリックして、
リポジトリのクローンをクリック
↓
保存先を指定
↓
連携完了
楽天Casaが繋がらない時の対処法3種類

Wi-Fiは飛ぶのに、LTE(4G)を中々吹いてくれない時に参考にしてください。
管理画面のログインを塞がれるまでの3日間システムログを読んだり、図書館でIPv6アドレスの仕組みを勉強したりして到達した答えです。

#そもそもRakutenCasaとは

[楽天モバイルがMNOに参入](https://mitsuboshitx.blog.fc2.com/blog-entry-1552.html)して早くも1年が経過しつつある中、鉄筋コンクリート造りのマンション等は室内・基地局の陰でアンテナ受信レベルが低くなってしまう事が判明（PHSの時代から知ってた）
仮想化を売りにしていた楽天であっても、物理的に電波が届かないのではどうしようもなく
急遽、個人宅や個人商店向けにフェムトセルを配ることになりました
#### NTTドコモのフェムトセルのイメージ
![img_femto-cell_01_pc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275132/c5cbb604-72ed-ab88-cf46-230e6781e7ce.jpeg)

#利用料金など
今の所、無料（実質0円）です
[申し込み条件が少し複雑なので公式ページを確認してください](https://mitsuboshitx.blog.fc2.com/blog-entry-1562.html)

**以前は楽天ひかり契約者のみでしたが、現在はJ:COM等ケーブルテレビ回線でも設置可能**

#Rakuten Casaのスペック

|名称|値|
|---|---|
|DDRメモリ|512MB|
|CPU|ARMv7 プロセッサ revision 5|
|モデム(SoC)|[Qualcomm IPQ4019](https://www.qualcomm.com/products/ipq4019) |
|LAN|1Gbps(Cat5e)|
|4G周波数|1.7GHz帯|
|LTE種類|FDD-LTE 64QAM 2x2 MIMO|
||[下り100Mbps/上り50Mbps程度](https://www.soumu.go.jp/main_content/000579869.pdf)|
|LTE出力|50[mW]|
|Wi-Fi周波数|2.4GHz帯|
|Wi-Fi種類|IEEE802.11ngb|
|Wi-Fi出力|10[mW] ※2|
|Wi-Fi周波数|5GHz帯 |
|Wi-Fi種類|IEEE802.11nac(Wi-Fi5)|
|Wi-Fi出力|398[mW] ※2|
**総務省の1MHzあたり出力x20MHzで計算？**

#仕組み

内部に電波出力を管理するOS(プロセス／常駐ソフト)が2つ入っています
Linux系の組み込み用UbuntuでRaspberry Pi等で有名です
人によってはブラウザにローカル（プライベート）IPアドレス
もしくは192.168.210.1を手打ちすると
OpenWrtのGUI管理画面にログインできたりします
また別の人だとWebアクセスは塞がれていてアプリからしかログインできません
ちなみにいずれの場合も**LTE出力を担っている側はパスワード保護されている**ため入れません
4G出力を担っている側へはLAN6 IPv6アドレスにてTCP/IP通信の橋渡しがされるようです

#シスログ

定期的に世田谷区のIPv4にハートビート（生存確認信号）を送信しています
IPsecでVPNを貼っているため、途中経路となるKDDI au/Softbank YahooBB/NTT東西日本/YCV(J:COM CATV編)など各社に楽天モバイルがフレッツ光sunnet、DMMひかり時代の流れで後日従量課金でトラフィック量分を支払う契約になっているのかもしれません

###サンプルログはこちら
（準備中）

#どうして繋がらないのか？:thinking:
- グローバルIPv6アドレスが取得できていない([プレフィクス](https://www.nic.ad.jp/ja/newsletter/No32/090.html)が関係)
- 上記うまくいかないとDTIのIPv4 over IPv6サービスに接続しようとするらしいけどアクセス集中して満員だと弾かれてしまう
- ルーターに手動設定したGoogle DNS、DMZなどのせい(標準が一番ということも)
- 楽天モバイル側で接続拒否している(コンセント抜き差しが頻繁だとブラックリスト入りする)
- 近所に電波が強い基地局が存在している

###サンプルログはこちら
（準備中）

#結局どうすればいいの？
みなさんが一番知りたいのは多分これ

**1.WANではなくLANポートにONUモデム／既存ルーターを繋いで12時間放置してからWANに戻してみよう**
一番右のLEDが赤く光るけど気にしないこと

**2.RakutenCasaのSSIDにWi-Fi接続して、楽天モバイル公式アプリでスピードテストを実施してみよう**
楽天のIPアドレスとの通信を強制的に流してやる事で目覚めるかも

**3.コールセンター(新規とは別窓口)に電話しよう**
48時間待機を命じられるか、機械交換を提案されたら素直に従う


#（2021-03-19 細かい部分は読者が増えてきたら執筆予定）
nginxサーバのrootディレクトリをどこに置くべきか

# OSごとにindex.htmlの場所が異なる

alpineにnginxを入れて使おうとしたが、今までubuntuしか使っていなかったため、ディレクトリ構造が異なり、rootディレクトリをどこに置くべきかわからなかった。

## コマンドでの検索

```
/ # find / -type f -name "*.html"

/var/lib/nginx/html/index.html
/var/lib/nginx/html/50x.html
```

confファイルも探すことを兼ねて、以下なら一発で調べられる。

```
/ # find / -type d -name nginx | xargs grep -r html

/etc/nginx/http.d/default.conf: location = /404.html {
/etc/nginx/mime.types:    text/html                                        html htm shtml;
/etc/nginx/mime.types:    application/xhtml+xml                            xhtml;
grep: /var/lib/nginx/run: No such file or directory
/var/lib/nginx/html/index.html:<!DOCTYPE html>
/var/lib/nginx/html/index.html:<html>
/var/lib/nginx/html/index.html:</html>
/var/lib/nginx/html/50x.html:<!DOCTYPE html>
/var/lib/nginx/html/50x.html:<html>
/var/lib/nginx/html/50x.html:</html>
/usr/share/nginx/http-default_server.conf:      location = /404.html {
```


## 正解は存在しない？

自分としては、今のところ、index.htmlを配置する場所の候補としては、2つの選択肢があリます。

- インストール時にデフォルトでindex.htmlが配置されている場所に合わせる。
- nginxの公式Dockerイメージで配置されている場所に合わせる

自分は、デフォルトでindex.htmlが配置されている場所に合わせて作っていくことにしました。

## ファイルシステム階層標準

追記 2021/04/22

Linuxのファイルシステムでは、ある程度の役割が予めルールとして決められているようです。
答えとしては、`/var/www`に置くのがよいようです。

> Linuxを含めたUnix互換OSにはFHS(Filesystem Hierarchy Standard)という、ファイルシステムの標準的なディレクトリー構成を定めたものがあり、/varは「頻繁に内容が変化するようなファイルの置き場所」ということが規定されています。

> Webサイトを構成するファイル群（HTMLファイルやスクリプトファイル等）は、当然のことながら頻繁に更新される性質のものなので、その置き場所としては/varディレクトリーの下にWebサイトであることを示すwwwディレクトリーを作り、その中に納めるのが相応しかろうということです。

> 参考 : [nginx - なぜWebデータを/var/wwwに置かないといけないのか？｜teratail](https://teratail.com/questions/186783)
dotenv使うときに便利！VSCodeで全部大文字の定数名とかのメンドーなやつ解決する拡張機能

## これです

[change-case - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=zhengxiaoyao0716.intelligence-change-case)

Qiitaで記事書いている人もいました

https://qiita.com/Anders/items/8fe6c89c9ac969cc4626

---以下、日記です…---

## .envファイルを作って、キーを…

Twitter APIを久々に使おうと思った時

[twitter - npm](https://www.npmjs.com/package/twitter) ここから

```js
var Twitter = require('twitter');
 
var client = new Twitter({
  consumer_key: '',
  consumer_secret: '',
  access_token_key: '',
  access_token_secret: ''
});
 
var params = {screen_name: 'nodejs'};
client.get('statuses/user_timeline', params, function(error, tweets, response) {
  if (!error) {
    console.log(tweets);
  }
});
```

ここをコピーして、

`.env` ファイルを作って

[![Image from Gyazo](https://i.gyazo.com/529dcefe36348098660f476f73a5adac.png)](https://gyazo.com/529dcefe36348098660f476f73a5adac)

以下の部分をコピー

```js
  consumer_key: '',
  consumer_secret: '',
  access_token_key: '',
  access_token_secret: ''
```

`olt+Shift` で矩形選択して前の方に`twitter_`ってつけてみた

[![Image from Gyazo](https://i.gyazo.com/ebdeebff95e45ea9058d6c995db1d1cd.png)](https://gyazo.com/ebdeebff95e45ea9058d6c995db1d1cd)

後ろの方を選択して、` = `に変えてみまして

[![Image from Gyazo](https://i.gyazo.com/822b8fca70285a47a57e67085cd61d01.png)](https://gyazo.com/822b8fca70285a47a57e67085cd61d01)

**ここから拡張機能使います！**

上記の「change-case」拡張機能を入れてから、 `cmd+Shift+p`から、「Change Case upper」を選択

[![Image from Gyazo](https://i.gyazo.com/2ccaf9956ae30d1306594047c1535a5f.png)](https://gyazo.com/2ccaf9956ae30d1306594047c1535a5f)

完成！

[![Image from Gyazo](https://i.gyazo.com/f0c4c5553904683d1d3b3ea6d1ffd967.png)](https://gyazo.com/f0c4c5553904683d1d3b3ea6d1ffd967)

Twitterのデベロッパー管理画面からのコピーは、まぁがんばろう

## ソースコード用に

1.以下の部分をコピー

[![Image from Gyazo](https://i.gyazo.com/5054fb61a8b4fae5499809011ad9d641.png)](https://gyazo.com/5054fb61a8b4fae5499809011ad9d641)

2.また矩形選択して、前の方に挿入

[![Image from Gyazo](https://i.gyazo.com/4c3862540dbc723b4b6cfa0e0e56e57a.png)](https://gyazo.com/4c3862540dbc723b4b6cfa0e0e56e57a)

3.(最後の行にカンマをつけて)「'',」部分を選択肢、コマンド+D

[![Image from Gyazo](https://i.gyazo.com/e0c6c9a8fd2888035d938fe4c3884c0e.png)](https://gyazo.com/e0c6c9a8fd2888035d938fe4c3884c0e)

4.`process.env.`を記入

[![Image from Gyazo](https://i.gyazo.com/4d6244f5cd4c600882b99a3cf60b04c5.png)](https://gyazo.com/4d6244f5cd4c600882b99a3cf60b04c5)

5.2〜4行目の行頭を矩形選択し、左キーを押して1〜3行の末にカンマを入れる

[![Image from Gyazo](https://i.gyazo.com/b4f042f1c0de8a169e047c5d1904c492.png)](https://gyazo.com/b4f042f1c0de8a169e047c5d1904c492)

6.コードへコピペ！！

[![Image from Gyazo](https://i.gyazo.com/9e607aeb4bb6f6f482a5730f99df77cc.png)](https://gyazo.com/9e607aeb4bb6f6f482a5730f99df77cc)

おつかれさまでした！
【Rails】db/schema.rbファイルとは何か？役割

`rails db:migrate`でマイグレーションすると、db > schema.rbが更新される。このファイルの役割は何かについて。

<br>
##schema.rbファイルとは？
schema(スキーマ)と名前がついているだけあって、DBの構造を示すデータ。

**DBのスキーマをそのままキャプチャしたもの。最新のDBの状態が反映されている**。



<br>
##schema.rbの活用
DBの構造を確認したい時に、このファイルを見ればすぐにわかる。

DBやテーブルが多い場合はファイルの中身はかなり長いものになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c0a1e5b6-a8d1-9344-88bc-eb4e40d6b0f7.png)



<br>
**▼schema.rbの生成例**

```:例
#マイグレーションファイルの作成とマイグレーション
$ rails g scaffold Tweeet tweeet:text
$ rails db:migrate
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/f36d44bc-c5b9-065d-3be3-0f9b64c15573.png)

**▼ファイルの中身**

```rb:schema.rb
ActiveRecord::Schema.define(version: 2021_03_19_063511) do

  create_table "tweeets", force: :cascade do |t|
    t.text "tweeet"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

end
```

テーブル名とカラムの型とバリデーションが表示される。
上記例の場合は、テーブル名 tweeets。カラムは、tweet, created_at, updated_atの３つ。


##参考
https://railsguides.jp/active_record_migrations.html

<br>
###マイグレーションの状況を確認する
schema.rbの中にはマイグレーションをいつ実施し、どんなテーブルを作成したかの情報が入っている。以下のコマンドで確認できる。

・`$ rails db:migrate:status`

```
$ rails db:migrate:status

database: db/development.sqlite3

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210319063511  Create tweeets
```
WordPressで固定ページにカテゴリーを付与する方法


WordPressで固定ページにカテゴリーを付与する方法を探していたら参考サイトを見つけたのでメモ。

WordPress管理画面から「外観」→「テーマエディター」→「function.php」に以下のコードを記述

```php
add_action('init','add_categories_for_pages'); 
function add_categories_for_pages(){ 
   register_taxonomy_for_object_type('category', 'page'); 
} 
add_action( 'pre_get_posts', 'nobita_merge_page_categories_at_category_archive' ); 
function nobita_merge_page_categories_at_category_archive( $query ) { 
    if ( $query->is_category== true && $query->is_main_query() ) { 
    $query->set('post_type', array( 'post', 'page', 'nav_menu_item')); 
    } 
} 
```


参考記事↓ありがとうございました

https://koroblog.org/wordpress/no-categories/

## WordPressのお役立ち情報

https://wagtechblog.com/programing/wordpress-school

https://wagtechblog.com/programing/php-school
update_attributesとupdate_attributes!の違い

#結論

どちらのメソッドもデータベースに更新する直前にvalidationを実行する。
validationに妥当性がなかった場合に

・**update_attributes**　falseを返す

・**update_attributes!**　例外処理を返す

という違いがある。


#例

入力必須であるnameカラムを持つUserモデルを例にすると

###update_attributes

```
user.update_attributes(name: nil)
=> false
```

###update_attributes!

```
user.update_attributes!(name: nil)
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
```

それぞれ上記の処理をする。
エラー処理〜doesn't have a default value〜

###こんなエラーが出ました。

![スクリーンショット 2021-03-19 16.06.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782402/34b9be6d-9ccc-c345-5a90-e154287f7083.png)

このような文が出ていた時は、大抵マイグレーションファイルに問題がある。
**・必要のないカラムを作っていないか**
**・存在しないカラムに対してバリデーションをかけていないか**

を確認する。

入力の必要のないカラムをつくっていて、かつバリデーションをかけていると、そんな入力のところないぞ！と怒られる。当然ですね。

**＜ロールバック技＞**
１０こマイグレーションファイルがあったとして、４個前まで一気に戻りたい。
（一回のrails db:rollbackでは１つ前までしか戻れない）

→ **rails db:rollback STEP=4**

このように記述すると４つ前まで一気にロールバックしてくれる。

以上。