OmniAICreator/Qiita-1.07M huggingface dataset:
【戒め】インターネット無料賃貸でサーバ運用するときは個別にLANを分けてください。


<!-- 発端や概要を記載 -->
# はじめに
タイトルの通りではありますが、
自宅でサーバーを運用している人、これから運用しようと思っている人に向けて、これだけは言わせてください。
**インターネット無料賃貸で自宅サーバを飼う場合は、必ず個別にLANを分けてください。**

本記事は私の実体験に基づき、LANを分けないことの危険性を伝えるものです。
既にお伝えしたい意図が分かった方は、読まなくてもたぶん大丈夫です…。

なお、筆者のネットワークに関する知識が乏しいため、
専門のネットワークエンジニアの方々からは`「常識やろ…」`とか`「え？これ違くない？」`と思われるような内容が含まれる可能性があります。
そのため内容に不備があれば、暖かくご指摘いただけると幸いです。

<!-- 各チャプター -->
<a id="#Chapter1"></a>

# 1.事件発生前のネットワーク構成について
まず、筆者は23年末頃まで以下のような構成で自宅サーバーを運用していました。
初学者にありがちな、足りないLANポートをとりあえずハブで増やして補っている感じですね。

![local-network.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491064/319e8ffe-73fe-0900-de16-e7eea7e99dac.png)

LANのCIDRは共有ルーター側で設定されており、172.16.XX.XX/XXで、いわゆるクラスBの範囲で設定されていました。
また、ローカルIPは共有ルーターのDHCP機能で管理されており、サーバーのIPを固定したいからといって、当然ながら共有ルーターをいじることはできません。

そのためLAN内から適当に空いているIPアドレスを見繕って、
以下のようにサーバー側の設定ファイルでIPアドレスを固定していました。
~~さらに今考えるとマジであり得ないですが、この状態でkubernetesクラスタを構築していました。~~

```text:ローカルIP 設定ファイル (/etc/netplan/xx-netcfg.yaml)
network:
  version: 2
    ethernets:
      enp2s0:                         // NIC名 
        addresses: [172.16.XX.XX/24]  // サーバのIPアドレス
        gateway4: 172.16.XX.XX        // ゲートウェイ
        nameservers:
          addresses: [172.16.XX.XX]   // DNSサーバ
          search: []
        optional: true
```

はい、もうお気づきの方もいるかと思いますが、
この状態では`ローカルIPが自動的に変わる`可能性があります。

**何故ならLAN内のCIDR/IPは共有ルーター側で管理されているのですから...。**

<a id="#Chapter2"></a>

# 2.何が起きたか
そして23年末頃、事件は起きました。

いつものようにノートPCからkubernetesのお世話をしようとしたところ、
kubectlコマンドがエラーになります。
```text:kubectl エラー
$ kubectl get node
Unable to connect to the server: dial tcp 172.16.XX.XX:6443: connect: no route to host
```
そしてサーバーにSSHで繋ぐこともできません。
```text:ssh エラー
$ ssh -i ~/.ssh/id_rsa ubuntu@172.16.XX.XX
ssh: connect to host 172.16.XX.XX port 22: Connection timed out
```

仕方なく各サーバーにモニターをつないで直接ログインしたところ、ローカルIPアドレスが変わっていることに気づきました。
さらにLAN自体のCIDRが`10.XX.XX.XX/XX`となっておりクラス単位で変更されていました、そりゃ疎通できないっすよね...。
```
変更前：172.16.XX.XX/XX
変更後：10.XX.XX.XX/XX
```

なお、CIDR単位で変更が入った理由は未だに謎のままですが、
今思えば当時賃貸の管理会社が変わっていたので、それに起因するのかもしれません…。

<a id="#Chapter3"></a>

# 3.事件発生後のネットワーク構成について
こうして今更ながら、LANを分けて個別にローカルIPを管理する必要性に気づきました。
方法はいくつか考えられますが、大まかに以下のどちらかになるでしょう。

* DHCPサーバを共有ルーターの間に挟む
* DHCP機能付きのルーターを共有ルーターの間に挟む

`DHCP用にサーバーを立てるのはリソース的にもったいないし、そもそもサーバー空きがないなぁ。`
と思った筆者はDHCP機能付きのルーターをしぶしぶ購入しました。
別に筆者と同じ製品を買う必要はないですが、念のため買ったものを載せておきます。
~~VPNを使う予定がないのに何故VPNルーターを買ったのかは謎です...。~~
* [TP-Link Omada ギガビット マルチWAN VPNルーター ER605](https://www.amazon.co.jp/dp/B08MH4VLR3)


そして結果的に以下の構成に変更しました。
Myルーターは共有ルーターの管理下に置き、
個別にLANを`192.168.XX.XX/XX`で切り、この中をMyルーターのDHCP機能で個別にIP管理をしています。
またMyルーター側でMACアドレスを指定してIPを固定化できたため、各サーバー側での設定は不要となりました。
![local-network-after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491064/5b2a2899-9c83-8eea-352b-47b3cd4ffd46.png)

これでとりあえず共有ルーター側でLANのCIDRが変更になっても、
Myルーター内のLANのCIDRと被らない限りは、たぶん大丈夫な状態になりました。
100%安全なわけではないですが、これ以上の確実性を求めると個別に回線を引くことになるので、いったんはこれで大丈夫でしょう...。たぶん...。


<a id="#Chapter4"></a>

# 4.最後に
記事は以上になります、お読みいただきありがとうございました。
きっと皆さんにもインターネット無料賃貸でサーバー運用する際にLANを分ける重要性が分かっていただけたかと思います。

また全然関係ない話ですが、本記事の画像はいらすとやを利用しており、
記事作成の過程で笑えるものを見つけたので共有します。
こういうどこに需要があるかわからない画像があるのがいいですよねぇ…。
* [ループ接続のイラスト](https://www.irasutoya.com/2016/07/blog-post_529.html)
二値分類そのままやるべからず(関数作った)

二値分類は基本的に0.5を境に0.5以上だと陽性、未満だと陰性とみなすことが多いですが、必ずしもそれが正しいとは言い切れません。
というのも正解率がどれだけ高くても再現率と適合率のバランスが悪かったりするとダメですし、元のデータの偏りも考慮しないといけません。
(例えばですが陽性90個と陰性10個のデータがあって全部陽性と診断しても正解率単体では90%でも陰性に着目すると評価は悲惨(陰性反応的中率0%で陰性再現率0%)なんですよ)
そこで最適なしきい値を考察する関数を作ってみました。
# 関数
```Python3
from sklearn.metrics import classification_report
import numpy as np
import matplotlib.pyplot as plt
def optimal_threshold(y, pred, n="0.0", p="1.0"):
    tsd = np.linspace(0.1, 0.9, 100)
    accs = []
    rec0 = []
    rec1 = []
    pcs0 = []
    pcs1 = []
    f1_0 = []
    f1_1 = []
    for i in range(len(tsd)):
        y_pred = np.where(pred >= tsd[i], 1, 0)
        rep = classification_report(y, y_pred, output_dict=True)
        accs.append(rep["accuracy"])
        rec0.append(rep[n]["recall"])
        rec1.append(rep[p]["recall"])
        pcs0.append(rep[n]["precision"])
        pcs1.append(rep[p]["precision"])
        f1_0.append(rep[n]["f1-score"])
        f1_1.append(rep[p]["f1-score"])
    plt.plot(tsd, accs, label="accuracy")
    plt.plot(tsd, rec0, label="recall 0")
    plt.plot(tsd, rec1, label="recall 1")
    plt.plot(tsd, pcs0, label="precision 0")
    plt.plot(tsd, pcs1, label="precision 1")
    plt.plot(tsd, f1_0, label="f1-score 0")
    plt.plot(tsd, f1_1, label="f1-score 1")
    plt.legend()
    plt.show()
    return tsd[np.argmax(accs)], tsd[np.argmax(rec0)], tsd[np.argmax(rec1)], tsd[np.argmax(pcs0)], tsd[np.argmax(pcs1)], tsd[np.argmax(f1_0)], tsd[np.argmax(f1_1)]
```
返り値の順は正解率・陰性の再現率・陽性の再現率・陰性の適合率・陽性の適合率・陰性のF1・陽性のF1になります。
## 使用例
```Python3
import pandas as pd
import statsmodels.api as sm

df = pd.read_csv("breast_cancer.csv")

y = df["y"]
x = df.drop("y", axis=1)
for col in x.columns:
    x[col] = (x[col] - x[col].mean()) / x[col].std()

model = sm.Logit(y, x).fit_regularized()
otp = optimal_threshold(y, model.predict(x), n="0.0", p="1.0")
otp
```
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782889/b76c9d11-4082-4e39-abc5-958c5f1ee5f8.png)
```
(0.398989898989899,
 0.5767676767676768,
 0.1,
 0.1,
 0.5767676767676768,
 0.398989898989899,
 0.398989898989899)
 ```
なので正解率とF1からおおよそ0.4くらいをしきい値にするとちょうどよいかもしれません。
では実際に混合行列を見てみましょう
```Python3
from sklearn.metrics import confusion_matrix

pred = model.predict(x)
y_pred1 = np.where(pred >= 0.5, 1, 0)
y_pred2 = np.where(pred >= 0.398989898989899, 1, 0)
```
### しきい値が0.5(デフォルト値)
```Python3
pd.DataFrame(confusion_matrix(y, y_pred1))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782889/4122d807-c46d-e9e0-88d7-1b4f6cea2202.png)

### しきい値を最適値にした場合
```Python3
pd.DataFrame(confusion_matrix(y, y_pred2))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782889/c82889e8-5648-5668-e940-003afb7acd18.png)

陽性を正しく陽性と完璧に判断でき、再現率が向上したことが分かります。

# まとめ
0.5が最適とは限らない。
Understand APIとPlantUMLを使ってC/C++のソースコードからシーケンス図を生成する

ソフトウェア開発プロジェクトにおいて、ソースコードの構造を視覚的に理解することは非常に重要です。特に、大規模なプロジェクトや複雑なアーキテクチャを持つアプリケーションでは、コードの流れや関数間の依存関係を把握することがプロジェクトの成功に直結します。今回は、Understand APIを活用してC/C++のソースコードからシーケンス図を自動生成する方法をご紹介します。さらに、PlantUMLを使用してこれを視覚化し、コードの理解を深める手助けをします。

#### 必要なツール

- **Understand**: 静的解析ツールで、豊富なAPIを提供しており、ソースコードからさまざまなデータを抽出できます。
- **Python**: UnderstandのPython APIを使用します。
- **PlantUML**: シーケンス図などのUML図を生成するツールです。

#### ステップバイステップの説明

1. **Understandプロジェクトの準備**: まず、Understandでプロジェクトを開き、分析したいソースコードをプロジェクトに追加します。
2. **スクリプトの作成**: Pythonスクリプトを書いて、特定の関数から始まる関数呼び出しの階層を解析します。このスクリプトは再帰を使用して、深さ優先検索で関数呼び出しを追跡します。
3. **再帰の制御**: 再帰関数の呼び出しを5回に制限し、シーケンス図が過度に複雑になるのを防ぎます。
4. **シーケンス図の生成**: PlantUML記法を用いて、解析結果をシーケンス図として出力します。これにより、ファイル間の関数呼び出しの関係が視覚的に明確になります。

#### コードサンプル

以下は、UnderstandとPlantUMLを使ってシーケンス図を生成するためのPythonスクリプトの例です。

```python
import understand as und

# Understandプロジェクトを開く
db = und.open("path/to/project.und")

def generate_sequence_diagram(func, depth=0, output=None, recursion_count={}):
    if output is None:
        output = ["@startuml"]
    
    # 関数が宣言されているファイル名を取得
    func_file = func.parent().longname()
    
    # 再帰の深さをチェックし、上限を超えていたら戻る
    if recursion_count.get(func.longname(), 0) >= 5:
        return
    
    # 再帰カウントを更新
    recursion_count[func.longname()] = recursion_count.get(func.longname(), 0) + 1

    # Activate the function
    output.append(f"{depth * '  '}activate {func_file}")

    # Handle function calls
    for ref in func.refs("call", "Function"):
        callee = ref.ent()
        callee_file = callee.parent().longname()
        output.append(f"{(depth + 1) * '  '}{func_file} -> {callee_file}: {callee.name()}")
        generate_sequence_diagram(callee, depth + 1, output, recursion_count)

    # 再帰カウントを減少
    recursion_count[func.longname()] = recursion_count.get(func.longname(), 0) - 1

    # Deactivate the function
    output.append(f"{depth

 * '  '}deactivate {func_file}")

    if depth == 0:  # Only append this line at the end of the initial call
        output.append("@enduml")
    return "\n".join(output)

# シーケンス図の生成
diagram = generate_sequence_diagram(db.lookup("main", "Function"))
print(diagram)
```

このスクリプトは、プログラムの各関数がどのファイルに定義されているかを基に、関数間の呼び出しを追跡し、それをシーケンス図で表現します。再帰制御により、図が適切な複雑さに保たれ、コードの理解が容易になります。これは、特に大規模なソフトウェアプロジェクトやリファクタリングの際に非常に有効なツールです。
連番の配列をつくるのが意外と頭使った(JavaScript)

## 前座問題

Q. （JavaScript）20 ~ 50 までの連番からなる配列arrを作成せよ。

## 真っ先に思い浮かんだやつ
```javascript
const arr = []

for (let i = 20; i <= 50; i++) {
  arr.push(i)
}

console.log(arr)
// arr=[20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50]
```

👶「...」
👶「なんかださい」
👶「一個ずつ入れるんじゃなくて、map関数みたいにできないものか」
（うまく言語化できなかったので雰囲気掴んでください）

## 本問題

Q. （JavaScript）20 ~ 50 までの連番からなる配列arrを**スマートに**作成せよ。

## 解答

MDNを参考に作ってみましょう
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/from#%E3%82%A2%E3%83%AD%E3%83%BC%E9%96%A2%E6%95%B0%E3%81%A8_array.from_%E3%81%AE%E4%BD%BF%E7%94%A8


（MDN引用）
```javascript
// 連番の生成
// 配列はそれぞれの場所が `undefined` で初期化されるため、
// 以下の `v` の値は `undefined` になる
Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]
```


これを元に考えた回答がこちら
```javascript
const arr = Array.from({ length: 31 },(v,i) => i + 20)
```

## すこし深掘り
👶「Array.from()かあ、あんま使ってないから調べよか」

>**構文**
>Array.from(arrayLike, mapFn)
>
>**引数**
>arrayLike
>配列に変換する反復可能オブジェクトまたは配列風オブジェクト。
>
>mapFn （省略可）
>配列の各要素に対して呼び出す関数です。指定された場合、配列に追加されるすべての値は最初にこの関数に渡され、代わりに mapFn の返値が配列に追加されます。この関数は以下の引数で呼び出されます。
>>element
>>配列内で現在処理中の要素です。
>>
>>index
>>配列内で現在処理中の要素のインデックスです。

👶「おい、待てぃ。arrayLikeに入った{length:5}の説明がないやん」

>Array.from() は、以下のものから Array を生成します。
>
>- 反復可能オブジェクト（Map や Set のような要素を取得するオブジェクト）
>- オブジェクトが反復可能でない場合は、配列風オブジェクト（length プロパティおよび添字の付いた要素を持つオブジェクト）

👶「ほーん。今回は『length プロパティを持つ配列風オブジェクト』ってわけか」

```javascript
const a = Array.from({length: 5})
console.log(a) //[undefined,undefined,undefined,undefined,undefined]
```

```javascript
const b = Array.from({length: 5,2: 'hoge'})
console.log(b) //[undefined, undefined, 'hoge', undefined, undefined]
```

👶「なんとなくわかった」

おしまい
【JavaScript】兄弟要素を追加する方法

以下のように記述すると兄弟要素を追加できます。

```js
const sample = document.getElementById('sample')
const div = document.createElement('div')

sample.parentNode.insertBefore(div, sample.nextElementSibling)

```

または以下のように記述します。

```js
const sample = document.getElementById('sample')
const div = document.createElement('div')

sample.parentNode.appendChild(div)

```
2次元配列と1次元配列の相互変換

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【 Swift 】2次元配列と1次元配列の相互変換 🤔 <a href="https://t.co/X1BKRtRcZB">https://t.co/X1BKRtRcZB</a></p>&mdash; chanzmao (@maochanz) <a href="https://twitter.com/maochanz/status/1791114035985072491?ref_src=twsrc%5Etfw">May 16, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ロジックとしては以下だけの模様。

```swift
p.x = index / 3;
p.y = index % 3;
```
どの言語であれ、競プロであれ、配列の処理は大事。

<a href="https://android.benigumo.com/20240211/handle-array/" target="_blank" rel="noopener">👉 【 Swift 】2次元配列と1次元配列の相互変換 🤔&nbsp;<img src="https://b.hatena.ne.jp/entry/image/https://android.benigumo.com/20240211/handle-array/" alt="hatena-bookmark" /></a>
【JavaScriptとは？】基礎まとめ

<br>
こんにちは、Reikaです。<br>私は現在、アプレンティスの5期生として日々勉強に励んでいます。<br>

https://apprentice.jp/lp

アプレンティスの課題として**HTML/CSS/JavaScriptの学習**に取り組みました。<br>今回はこの経験を通じて学んだことについて、記述していきます。<br>
※今回の記事ではHTML/CSSについては省略しています。

# 目次
1.JavaScriptとは？
2.関数について
3.DOM操作とは？

今回私は[JavaScript Primer
迷わないための入門書](https://jsprimer.net/)を参考に勉強を進めました。<br>この入門書から理解した内容をこの記事にまとめていきます！<br>


## 1.JavaScriptとは？
* JavaScriptとは、主にウェブブラウザの中で動くプログラミング言語
* ウェブサイトの表示を変化させたり、ウェブサイトのサーバーと通信してデータを取得したりできる
* フロントエンド唯一のプログラミング言語

## 2.関数について
関数とは、ある一連の手続き（文の集まり）を1つの処理としてまとめる機能です。<br> 関数を利用することで、同じ処理を毎回書くのではなく、一度定義した関数を呼び出すことで同じ処理を実行できます。

定義は次のように、functionを使って定義します。

```JavaScript
function 関数名(引数1, 引数2) {
    // 処理
    // ...
    return 返り値;
}
```

余談ですが、私は学習を始めてから5ヶ月間、PHPをメインに触っていたので、関数についてはすんなり理解できました。<br>言語が違っても共通する部分が多いので、基礎をしっかり固めることはとても重要ですね。

## 3.DOM操作について
DOM操作とは、**JavaScriptを使ってHTMLやCSSを参照したり、書き換えたりすること**を指します。<br>DOMはDocument Object Modelの略です。<br>

HTMLでは、要素（タグやIDなど）を指定して文字を書きますよね。その**要素の動きを制御する**のがDOM操作です！<br>以下に、具体的な例を2つ示します。

#### 1. **IDを利用して要素を取得する**

```JavaScript
document.getElementById(exampleId);
```

このコードは、特定のIDを持つ要素を取得する方法を示しています。<br>documentはHTML文書全体を表し、getElementByIdは特定のIDを持つ要素を取得します。<br>getElementByIdは、直訳すると「IDを手掛かりにして要素を取得する」という意味です。
<br>
例えば、HTML内に以下のような要素があるとします。

```JavaScript
<div id="exampleId">Hello, World!</div>
```

この場合、document.getElementById("exampleId")を使うことで、この<div>要素をJavaScriptで参照したり、操作したりすることができるようになります！<br>

#### 2. **クリックイベントの設定**

```html
<div id="myModal" class="modal">
  <div class="modal-content">
    <span class="close">&times;</span>
    <img class="modal-logo" src="img/user-thumbnail.png" alt="ユーザーアイコン" id="modal-logo">
  </div>
</div>
```

```JavaScript
document.addEventListener("DOMContentLoaded", function () {
  let modal = document.getElementById("myModal");
  let logoLink = document.getElementById("modal-logo");

  logoLink.addEventListener("click", function (event) {
    event.preventDefault(); // デフォルトのリンク処理を防ぐ
    modal.style.display = "block"; // モーダルウィンドウを表示する
  });
});
```

この例では、addEventListenerメソッドを使用して、**特定の要素（ここではmodal-logo）がクリックされたときにイベントを処理する方法**を示しています。<br>

* DOMContentLoadedイベントは、HTMLの全コンテンツが読み込まれたときに発生します。<br>これにより、JavaScriptがHTML要素にアクセスできるようになります。<br>
* clickイベントは、指定した要素がクリックされたときに発生します。<br>この例では、ユーザーがアイコン（modal-logo）をクリックしたときにモーダルウィンドウ（myModal）が表示されるようにしています。

<br>

:::note info
JavaScriptに元々用意されているメソッドは以下を参照ください。
:::

https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById


# まとめ
いかがでしたでしょうか？<br>JavaScriptの基礎の基礎を簡潔すぎるぐらい簡潔に書いてしまいましたが、HTML/CSSとの関係性はまとまっていたでしょうか？<br>最後まで読んでいただき、ありがとうございました！
ローカルストレージとセッションストレージとは

# 前提：ユーザーデータを保存するには
そもそもJavascriptは、スクリプトを用いてクライアントのコンピューターファイルに勝手に書き込むことはできません。
なぜかというと、クライアントのセキュリティを守るために必要だからです。
悪意のあるスクリプトが埋め込まれているサイトにアクセスしたことで、クライアントの重要なファイルを削除したりできたら一大事です。

では、どのようにしてユーザーデータを保存すればよいのか。
そこで登場したのが**cookie**という仕組みでした。

ただcookieは、JSからは操作がしにくく、サイズの制限もあります。
そこで、近年は代替手段として**WebStorage**（いわゆる**ストレージ**）があります。

cookieとWeb storageの違いは、こちらの記事がとてもわかりやすいです。

https://qiita.com/pipiox/items/95554673ba3b078ac112

# ストレージとは
ブラウザ内部のデータストア（つまり保管庫）です。
key - valueの組み合わせでデータが保管されています。

## セッションストレージとは
ブラウザが開いている間だけデータを管理する。
ブラウザを閉じたタイミングでデータは破棄される。（ウィンドウ / タブ間でデータを共有することもない）

## ローカルストレージとは
オリジン単位でデータを管理する。
「明示的に破棄をする or ブラウザを閉じる」をするまでデータを維持している。（ウィンドウ / タブ間でデータを共有することが可能）

オリジンとドメインの違いなどについては、こちらの記事がとてもわかりやすいです。

https://qiita.com/att55/items/2154a8aad8bf1409db2b


ちなみに、**CORS**について簡単に説明をすると、
あるドメインで動いているwebアプリケーションに対して、別のドメインで動いているwebアプリケーションにアクセスするためのセキュリティを緩和する機能です。
デフォルトでは、ブラウザは異なるオリジンへのリクエストを制限している。

ただ「CORSをサーバー側で設定していなければ、別オリジンへのアクセスを緩和できない」というわけではなく、サーバー側でJSの代理として別オリジンにアクセスするという仕組みもある。
これが、**プロキシ**と呼ばれているオリジンまたぎの手段です。

## データの保存・取得・削除
セッションにデータを保存・取得・削除する方法の一例として以下があります。
ローカルストレージの場合は、セッションデータの削除を行わなければデータが維持され続けてしまい、意図しないバグやメモリのひっ迫に繋がります。
```
// もしくは、sessionStorage
let storage = localStorage

// データを保存
storage.setItem('SNS', 'LINE');
// データを取得
// 結果：SNS
console.log(storage.getItem('SNS'))

// データの削除
storage.removeItem('SNS')
```
Alembicで現在適用されている最新のMigrationを確認する方法

## メモ
Migrationが現在適用されているのは、 `alembic history`結果の`(head)`がある部分だと勘違いしていた

```Shell
➜ poetry run alembic history
6c52ef9fa20e -> 6d7e21f4d179 (head), add type to users
e7922bb77adc -> 6c52ef9fa20e, drop index
4649a7485736 -> e7922bb77adc, creator and sendor nullable
394800e0fa0e -> 4649a7485736, Add ai and anonymous user
3604267b7044 -> 394800e0fa0e, Increase message text size
c4d28179c9f3 -> 3604267b7044, add default ts to messages
b42d3974bfbf -> c4d28179c9f3, apply SQLAlchemy 2.0
e0522be70879 -> b42d3974bfbf, add user_id to thread
fd1027b6d882 -> e0522be70879, add user and thread
2848b16932f2 -> fd1027b6d882, Add ts to messages
<base> -> 2848b16932f2, Create message table
```

実際は、`alembic current` で表示されるVersionが、現在適用されている最新のMigrationであった

```Shell
➜ poetry run alembic current
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
6c52ef9fa20e
```
Laravel 11がリリースされました

# Laravel 11がリリースされました。

Laravel 11は、Laravel 10.xでの改善を続け、アプリケーション構造を合理化し、秒単位のレートリミット, ヘルスルーティング、暗号化キーのローテーション,キューテストの改善、プロンプトバリデータの統合、新しいArtisanコマンドなどを導入しました。さらに、リアルタイム機能をアプリケーションに提供するために、ファーストパーティーのスケーラブルなwebsocketサーバーであるLaravel Reverbが導入されました。

## PHP 8.2
Laravel 11.xは、最低限PHPバージョン8.2が必要です。

## 合理化したアプリケーション構造
Laravel 11は、既存のアプリケーションに変更を加えることなく、新しいLaravelアプリケーションのためにシンプル化されたアプリケーション構造を導入しました。この新しい構造は、より現代的でスリムな体験を提供することを目的としていますが、Laravel開発者がすでに慣れ親しんでいる多くのコンセプトを維持しています。

## Laravel Reverb

Laravel Reverb は、Laravelアプリケーション向けの新しい公式WebSocketサーバーで、クライアントとサーバー間のリアルタイムコミュにケーションを実現します。


## ブートストラップ ファイル

Laravel 11の`bootstrap/app.php`ファイルは、中心的な設定ファイルとして強化されました。このファイルから、開発者はルーティング、ミドルウェア、サービスプロバイダー、例外処理などのアプリケーションの設定を直接カスタマイズできるようになります。この更新により、さまざまな設定が一つのファイルに統合され、アプリケーション構造が簡素化されました。

```php

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

```


## モデルキャストの変更
モデルのキャストはプロパティではなく、メソッドとして定義されるようになりました。メソッドとして定義することで、他のメソッドをキャストから直接呼び出すなど、他のこともできるようになります。以下は新しいLaravel 11のAsEnumCollectionを使用した例です：

```php
protected function casts(): array
{
    return [
        'email_verified_at' => 'datetime',
        'password'          => 'hashed',
        'options'           => AsEnumCollection::of(UserOption::class),
    ];
}
```

## Console Kernel の削除

コンソールカーネルは削除され、代わりにコンソールコマンドをroutes/console.phpに直接定義できるようになります。

## 新しいArtisanコマンド
クラス、Enum、インターフェース、およびトレイトを簡単に作成するための新しいArtisanコマンドが追加されました。

```shell
php artisan make:class
php artisan make:enum
php artisan make:interface
php artisan make:trait
```

## Doctrine DBALの削除
LaravelはもはやDoctrine DBALに依存しておらず、カスタムタイプの登録が不要になりました。



---

Laravel11の新機能について詳しく知るために、[Laravelリリースノート](https://laravel.com/docs/11.x/releases)をチェックしてください。

---

本記事についてのご感想をお聞かせください。読者の皆様の意見を反映させて、今度の内容改善につなげたいと考えております。
prismaではincludeの中でwhereが使えない

includeの中でwhereを使おうとするとこんな感じになると思います。

```javascript
const user = await prisma.user.findFirst({
        where: {
          email,
        },
        include: {
          wishlist: {
            include: {
              product: {
                include: {
                  images: true,
                  pricing: {
                    take: 1,
                    orderBy: {
                      createdAt: "desc",
                    },
                  },
                  productDiscount: {
                    where: {
                      isValid: true,
                    },
                  },
                },
              },
            },
          },
          cart: {
            include: {
              product: {
                include: {
                  images: true,
                  pricing: {
                    take: 1,
                    orderBy: {
                      createdAt: "desc",
                    },
                  },
                  productDiscount: {
                    where: {
                      isValid: true,
                    },
                  },
                },
              },
            },
          },
        },
      });
```
これはエラーになります。
```
Type '{ where: {}; include: { user: true; }; }' is not assignable to type 'boolean | QnAArgs | undefined'.
  Object literal may only specify known properties, and 'where' does not exist in type 'QnAArgs'
```

これは以下のissueの通りです。直っていません。

https://github.com/prisma/prisma/discussions/11172#discussioncomment-1975090

prismaのようなORMは便利ですがあまりそれに依存してしまうとORM特有の問題にぶち当たることがあるので注意が必要です。
UbuntuをUSBドライブにインストールする

Ubuntu22.04LTSを、USBにインストールする方法を解説していきます。

### 更新履歴
2024-05-16 記事投稿
2024-06-02 更新履歴の追加・画像や文章の追加

## 用意するもの
・USBメモリ 16GB以上
・USBストレージ 32GB以上 (SSDがオススメ)

## 下準備
Ubuntuをインストールするために、インストールメディアを作っていきましょう

### 1.WindowsにRufusをダウンロード
USBメモリにISOファイルを書き込むためにRufusをダウンロードします
https://rufus.ie/ja/ にアクセスしてください
そして、rufus-4.4.exe をダウンロードしてください

### 2.USBメモリに書き込む
さっそく書き込んでいきます
まずRufusを管理者権限で起動してダイアログでOKを押します
そして、インストールメディアになるUSBメモリを選択して、
選択ボタンでISOファイルを選択し、
スタートを押します。
ダイアログがでてきたらOKを押します。

これでインストールメディアは完成です

## 3.UbuntuをUSBにインストール
UbuntuをUSBストレージにインストールしていきます
まずUSBメモリをPCに挿して、「再起動」します。
そしたら、特定のキーを押し、BIOSに入って、
USBメモリの起動優先順位を上げておきましょう。
「保存＆終了」でPCが再起動します。

するとGRUBブートローダーの画面が出てきます。
「Try or Install Ubuntu」を選択して、Enterを押しましょう。
すると言語選択画面が出てきます。
ここで、「Ctrl+Alt+T」でターミナルを開き、
以下のコマンドを実行してください
```
sudo gedit /etc/gdm3/custom.conf
```
大体7行目ぐらいにある「WaylandEnable=false」を、
「WaylandEnable=true」に変更しましょう。
そしたら内容を保存してGeditを閉じましょう。

そして、言語で日本語を選択して、「Ubuntuを試す」を押します。
「Gparted」と「Disk」を開きます。
まずDiskでWindowsがインストールされているSSD/HDDの名前を確認します
（筆者の場合は  /dev/nvme0n1）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3691296/24bd9ce3-9230-dc1e-a951-6637cdf162c8.png)
そしたら、Gpartedに移り、WindowsがインストールされているSSD/HDDを選択します
「EFI system partition」を選択し、上のメニューでパーティション→フラグを編集を押します。
「esp」「boot」のチェックを外して、「msftdata」にチェックを入れます。
(以下のようになればOK)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3691296/2d45c95f-0e01-1395-d0fc-635966576950.png)


そして、「Disk」と「Gparted」のウィンドウを閉じて、
「Ubuntu22.04LTSのインストール」を押します。

:::note info
このときにUbuntuをインストールするUSBストレージをPCにさしておきましょう
:::
そして、セットアップを進めていき、最後の「インストールの種類」でその他を選択します
USB（UbuntuをインストールするUSB）のパーティションを全て消して、
空き領域を選択して、以下の順番と設定で追加してください

1.サイズ: 500MB
 　パーティションのタイプ:基本パーティション 
　パーティションの場所:この領域の始点
　利用方法:EFI system partition

 2.サイズ: 現在の空き領域 - 4 x 1024 (電卓で計算)
 　パーティションのタイプ:基本パーティション 
　パーティションの場所:この領域の始点
 　利用方法:ext4ジャーナリングファイルシステム
　マウントポイント:　/

 3.サイズ: 現在の空き領域
  　パーティションのタイプ:基本パーティション 
　パーティションの場所:この領域の始点
　利用方法:　スワップ領域

 正しくパーティションを設定できたか確認したら、
 「ブートローダーをインストールするデバイス」で、UbuntuをインストールするUSBを、
 選択して、「インストール(I)」をクリックして、インストールします。

 インストールが終わり、ダイアログが出てきたら「試用を続ける」をクリックして、
 「Gparted」を開き、Windowsのドライブの「EFI system partition」のフラグを、
 元に戻して置きます。
 (以下のように戻す)
 
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3691296/3ebadde2-d010-7355-6163-eaf6b1f1faee.png)

 そして、再起動して、Ubuntuをインストールしたデバイスから起動しましょう

 インストールは完了です！お疲れ様でした！
僕なりの 環境変数 備忘録

# 全般

システム環境変数とプログラム環境変数がある

システム環境変数→OS自体に

プログラム環境変数 → プログラム毎に

※優先されるのはプログラム環境変数


# Docker

docker-compose内の環境変数の話

```
$ cat .env
TAG=v1.5

$ cat docker-compose.yml
version: '3'
services:
  web:
    image: "webapp:${TAG}"
```

上の例の場合、docker-compose.ymlを環境変数を置き換えて表示するコマンド`docker-compose config`をやると

```
$ docker-compose config

version: '3'
services:
  web:
    image: 'webapp:v1.5'
```

のように `${TAG}`が`v1.5`に置き換わる。
Rails 7.1 では存在しないアクションをコールバックに指定できない

Rails 7.1 では，`before_action` などのコールバック指定で，存在しないアクションを `only` パラメーターの値に含めることができない。

たとえば，

```rb:config/routes.rb
resources :items
```

というようなアプリがあったとする。
要するに Item に対する基本的な操作ができるようなもの。

そして，ItemsController のほうで，

```rb
class ItemsController < ApplicationController
  before_action :set_item, only: %i[ show edit update destroy ]

  # 中略

  private 

  def set_item
    @item = Item.find(params[:id])
  end
end
```

のようにコールバックを設定するとする。

ここで，仕様の変更によって，Item の削除機能を省くことになったとする。
すると，まずルーティングのほうは

```rb:config/routes.rb
resouces :items, except: %i[ destroy ]
```

のように，`destory` アクションは無い，ということにしておいて，さらに ItemsController の `destroy` メソッドを削除することになる。

問題はコントローラーの

```rb
  before_action :set_item, only: %i[ show edit update destroy ]
```

の部分だ。
もはや `destroy` アクションは無いので，`only` の指定から `destroy` を取り除かなければならない。

しかし，Rails 7.0 までは取り除き忘れてもとくに問題は起きなかった。

これが，Rails 7.1 になると，エラーが起きるようになった。

Rails バージョンを上げたらテストが通らなくなったが，当初何が起こっているのか全く分からず時間を溶かしてしまった。
Rails サーバーで動作を確認してもやはり何かがおかしい。
基本に立ち返ってログをきちんと見たらちゃんと書いてあった。
つまり

```
AbstractController::ActionNotFound (The destroy action could not be found for the :set_item callback on ItemsController, but it is listed in the controller's :only option.

Raising for missing callback actions is a new default in Rails 7.1, if you'd like to turn this off you can delete the option from the environment configurations or set `config.action_controller.raise_on_missing_callback_actions` to `false`.
):
```

であると。
要するに，コントローラーの `:only` オプションにリストアップされている `destroy` アクションは見つからん，これで例外を出してほしくないならコレコレせよ，と。
僕なりの Vim 備忘録

### 選択

- 単語：	viw



### 画面分割

- 横に  `:vs`
- 開いているウィンドウのdiff表示 `:windo diffthis`
- 開いているウィンドウのdiff表示終了 `:windo diffoff`
- 開いているウィンドウの一括文字列置換 `:windo %s/xxxxx/yyyyy/gc`



### jobとforegroundとbackground

```
[foreground] ─── 入力:Ctrl-z ───> [background]
     ^                                 │
     └──────────── 入力:fg ────────────┘
```

- backgroundを確認：	jobs



### 行番号を選択してファイルを開く

```bash
vim ファイル名 +行番号
```



### 検索

```
/
```



### マッチ数を出力

```
:%s/hoge//gn
```
僕なりの MySQL 備忘録

# 出力

## 見やすく

```sql
select * from table_name\G
```

## 新しい方から表示する

```sql
select * form table_name order by id desc limit 4;
```


## ファイルに書き出し

※以下はroot権限がないとできないこともある。

```sql
SELECT フィールド名 FROM テーブル名 INTO OUTFILE 'ファイル名'
```

```bash
echo 'select <field> from <table>' | mysql -u user -p db > ./test.dmp
```



# カラム

追加

```sql
ALTER TABLE テーブル名 ADD COLUMN 新規カラム名 型情報 before `colomnName`;
```

> mysqlのバージョンによってbefore/afterが使えないときがあるかも？



削除

```sql
ALTER TABLE [テーブル名] DROP COLUMN [フィールド名];
```



名前変更

```sql
ALTER TABLE tbl_name CHANGE COLUMN old_col_name new_col_name column_definition;
```



定義変更

```sql
ALTER TABLE tbl_name MODIFY col_name column_definition;
```



# データ

データの入力

```sql
INSERT INTO tbl_name (``,``) VALUES ('','');
```



データのアップデート

```sql
UPDATE tbl_name SET ``='', ``='' WHERE `` = ;
```



データの削除

```sql
DELETE FROM tbl_name WHERE `` = ;
```
僕なりの Mail 備忘録

# 全体のイメージ

app → smtp server (mail server) → dns → smtp server (mail server)  → mail client


メールサーバー：別名SMTPサーバー。MTAが中で動いているサーバーのこと。

postfix: mta、メールサーバーの役割を果たす。受信、格納、転送。sendmail（MTA）の後継。

msmtp: SMTPクライアント、メールを実際にSMTPプロトコルで送信する。送信。ローカルで実行されるアプリケーションがメールを外部のSMTPサーバーに送る際に便利

sendmailコマンド：postfixやsendmailが提供するコマンドインターフェース。phpのmail関数では内部的にこのコマンドを叩いて、MTAへメールを渡す。



# さくらクラウドの場合

サーバに postfixを入れた。postfix(MTA)がメールサーバー（メールサービス）の役割を果たす。

postfix が smtp serverにsmtpプロトコルで送信する

appから postfix への指示が phpの mail関数 を使ってsmtpサービスで送る、もしくは

appから postfix への指示が smtpサービスを直接指定して（smtp手法）送る。



# localの開発環境でやる場合

smtpコンテナを作った場合。

app コンテナに msmtp ミドルウェアをインストールしている

php の mail関数を使って飛ばす場合、mail関数を叩いて、msmtpソフトウェアを内部的に使って smtpコンテナにメールを送って、そこからメールが飛ぶ。

直接smtpコンテナを指定する場合（smtp手法）、直でsmtpコンテナに接続して、smptコンテナからメールが飛ぶ。
東北大生向けのGPA計算拡張機能

# Tohoku Univ. GPA calculator
この拡張機能は東北大学の学生向けにGPAの計算機能を提供するものです．
学務情報システムの成績確認ページに年度，前後期，全学専門を条件として絞り込んだ履修授業一覧とその条件に当てはまる科目のGPAを表示する機能と学務情報システムで表示される科目一覧をCSV形式で保存するボタンを追加するChrome拡張機能です．
![Screenshot](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3794160/c66fb194-8c77-37de-3e87-324929a27fac.png)


## 1.導入方法

https://chromewebstore.google.com/detail/tohoku-univ-gpa-calculato/agbmcohifheiailbnjmcnapjdnkehajf?hl=ja&authuser=1

にアクセスし，Chromeに拡張機能をインストールしてください．

## 2.使い方
### GPAの見方
GPAは成績照会ページの最下部にある単位数などが記載されている表に追加で記載されます．
### 絞り込み機能
上部に追加された年度，前後期，全学専門のセレクタで絞りこむことができます．また，科目一覧上部のタブをクリックすることでも移動することができます．
#### 年度
年度のセレクタから選択できます．デフォルトでは最新の年度が選択されています．
#### 前後期
前後期のセレクタと科目一覧の上部のタブのクリックで選択できます
|オプション|詳細|
|-|-|
|年間|年度全体|
|前期|年度前期|
|後期|年度後期|
|通算|年度無関係(デフォルト)|
#### 全学専門
全学専門のセレクタから選択できます．
|オプション|詳細|
|-|-|
|合算|全学専門区別なし(デフォルト)|
|全学|全学のみ|
|専門|専門のみ|

### CSVのダウンロード
印刷ボタンの左隣にCSVファイルをダウンロードするボタンが追加されます．CSVにはセレクタの絞り込みが反映されないことに注意してください．
Oh My Zshを入れて作業効率アップ


## ターミナル操作で打ち間違えが多い

普段の業務の中でコマンドを忘れて何回も調べ直したり　 control + R でコマンド検索していたりしていたので,
そのわずらわしさをなくしたかった。

[oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes)
↑ テーマカラーは上記に種類が書いてあるのでお好みになると思う。
git を入れた場合ブランチ名がどのテーマも表示されるようになるみたいなので
本当に好みだと思った。

oh-my-zsh の導入方法については沢山記事があるので割愛。
ただ私の場合　元々 zshrc に設定の記述を加えていたため

oh-my-zsh の導入にあたって生成された
.zshrc のファイルを
.oh-my-zshrc に名前変更して、

.zshrc.pre-oh-my-zsh のファイルを
.zshrc に戻して
戻した.zshrc には今までの.zshrc に記述していた内容の記述を,
.oh-my-zshrc のファイルには
oh-my-zsh の記述の内容を移動させた。

※.zshrc に oh-my-zsh の設定内容を基本的には追記したらいいいだけなので
.zshrc にまとめても別に良いと思う。
私の場合は oh-my-zsh の設定内容を別で分けたかったので下記のようにして別ファイルで扱い、
.zshrc から.oh-my-zshrc を読み込むように設定した。

```sh
# oh-my-zshで作成されたzshrcをリネーム
mv ~/.zshrc ~/.oh-my-zshrc

# バックアップされた既存のzshrcを元の名前に戻す
mv ~/.zshrc.pre-oh-my-zsh ~/.zshrc

```

[イケてるターミナルをコマンド 1 つで作れる oh-my-zsh!git のブランチ名だって表示できる!](https://nishinatoshiharu.com/install-ohmyzsh/)

上記の記事の引用。

> 人によっては oh-my-zsh をインストールする以前に.zshrc に色々と自分の設定を追加している人もいると思います。
> oh-my-zsh をインストールすると新しく.zshrc が作成され、既存の.zshrc は.zshrc.pre-oh-my-zsh という名前でバックアップされます。

```~/.zshrc
source ~/.oh-my-zshrc
#.oh-my-zshrcを読み込む設定の記述
.
.
. 以下　個別の設定の記述......


```

そして default で作成される.oh-my-zshrc に
oh-my-zsh の記述を移動させた。

```~/.oh-my-zshrc
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=blue'
#追記

# If you come from bash you might have to change your $PATH.
# export PATH=$HOME/bin:/usr/local/bin:$PATH

# Path to your oh-my-zsh installation.
export ZSH="$HOME/.oh-my-zsh"

# Set name of the theme to load --- if set to "random", it will
# load a random theme each time oh-my-zsh is loaded, in which case,
# to know which specific one was loaded, run: echo $RANDOM_THEME
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
ZSH_THEME="awesomepanda"
#追記
.
.
.

```

## zsh-autosuggestions プラグインの install と有効化

今回ターミナルでの入力補完機能が欲しかったので以下のコマンドを打ってリポジトリの clone を行う。

```bash
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-${ZSH:-~/.oh-my-zsh}/custom}/plugins/zsh-autosuggestions

```

oh-my-zshrc の設定ファイルを開いて、

```bash
vi  ~/.oh-my-zshrc
```

plugins セクションに zsh-autosuggestions を追加

```~/.oh-my-zshrc
plugins=(
    git
    zsh-autosuggestions #追記
)
```

zshrc の再読み込み

```bash
source ~/.zshrc
```

## zsh-autosuggestions の使用方法

ターミナルにコマンドを入力し始めると、補完候補がグレーの文字で自動的に表示される。
補完候補が表示された状態で、→ キー（右矢印キー）を押すと、補完候補がそのまま入力される。

```bash
➜  ~ git stat
# "→" キーを押すと、"git status" に変わる
➜  ~ git status
```

## 補足

以下の github によると ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE の値を
変更することによって
補完候補として表示されるテキストに下線や色をつけたりできるよう。
私はテキストの文字を青文字にした。

> 提案のハイライトスタイル
> ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE 提案が表示されるスタイルを構成するように設定します。デフォルトは で fg=8、前景色を 256 色パレットのカラー 8 に設定します。端末が 8 色のみをサポートしている場合は、0 ～ 7 の数値を使用する必要があります。

> 背景色も設定でき、提案を太字、下線付き、または目立つようにスタイル設定できます。たとえば、これはシアンの背景に太字の下線付きのピンクのテキストで候補を表示します。
> `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#ff00ff,bg=cyan,bold,underline"`

[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions/blob/master/README.md)
僕なりの Docker の備忘録

# 容量の確認

```bash
docker system df
```



# tag

様々なパッケージ等が含まれていればいるほど、サイズも多くなる。

以下`nodejs`を例にその大小関係を上から小さい順。

```
alpine				`node:<version>-alpine`
slim				`node:<version>-slim`
buster(bullseye)	`node:<version>-buster` or `node:<version>-bullseye`
standard(latest)	`node:<version>` or `node:latest`
```

`standard`と`alpine`は1/9程サイズが異なった。

> 参考：
>
> https://qiita.com/miriwo/items/52ea9ec1c2805137dd5a





# イメージ

## 作成

```bsh
docker build -t <imageName> .
```

`-t` オプションはタグ（tag）を意味する



## 削除

```bash
# 現在使用されていないイメージの削除
$ docker image prune

# すべてのイメージを抹消
$ docker image prune -a
```

## 確認

```bash
docker image ls <imageName>
```



# コンテナ

## 削除

```bash
# 現在使用されていないコンテナの削除
$ docker container prune -f

# すべてのコンテナを抹消
$ docker container prune
```

## 停止

```bash
docker stop `docker container ls -lq`
docker kill `docker container ls -lq`
```

## 確認

```bash
docker ps
```



# ボリューム

```bash
# 確認
$ docker volume ls

# 個別に削除
$ docker volume rm volume_name

# 使用していないものすべて
$ docker volume prune -f

# 全て削除
$ docker volume rm $(docker volume ls -qf dangling=true)
```



# ネットワーク

```bash
# 確認
$ docker network ls

# 削除
$ docker network prune -f
```



# 全てを一括削除

```bash
# イメージ、コンテナ、ネットワーク
$ docker system prune

# volumeも含める場合
$ docker system prune --volumes
```



# キャッシュの削除

```bash
$ docker builder prune -f
```



# Dockerfile

## 共通

### RUN

`RUN` 命令は、Dockerイメージのビルドプロセス中に実行される命令。

`RUN` はイメージを作成する際に、必要なパッケージのインストール、ファイルのコピー、環境設定など、イメージ内で実行すべきコマンドを指定するのに使用される。

それぞれの `RUN` 命令が実行されると、新しいレイヤーがイメージに追加され、その結果が次のステップに引き継がれる。

つまり、`RUN` はイメージの構築過程で使われ、その結果はイメージに永続化される。

### CMD

`CMD` 命令は、Dockerコンテナが実行される際にデフォルトで実行されるコマンドを指定する。

コンテナのスタートアップ時に実行されるコマンドを定義し、主にコンテナの主要な実行プロセス（エントリーポイント）を設定するのに使用される。

`CMD` はイメージのビルド時には実行されず、コンテナが起動するときのみ実行される。また、`CMD` で指定されたコマンドは、コンテナ起動時にコマンドラインから指定されたコマンドによってオーバーライドされる可能性がある。



## go

### RUN go install github.com/cosmtrek/air@v0.10.2

`air` というライブリロードツールを特定のバージョン（v0.10.2）でインストール。

`air` はGo言語で書かれたアプリケーションの開発中にソースコードの変更を検知し、自動的にアプリケーションを再ビルドして再起動するツール。

開発者はソースコードを編集する度に手動でビルドや起動を行う必要がなく、効率的に開発を進めることができる。

### CMD ["air"]

コンテナが起動すると自動的に `air` が実行され、ソースコードの変更を監視し始める。

コンテナ内でアプリケーションを開発している間、連続して変更を適用しテストすることが容易になります。
VMware NSX Upgrade TIPS：ホストのアップグレード順番の制御について

VMware Cloud FoudationのSDDC Manager管理下ではない、<font color="red">**スタンドアロンのVMware NSXにおけるアップグレード**</font> は、<font color="green">**Upgrade Coordinator**</font> を通じて実行します。

Upgrade Coordinatorによるアップグレードは、下記の順番で実施されます。
1. Upgrade Coordinator のアップグレード
1. NSX Edge のアップグレード
1. ESXi ホストのアップグレード
1. NSX Manager のアップグレード

前のフェーズの成功後に次のフェーズに進む形で作業を進めていきますが、今回は<font color="blue">**「3.ESXiホストのアップグレード」作業**</font> における 、<font color="red">**ホストのアップグレード順番を制御したいニーズがある場合の対応方法**</font> について、動作を確認しましたので下記に記載いたします。

**動作確認環境**：VMware NSX 4.1.1.0 → 4.1.2.1 へのアップグレード

# VMware NSX Upgrade Coordinator におけるホストのアップグレード画面
「1. Upgrade Coordinator のアップグレード」の実施画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/debb9a7d-2f07-a2a3-ae4c-7fd9866eb538.png)

「2. NSX Edge のアップグレード」の実施画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/20272b98-edac-9f1d-bed8-38ab63d74662.png)


# ホストアップグレードユニットグループを使用した、ホストのアップグレード順番の制御
「3. ESXiホストのアップグレード」の実施画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/63f99cd1-0c6f-ad66-33c8-0be1693493f0.png)

デフォルトでランダムな順番にて登録されたクラスタ内のホストの、アップグレード順番を指定したい場合の対応について、見ていきます。今回は <font color="red">**先に「10.44.9.124」のホスト、続いて「10.44.9.125」のホスト**</font> にてアップグレードを実施します。

メニューに左上の「グループの追加」より名前を指定して新規にグループを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/2bef5457-a1c7-8a59-b10f-dcdbe506c865.png)

「ノードの選択」にて、該当のグループに含めるホスト（10.44.9.124）を選択し、「保存」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/86b10399-ba5b-8460-8dcc-1b2fefc9e88e.png)

新規に「Group1」として「10.44.9.124」が追加された事が確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/0e7b7d85-23ec-4170-3bad-d2749b2683a8.png)

再度「グループの追加」より名前を指定して新規にグループを追加し、含めるホストを選択の上（10.44.9.125）「保存」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/86a84189-2e38-7c40-1b34-92f64fc43213.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/424f82f7-fdbe-ca89-2220-a02ab66cf5db.png)

1つのクラスタ内のホストが2つのグループに分かれて登録され、かつ登録された順番に並んでいる事を確認します。（Group1:10.44.9.124、Group2:10.44.9.125）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/2f98bdb3-dced-e5d5-8046-433a8f414935.png)


空になったグループは削除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/fd91b6dd-f61e-35e5-6aa1-36c16ab5f3e6.png)


「グループ間のアップグレード順序」が「連続」であることを確認し、「アップグレードの開始」によりアップグレードを開始します。（連続：１つのグループの実行終了後に次のグループに進む方式）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/e3682c68-93a0-3462-503f-86ac89946c95.png)

並び順通り、先にGroup1のホスト（10.44.9.124）、続いてGroup2のホスト（10.44.9.125）のアップグレードが実施されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/c76f23b5-cc50-2ffe-a035-9fc40fb0eb09.png)

意図通りの順番でホストのアップグレードが成功したことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251519/2e03ed3e-8f68-f8b6-4497-b47e58f0fde2.png)
mesonでオプションを追加する

意外と情報が少なかったので、書いときます。
なお、ubuntu24.04、meson1.3.2です。

```c
#include <stdio.h>

int main()
{
#ifdef	HOGE
	printf("My name is hoge.\n");
#else
	printf("Hello, world!\n");
#endif
	return 0;
}
```

といった「`HOGE`」の定義を、ビルド時に指定する、というのが、今回のミッションです。

まずは、**`meson_options.txt`というファイルを作成します**。(これがなかなかわからなかった。)

```text:meson_options.txt
option('hoge', type: 'boolean', value: false)
```

`option`の書き方は、[公式のマニュアルの「Build options」](https://mesonbuild.com/Build-options.html)を見てください。

次に、`meson.build`。

```meson:meson.build
project('hoge', 'c')

if get_option('hoge')
  add_project_arguments('-DHOGE', language: 'c')
endif

executable('hoge', ['main.c'])
```

オプションの値を見て、コンパイルオプションを追加します。これに関しては、[マニュアルの「Add arguments」](https://mesonbuild.com/Adding-arguments.html)を参照。

で、setupコマンド。

```
$ meson setup -Dhoge=true build
```

「`-D`」で、`meson_options.txt`で定義したオプションを指定します。
(ここもちょっとハマりポイント。`-D`というのだから、てっきり`gcc`の`-D`オプションを指定するのかと思いこんでしまった)

後は通常通り。

```
$ meson compile -C build
$ ./build/hoge
My name is hoge.
```

なお、オプションの設定を変える場合は、setupコマンドをやり直してもいいですが、configureコマンドも使えます。

```
$ meson configure -Dhoge=false build
```
【PHPフレームワークFlow】Fluidを用いてHTMLのテンプレートを作成する

# 初めに
Webアプリケーションを作成する際、ヘッダーやフッターなどは共通的な部品にしたいですよね。今回は、FlowのテンプレートエンジンであるFluidを用いた、HTMLのテンプレート作成方法を解説します。


# テンプレート作成してみる

プロジェクト構成は以下です。

```
Project
  └ Packages/
      └ Application/
           ├ Neos.Welcome/
           |    └ Classes/
           |         └ Controller/
           |              └ LayoutTestController.php（★）
           |
           └ Resources/
                ├ Private/
                |    ├ Layouts/
                |    |  └ Default.html
                |    └ Templates/
                |         └ LayoutTest/
                |              └ index.html（★）
                └ Public/
                     └ Styles/
                          └ Default.css
```


## 1. 共通的なテンプレートを用意

以下のようなHTMLファイルを作りました。
ヘッダーとフッターを定義し、コンテンツは各HTMLファイルで作成するような作りです。

```html:Default.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title><f:render section="Title" /></title>
    <link rel="stylesheet" type="text/css" href="{f:uri.resource(path: 'Styles/Default.css')}">
</head>
<body>
    <header class="stylish-header">
        <h1>Header</h1>
    </header>

    <f:render section="Content" />

    <footer class="stylish-footer">
        <h2>Footer</h2>
    </footer>
</body>
</html>
```

**ポイントは以下**
* HTMLをLayouts配下に作成する
* 各ファイルで作成するコンテンツを`f:render`タグで指定


:::note info
Fluidの`f:uri.resource`を用いることで、`Resources/Public`配下のコンテンツを参照することができます。
:::

## 2. 各HTMLファイルでテンプレートを使用
各HTMLファイルは以下のようにしてテンプレートを呼び出します。

```html:index.html
<f:layout name="Default.html" />

<f:section name="Title">
    タイトル
</f:section>

<f:section name="Content">
    <h1>コンテンツ</h1>
    <p>コンテンツの内容</p>
</f:section>
```

**ポイントは以下**
* `f:layout`タグで参照したいLayouts配下のファイルを指定
* `f:section`タグで`f:render`のファイルの内容を指定

こんな感じで、参照することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/658aefcf-1283-b70e-958e-fa4003a5dcda.png)

:::note info
テンプレートHTMLファイル内に`f:render`タグが複数ある場合、全て`f:section`で紐づける必要があります。
:::



# 終わりに
フロントエンドは経験が乏しく、苦手なイメージがあったのですが、テンプレートエンジンを使えば簡単に作ることができました。
Fluidは便利な機能がたくさんあるので、これからも活用していきたいです。

ここまで読んでいただきありがとうございました！
Docker composeのコマンドの挙動も図解にして覚えやすくしてみた

## 対象者

- Docker compose使いたいけどよくわからない
- Docker使い始めたけどコマンドを言われるがままに打っていてよくわかっていない
- コマンドと動作を図示で理解したい

こんな人の理解の一助になれば幸いです。

(前提条件: docker version 26.0.2)

前回、dockerfileからimageを作成したり、containerをbuildしたりする記事を投稿したら予想外の反響をいただいたので、同じく、docker composeのライフサイクルも図解してみようと思った次第です。

↓前回の投稿記事

https://qiita.com/Tanachu8/items/5e85c75818e499042bfe


私自身も初学者で、勉強しながらまとめている手前、
前回同様おかしな点があれば是非ともコメントをいただけましたら幸いです。

## Docker composeコマンドを可視化してみた

勝手にローカルのPCをmacにしているのはご愛嬌です

![docker compose のライフサイクル図解](https://bitter-snowdrop-03e.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fc1a7fda7-198a-466a-8dd2-0bad57d3f317%2F3a9aef2e-5d2a-4f72-b1cd-bcfa1208a946%2FUntitled.png?table=block&id=fff7f448-50c3-4431-9759-c94e1c8cb609&spaceId=c1a7fda7-198a-466a-8dd2-0bad57d3f317&width=2000&userId=&cache=v2)

前回の記事同様、細かいオプションなどは一旦無視してライフサイクルの部分を抜き出してみました。

## Docker composeの操作

docker compose は、複数のコンテナを同時に(一括で)操作するのに便利です。

単体を一つずつ、

```bash
docker container run --rm \
                     -d \
                     -v hoge:/api \
                     -p 8080:8080 \
                     --name hoge-api \
                     my-api-img

docker container run --rm \
                     -d \
                     -v fuga:/src \
                     -p 3000:80 \
                     --name fuga-server \
                     my-server-img
```

のような、なっがーーーーい、コマンドをひたすら暗記してタイピングし続けるのとか、むちゃくちゃ面倒くさいですし、チームメンバーに同じコマンドを伝えるのもしんどいですよね。

docker composeがちゃんと使えたら、

```bash
docker compose up
```

で、一発起動できるので是非とも使いこなしたいと思っています。

docker-compose.ymlの書き方は、数多の記事やブログ、公式ドキュメントに情報がありますのでその辺りの説明は神記事にお任せしたいと思います。
Unveiling the Secrets of Marketing Strategies: Strategies & Insights Revealed

Marketing Success: Key Strategies for Any Business
Dominate any industry with powerful marketing strategies:

Know your audience: Research who your customers are and what they want.
Captivating content: Create valuable, interesting content to attract and engage.
Data is king: Use data to understand your audience and optimize campaigns.
Social media power: Connect with your audience and build brand awareness on social platforms.
SEO for visibility: Improve your website's ranking in search results to drive traffic.
Personalization matters: Tailor your marketing to individual customer needs.
Stay agile, adapt & grow: Be ready to adjust your strategies as trends & technologies evolve.
Unlock your marketing potential and achieve sustainable success!

Demanday Group: More than just a partner, an extension of your team.
https://demanday.com/unveiling-the-secrets-of-marketing-strategies-strategies-and-insights-revealed/
Xcode de リダイレクト

# 1. はじめに

Xcodeで実行するプログラムの標準入力（標準出力）をリダイレクトしたい場面に遭遇したので、調べた結果、プログラム変更不要で実現する方法を見つけました。その方法をここに残しておきます。

Swift、C++、Objective-C どの言語でも共通で使えます。

<br>

情報源は↓こちら

https://stackoverflow.com/questions/7629886/how-can-i-pipe-stdin-from-a-file-to-the-executable-in-xcode-4



# 2. Product Schemeを変更

1. XcodeのProductメニュー → Scheme → Edit Scheme... から スキームエディタを開く
1. 左側ペインの`Run Debug` スキームを選択
1. Infoタブの`Launch`のラジオボタンを`Automatically`から`Wait for the excutable to be launched`に切り替える
1. 右下`Close`で閉じる

<img width="468" alt="scr1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/b9c7b48a-f071-c039-1ebd-b91da0e51560.png" />


# 3. Run

1. `Run`ボタンを押す（or ⌘R）でターゲットをビルドして実行する
2. Debugナビゲータウィンドウに`Waiting to Attach`が表示されるのを待つ

<img width="330" alt="scr2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/1fa21b8e-4f99-15e2-3611-cd7bf8b54bb5.png" />



# 4. リダイレクト

任意のターミナルにて、ビルドしたアプリ（コマンド）を実行する。
このとき、コマンドのパスを正確に指定します。

```terminal:リダイレクト
$ path/to/you/yourApp < inputfile
```

もちろん、パイプも使えます。


```terminal:パイプ
$ echo inputfile | path/to/you/yourApp
```

コマンド起動すると、自動的にXcodeのデバッガにアタッチされます。

ビルドの出力先（ビルドしたコマンドのフルパス）が分からない場合は、次のようにプログラム引数の0番目を1度表示させて確認します。

```cpp
c++:
  int main(int argc, const char* argv[]) {
    std::cout << argv[0] << endl;
  

swift:
  print(CommandLine.arguments[0])
```

自分の環境だと、以下のパスでした。

> /Users/USERNAME/Library/Developer/Xcode/DerivedData/PROJECT-eajrulyzelmhdzhfkauwklsxldpm/Build/Products/Debug/myApp


# 5. おわりに

これでXcodeのデバッガが自由に使えます。

Xcodeのコンソールで実行する場合（元に戻す場合）は、スキームエディタのInfoタブの`Launch`のラジオボタンを`Automatically`に戻しておきましょう。

また、`Wait for the excutable to be launched`用のスキームを別に作り、Productメニュー → Scheme でスキームを選択しておいて、Runする方法もありますね。

<br><br><br>
<hr>

参考URL；

https://qiita.com/Ashipan/items/2085c6ad1a94eca70033

### 以上
初投稿 and 自己紹介

# ごあいさつ
Qiitaを利用する皆さま、はじめまして。
bdrccといいます。
ブラジルダークローストのコーヒーと、チーズケーキのセットが好き、という理由でつけたこのアカウント名と共に、頑張ります。
学生時代、たまにお世話になっていたQiitaの記事、自分で書くことになるとは、当時想像していませんでした。
これからどうぞよろしくお願いします。

# 経歴
* 大学の理系学部 (理工学部ではない) 出身
* 卒業研究で、階層性クラスター分析を行う
* 卒業後は教育系へ、約3年半強勤務
* 当時、社内ポータルサイト(wordpressもどき)管理を担当し、業務の負担軽減の機能を、プラグインを利用・変更していくつか作成していました。しかし、それでも作れない機能があり、力不足を感じました。
どう勉強していいかわからない、時間もない、サポートもない。でも、大学時代のプログラムへの熱意が復活し、作れるようになりたいと思っていました。ならば、学べる場所に行こう！と考え、離職
* 年明け後、職業訓練校で勉強・資格取得
* 現在、再就職活動中

# 経験
* C言語 : 大学で3年半使用 (基本レベル)
* R : 大学で1年使用 (クラスタリングに特化)
* HTML/CSS : 職業訓練校で3ヶ月 (後述の資格レベル)
* Python3 : 職業訓練校で2ヶ月 (後述の資格レベル)
* PHP : 職業訓練校で1ヶ月（後述の資格レベル）

# 主な資格
* 日商PC検定2級 文書作成編 / データ活用編
* WEBクリエイター エキスパート
* Python3エンジニア認定データ分析試験
* 基本情報技術者
* PHP7技術者初級試験

# 終わりに
先人の皆さまほどの、とってもためになる、良い記事が書けるとは全く思っていません。
ですが、備忘録のように、思いつきや学習した内容を書き貯め、偶然見つけてくださった方の些細な力になれたらいいなあ、と思っています。

また、異業種や未経験からSEやPGに転職された方がいたら参考にさせていただきたい、とも思っています。
精一杯頑張りますので、応援、どうぞよろしくお願いいたします。
ReactでInputとOKボタンを使った編集可能なテキストを作成する

まずは`Input`を使って、`InputForm`を作ります。
`InputForm`に`Input`と一つのOKボタンが含まれています。
`Input`の値が`tmpInputValue`に保存されてます。
OKボタンを押すと、`value`の値が`tmpInputValue`に更新されます。
OKボタンを押さなくて、別の所をクリックすると、`value`の値が更新されません。

```ts
const OKButton = () => (
    <button
      className="m-2"
      type="submit"
      onMouseDown={(e) => {
        setValue(inputTmpValue);
      }}
    >
      ✅
    </button>
  );

const InputForm = ({ isInput, text }: { isInput: boolean; text: string; }) => {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
      if (isInput && inputRef.current) {
        inputRef.current.focus();
      }
    }, [isInput]);
    return (
      <form
        style={{ display: isInput ? "block" : "none" }}
        className="border-collapse"
        action={() => { console.log("action"); }}
        onBlur={(e) => {
          setIsInput(!isInput);
          setInputTmpValue(value);
        }}
      >
        <input
          className="border-2"
          type="text"
          name="input"
          value={inputTmpValue}
          onChange={(e) => {
            setInputTmpValue(e.target.value);
          }}
          ref={inputRef}
        />
        <OKButton />
      </form>
    );
  };
```

次に、`Value`を表示するの`Span`を作ります。
`Span`をクリックすると、`Span`が消えて、`InputForm`が現れます。
その状態は`isInput`に保存します。

```ts
const Span = ({ isInput, text }: { isInput: boolean, text: string; }) => (
    <span
      style={{ display: !isInput ? "block" : "none" }}
      className=" border-red-300 border-2 min-h-4"
      onClick={() => setIsInput(!isInput)}
    >
      {text}
    </span>
  );
```

最後に、これらをまとめます。

```ts
export default function Block() {
  const [value, setValue] = useState("");
  const [inputTmpValue, setInputTmpValue] = useState(value);
  const [isInput, setIsInput] = useState(false);

  const OKButton = () => (
    ...
  );

  const Span = ({ isInput, text }: { isInput: boolean, text: string; }) => (
    ...
  );
  const InputForm = ({ isInput, text }: { isInput: boolean; text: string; }) => {
    ...
  };

  return (
    <div>
      <Span isInput={isInput} text={value} />
      <InputForm isInput={isInput} text={value} />
    </div>
  );

}
```

出来上がりです！
【Rails】Ruby on Rails 5 速習実践ガイド感想

# はじめに
Railsを勉強中。
Ruby on Rails5 速習実践ガイドを読んだので勉強したことなどのまとめ。

:::note warn
Railsの最新バージョンは記事執筆時点で7.1となっているため、参考にされる方はバージョン差異による挙動の違いなどにお気をつけください。
:::

# 学んだこと
- リダイレクトする際の`flash[:notice]`のようなメッセージなどの渡し方は、`flash[:alert]`も用意されている
- レンダーで渡したい際には`lash.now[:alert]`なども使える
- flash.keepなども使える
- コールバック：処理の前後に任意の処理をよばせる
- オリジナルの検証処理の書き方、`validate`と`validates`は違うので気をつける
- マイグレーションでバージョン上げ下げを指定できる
- データの絞り込み条件
- scopeの活用：繰り返しの絞り込み条件を読みやすく記述できる `scope :名前 -> { 指定条件(条件の値)}`
- 重複を避けるためにフィルタを使う
- Rspec（自動テスト）について
- resourcesとresourceの使い方　`only:`専用ルート,`expect:`以外の専用ルート
- routes.rbの記載:コントローラーに沿って記述する（汚くならないように）
- 国際化(yamlファイルの記述は日本語、英語の言語単位だけでなくともモデル単位でも良い。)
- 個別にロケールを指定して取ってくる、もしくは個別のユーザーにロケールを持たせて指定するなどの対応もある
- `TimeWithZone`クラス:時間を扱う(日本時間にするときは`'Asia/Tokyo'`と`Time.Zone`にセットする)
- `Time.zone.now`=`Time.current`
- `Time.zone.today`=`Date.current`
- エラー画面を本番/開発で切り分ける, `config.consider_all_requests_local = true`とすることで本番用画面を確認できる
- 特定値のエラー出力をしない（カード情報など）`config.initializers.filter_parameter_logging.rb`で設定する
- SQL：クエリメソッドはハッシュで条件指定すると自動的に安全処理を施して検索してくれるためSQLインジェクション対策になる。できる限りハッシュで検索を行うこと。直接書きたい場合にはプレースホルダーを利用すること
- Rubyのコードや`git log`などのコマンドを意図的に埋め込まれないように実行する際に工夫すること。
- ホワイトリストで制限するなどが一番安全。渡されたものをそのまま実行するのはどのようなものがくるか想定が困難で網羅できない可能性もあるため避ける。
- アセット`asset`（画像やCSS、JSなど諸々のリソース）を効率よく扱うための設定。`config/assets.rb`に記述したり。
- メール送信処理（メーラーを利用、簡易的にsmtpサーバーを立てて実装）
- `ActiveStorage`を利用した画像添付（インストールしてマイグレーションファイルを作成し、反映すると簡単に実現できる）
- csv出力機能（標準ライブラリで簡単に実現できた）
- CSV入力機能（import）
- ページネーション機能適用（gemのkaminariを利用する）
- 非同期処理（redis＋sidekiq）
- JSでページに変化をつける
- Ajax通信
- `Turbolinks`について
- `Yarn`や`Webpacker`の導入,`React`の導入 ※rails7では`Webpacker`は使わない
- チーム開発での視点や開発環境の構築（仮想化を行ったりスクリプトを共有したりなど）
- マイグレーションやバージョンアップなどの注意点
- コードの共通化や役割分担、適切な場所に書くことについて

# 良かったこと
- アプリケーション雛形のディレクトリやファイルの説明など、Railsに触れたことがない初心者向けに丁寧に説明がされていると感じた。
- 基本のCRUD操作や各機能に追加を行う応用編などがあり、実務で応用できそうな内容など幅広く学べることができた。

# 難しかったこと
- 内容と直結する部分ではないが、関連するライブラリのインストール（`Yarn`や`Node.js`）などについての説明がなくこの辺りは調べる必要があった。しかし、逆にそういった部分を自力で調べて解決することで、全てを書籍の誘導通りに解決するのではなく自走力の鍛える助けになったのではないかと思う。

# 最後に
まだまだRailsについては勉強途中ではあるが、Rubyを勉強した後Railsを勉強し、とても便利なライブラリや機能などが用意されていると感じた。うまく活用してアプリケーション開発のスキルを上げていきたい。