【2026年6月EOL】Amazon Linux 2からAL2023への移行記|Graviton採用とHexo 7化で遭遇した4つのトラブル

はじめに

本ブログを動かしていた EC2 インスタンスは Amazon Linux 2 (以下 AL2) で稼働していましたが、AL2 のサポート終了が 2026年6月30日 に迫っていたため、Amazon Linux 2023 (以下 AL2023) への移行を行いました。

あわせて、長らく放置していた Hexo 本体のアップグレード(3.9.0 → 7.3.0)も実施しました。本記事はその作業の流れと、途中で遭遇したトラブルおよびその対処をまとめたものです。

なお、過去のnginx関連記事については AmazonLinux2のDocker Imageにnginxをインストールする を、ローカル開発環境のNode.jsバージョン管理については hexoのテーマを設定する などの過去記事も合わせてご参照ください。

移行方針

AL2 から AL2023 への インプレースアップグレードはサポートされていません。新しい AL2023 インスタンスを別途立てて、そこに既存環境を再構築するというのが AWS 公式の推奨方法となります。

そこで以下の Blue-Green 方式で進めました:

  1. 旧インスタンスは稼働させたまま、新インスタンスを並行して構築
  2. 新インスタンス上で nginx・Hexo の動作確認
  3. /etc/hosts 書き換えで実機表示を検証
  4. DNSを切り替え
  5. 旧インスタンスは数日寝かしてから停止・削除

ダウンタイムをほぼゼロにでき、問題が起きてもすぐ切り戻せるのが利点です。

新インスタンスの選定

価格と性能のバランスを考えて、Graviton(Arm)アーキテクチャを採用することにしました。

  • nginx も Node.js も AL2023 標準リポジトリに arm64 ビルドあり
  • Hexo は Pure JavaScript なのでアーキテクチャ非依存
  • 同等の x86 インスタンスと比較して約20%安い
  • ローカル開発機(Apple Silicon Mac)と環境が揃う

最終的に t4g.micro(AMI:al2023-ami-2026.X.X.X-kernel-6.1-arm64、東京リージョン)で構築しました。

インスタンスの設定として、EBSは8 GiB、セキュリティグループは以前のインスタンスに適用していた設定をそのまま流用しました。

ミドルウェアのセットアップ

1
2
3
sudo dnf update -y
sudo dnf install -y nginx git tar nodejs npm
sudo npm install -g hexo-cli

AL2では yum が標準でしたが、AL2023では dnf に変わっている点に注意してください(yum コマンドも互換のため残されていますが、dnf の使用が推奨されています)。

旧インスタンスから以下を移行しました:

  • nginx 設定(/etc/nginx/
  • Let’s Encrypt 証明書(/etc/letsencrypt/
  • Hexo のリポジトリ(Git からクローン)

遭遇したトラブルと対処

1. npm install で node-sass のビルドが失敗

最初の npm installnode-sass のビルドエラーが発生しました。

1
gyp ERR! stack SyntaxError: invalid syntax

原因node-sass が依存している node-gyp@3.8.0 は Python 2 の構文(print "...")を使っていますが、AL2023 には Python 2 がインストールされておらず、Python 3 でパースできずに失敗していました。node-sass 自体も2020年に廃止(deprecated)されています。

対処:Dart Sass(sass パッケージ)ベースの hexo-renderer-sass-next に差し替えました。

1
2
3
4
npm uninstall hexo-renderer-scss
npm install --save hexo-renderer-sass-next
rm -rf node_modules package-lock.json
npm install

package.json の差分は以下のとおりです。

1
2
3
4
5
6
7
    "hexo-renderer-ejs": "^1.0.0",
"hexo-renderer-marked": "^3.0.0",
- "hexo-renderer-scss": "^1.2.0",
+ "hexo-renderer-sass-next": "^0.1.3",
"hexo-renderer-stylus": "^2.0.0",
"hexo-server": "^2.0.0"
}

2. nginx が systemctl で操作できない

sudo systemctl reload nginx を実行すると以下のメッセージが出ました。

1
nginx.service is not active, cannot reload.

しかし ps -ef | grep nginx で確認すると nginx プロセスは動いている、という状態でした。

原因:以前テスト時に sudo nginx を直接叩いて起動していたため、systemd の管理外プロセスになっていました。

対処:手動起動の nginx を一度停止し、systemctl から起動し直しました。

1
2
3
sudo kill -QUIT <マスターPID>
sudo systemctl start nginx
sudo systemctl enable nginx

3. SELinux による 403 エラー

コンテンツを所定の場所に配置後、directory index of "..." is forbidden というエラーが出ました。

💡 SELinuxとは:Linuxカーネルに組み込まれた強制アクセス制御(Mandatory Access Control)の仕組み。通常のファイルパーミッションとは別レイヤーで、プロセスごとのアクセスを細かく制御します。AL2023ではデフォルトで有効(enforcingモード)になっており、適切なファイルコンテキストが設定されていないとnginxからアクセスできません。

原因:AL2023 はデフォルトで SELinux が enforcing モード になっています。AL2 では多くの場合 disabled / permissive で運用していたため気にしていませんでしたが、AL2023 ではこれが実効的に機能してきます。

対処restorecon コマンドでデフォルトのSELinuxコンテキストを復元します。

1
sudo restorecon -Rv /usr/share/nginx/html/book-reviews

これで403が解消できました。

4. index.html が 0 バイトになる問題(最大の難所)

403 が解消した後、今度は 真っ白な画面 が表示されるようになりました。確認すると:

1
-rw-r--r--. 1 root root 0 ... index.html

トップページの index.html だけ 0 バイト、個別記事ページや /page/2/ などは正常に生成されている、という不思議な状態でした。

原因hexo-generator-index@0.2.1(2017年リリース)が Node.js 18 で silent fail していました。Hexo 3.9.0 系は公式サポートが Node 8〜10 までで、それより新しい Node 環境では一部プラグインが例外を投げずに空ファイルを出力してしまうケースがあります。

ここで「いっそ Hexo 本体ごと最新化しよう」と判断しました。

Hexo 3.9.0 → 7.3.0 への大型アップグレード

方針

  • テーマ(landscape)は AdSense や JSON-LD などの独自カスタマイズを多数加えていたため、触らずに保持
  • Hexo 本体・generator・renderer プラグインのみ最新化
  • メンテ停止している hexo-admin は削除

テーマカスタマイズ内容を upstream の最新 landscape と diff で確認したところ、layout/_partial 配下の AdSense 連携・JSON-LD・Amazon 検索ボックスなど、収益化と SEO に直結する機能が多数含まれていました。これらを再適用するコストを考えると、テーマは現状維持が現実的でした。

package.json の更新内容

主要な変更は以下のとおりです:

パッケージ
hexo 3.9.0 7.3.0
hexo-generator-index 0.2.1 3.x
hexo-generator-archive 0.1.5 2.x
hexo-generator-category 0.1.3 2.x
hexo-generator-tag 0.2.0 2.x
hexo-generator-sitemap 1.2.0 3.x
hexo-renderer-ejs 0.3.1 2.x
hexo-renderer-marked 1.0.1 6.x
hexo-renderer-stylus 0.3.3 3.x
hexo-server 0.3.3 3.x
hexo-admin 2.3.0 (削除)

package.json は最終的に以下のようになりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
},
"hexo": {
"version": "7.3.0"
},
"dependencies": {
"hexo": "^7.3.0",
"hexo-generator-archive": "^2.0.0",
"hexo-generator-category": "^2.0.0",
"hexo-generator-index": "^3.0.0",
"hexo-generator-sitemap": "^3.0.0",
"hexo-generator-tag": "^2.0.0",
"hexo-renderer-ejs": "^2.0.0",
"hexo-renderer-marked": "^6.0.0",
"hexo-renderer-stylus": "^3.0.0",
"hexo-server": "^3.0.0"
}
}

結果

  • public/index.html が約 120KB で正常生成
  • 生成ファイル数 371 個(旧 Hexo 時代と完全一致)
  • ビルド時のエラー・警告ゼロ
  • テーマカスタマイズ(AdSense、JSON-LD、Amazon 検索ボックス)すべて動作維持
  • 表示崩れなし

カスタマイズしていた古いテーマが新しい Hexo でそのまま動いたのは正直驚きました。EJS テンプレートの後方互換性が良く保たれていたおかげだと思います。

SSL 証明書

Let’s Encrypt の証明書は旧インスタンスから /etc/letsencrypt/ 一式をコピーし、AL2023 では certbot の自動更新を systemd timer で運用する形にしました:

1
2
sudo dnf install -y certbot python3-certbot-nginx
sudo systemctl enable --now certbot-renew.timer

DNS 切り替え

  • 切り替え前に DNS の TTL を短く設定(300秒)
  • A レコードを新インスタンスのパブリック IP に更新
  • 旧インスタンスは 1 週間ほど起動状態でスタンバイ

ドメインはValue-Domain(バリュードメイン)で取得しており、DNSもValue-DomainのDNSを利用しています。
久しぶりの作業だったので少し手間取りましたが、ログインしてAレコードを編集しました。

タイムゾーンの修正

切り替え後、nginx のアクセスログを確認したところ時刻が UTC(JST から9時間ずれ)になっていることに気づきました。AL2023 のデフォルトタイムゾーンは UTC のためです。

1
2
sudo timedatectl set-timezone Asia/Tokyo
sudo systemctl restart nginx

ポイント:nginx は起動時にタイムゾーンをキャッシュするため、reload ではなく restart が必要です。

学んだこと

今回の移行作業を通じて得られた知見は以下のとおりです。

  • AL2023ではSELinuxがデフォルトで有効になっており、コンテンツ配置や権限設計時に意識する必要がある
  • node-sassのような廃止(deprecated)されたパッケージは早めに後継へ移行しておくべき。後回しにするとランタイムアップグレード時にビルド地獄に陥る
  • 古いまま動かしているソフトウェアは、Node.jsなどのランタイムを上げた瞬間にサイレントに壊れることがある(今回の index.html 0バイト問題はその典型例)
  • Graviton (Arm) は静的サイト配信のような軽量ワークロードと相性が良く、x86インスタンスに比べて約20%のコスト削減効果がある
  • nginxは起動時にタイムゾーンをキャッシュするため、timedatectl で変更したら reload ではなく restart が必要

おわりに

Amazon Linux 2 の EOL 対応をきっかけに、放置していた Hexo のバージョンも一気に最新化することができました。

特に Hexo 3.9.0 系は2019年リリースの6年以上前のバージョンで、Node.js 18 上では一部プラグインがサイレントに失敗するという危険な状態でした。EOL 対応とプラットフォームのアップグレードを同時に行えたのは、結果的に良いタイミングだったと思います。

しばらくブログの運用は止まってしまっていましたが、やはり自分の学びやログを残すことはとても重要ですし、ブログを書くというアウトプットの目的があるからこそ、インプットが捗ると改めて感じています。

今後の課題としては以下を予定しています。

  • 記事を git push すると自動でデプロイ・HTML生成される CI/CDパイプラインの構築
  • 今回のように長期間放置してアップグレードコストが膨らまないよう、定期的なチェック・アップデート機構の整備

これらの仕組みも整えながら、技術記事と投資記事の両方を継続的にアウトプットしていきたいと思います。


📌 関連記事

【2026年5月1日】エンジニアの高配当株ポートフォリオ記録|5大商社の決算・増配・自社株買いラッシュ(vs 日経平均・TOPIX)

今日のサマリー

5月1日の日本株は、米株高を受けて日経平均が反発したものの、連休前で上値は限定的。中東情勢を背景に原油高・資源高への警戒が続く中、5大商社は増益見通しや増配、自社株買いを相次いで発表し、決算を手掛かりにした個別物色が相場を支えました。

5大商社の決算・増配・自社株買いまとめ

5大商社の見通し、配当、自社株買いをまとめると以下のようになります。

銘柄 2027年3月期見通し 予想配当 自社株買い
三菱商事 純利益は37.4%増の1.1兆円 125円 -
三井物産 純利益は10.3%増の9,200億円 115円→140円に増配 中期経営計画2029の期間中、3年間累計の基礎営業キャッシュ・フローの50%水準を目安として、株主還元(配当・自己株式取得)を実施する方針
伊藤忠商事 純利益は5.5%増の9,500億円 44円以上 3,000億円以上
住友商事 純利益は4.9%増の6,300億円 40円(4分割後の金額。150円→160円に増配) 上限800億円
丸紅 純利益は6.6%増の5,800億円 107.5円→115円に増配 上限600億円

住友商事は株式分割(1株→4株)も発表しています。

主要指数とポートフォリオの比較

指標 終値 前日比 騰落率
私のポートフォリオ 31,767,503円 +106,851円 +0.34%
日経平均 59,513.12円 +228.20円 +0.38%
TOPIX 3,728.73 +1.52 +0.04%

日経平均にはわずかに及ばなかったものの、私が指標としているTOPIX(+0.04%)はアウトパフォームできたので満足の一日でした。

私のポートフォリオはバリュー株が中心のため、グロース株比率が高い日経平均よりも、市場全体を反映するTOPIXに連動しやすい傾向があります。そのため、日々のパフォーマンス評価ではTOPIXをベンチマークとして、これを上回ることを目標にしています。

本日の購入銘柄

今日はわたしのお気に入りの銘柄である東京建物がさらに下げていたので2株買い増ししました。
購入基準価格は3,600円においています。

💡 東京建物とは
旧安田系の総合不動産。賃貸ビルとマンションが主力。オフィスや物流など収益物件開発を強化。(会社四季報より)

わたしがこの銘柄を買おうと思った理由は、東京建物グループが運営する「おふろの王様」(運営:東京建物リゾート)に以前家族で行った時に、とても楽しく、施設内の食事もとても美味しかったので、また来たい!と思ったからです。

優待は200株からですが、内容も豊富。コツコツと買い増しして、12月末の権利取り日には200株を保有しておきたいと思っています。

銘柄(コード) 約定株数 約定単価 平均取得単価
東京建物(8804) 2 3,559 3,686

今日は珍しく株を少し売りました。100株以上持っているのですが、分割によってキリの悪い数字になっていたものです。

銘柄(コード) 約定株数 約定単価 平均取得単価
日東富士(2003) 4 1,775 1,639
COTA(4923) 5 1,131 1,278

私の高配当株ポートフォリオ実績

下落TOP5

銘柄(コード) 保有数 終値 前日比(%) 評価損益 配当利回り
丸紅(8002) 200 5,755 -317(-5.22%) +745,200 1.87%
NSグループ(471A) 20 1,651 -74(-4.29%) +6,120 4.6%
シーティーエス(4345) 10 835 -30(-3.47%) +630 3.47%
日本マイクロニクス(6871) 1 12,560 -400(-3.09%) +4,850 0.96%
PILLAR(6490) 5 8,840 -280(-3.07%) +27,575 1.47%

上昇TOP5

銘柄(コード) 保有数 終値 前日比(%) 評価損益 配当利回り
住友商事(8053) 120 6,840 +1,000(+17.12%) +474,240 2.05%
豊田通商(8015) 55 6,868 +767(+12.57%) +243,595 1.69%
三菱鉛筆(7976) 5 2,547 +213(+9.13%) +1,830 2.16%
東洋製罐グループホールディングス(5901) 8 3,438 +199(+6.14%) -1,328 3.32%
双日(2768) 45 6,218 +358(+6.11%) +145,620 2.65%

ポートフォリオ全体

評価額 含み損益 含み損益(%) 前日比 前日比(%)
31,767,503円 +10,964,560円 +52.71% +106,851円 +0.34%

5大商社の決算の明暗が今日の値動きに現れています。
住友商事があわやストップ高となる水準での上昇率でした。
三菱商事(上昇7位、前日比+4.59%)、伊藤忠商事(上昇12位、前日比+2.53%)も強かったです。
逆に、丸紅(下落1位)、三井物産(下落8位、前日比-2.22%)は弱かったです。
その他では東京エレクトロンの決算が良くて強かったですが、わたしが少しだけ持っている半導体関連も弱かったです。

📚 初心者向けワンポイント解説:「自社株買い」とは

自社株買い(自己株式取得) とは、企業が自分の会社の株式を市場から買い戻すこと。発行済み株式数が減るため、1株あたり利益(EPS)や1株あたり純資産(BPS)が向上し、株価が上昇しやすくなります。配当と並ぶ代表的な「株主還元策」の一つです。

本日の5大商社の発表を見ると、伊藤忠商事は3,000億円以上、住友商事は最大800億円、丸紅は最大600億円という大規模な自社株買いを発表。これが今日の商社株上昇の大きな要因となりました。

💡 覚えておきたいポイント:自社株買いの発表は、その企業が「自社の株は割安」と考えていることのサイン。長期保有を考える高配当株投資家にとっては、増配と並んでポジティブなニュースです。

明日以降の注目ポイント

  • GW明けからの決算発表ラッシュ(個別物色相場の継続)
  • 5月末からの3月期決算企業の配当支払い開始
  • 中東情勢と原油価格の動向
  • 米株式市場の動向と為替(ドル円)

まとめ

金曜はいつも弱く、理由としては休みに入る前にポジションを解消したいという投資家が多いからと聞いています。
今日は連休前にも関わらず、上昇しました。やはり決算が良いとそれに合わせて買われるようです。逆に決算が良くないともちろん売られてしまいます。決算が良くない原因が一時的なものであれば、そこで買い増すのも一つの手かと思っています。

GW明けから決算がさらに本格化します。
5月末からは3月末決算の企業から配当金が来ます。楽しみにしています。


免責事項:本記事は個人の投資記録であり、特定銘柄の売買を推奨するものではありません。投資はご自身の判断と責任で行ってください。

【2026年4月30日】エンジニアの高配当株ポートフォリオ記録|原油高で続落-0.87%、下水道関連を仕込み(vs 日経平均・TOPIX)

今日のサマリー

4月30日の日本株は、中東情勢の緊迫化に伴う原油高が景気・企業業績への懸念を強めたほか、日米中銀のタカ派姿勢による金利上昇警戒も重なり、リスクオフ優勢の展開となりました。

日経平均は前営業日比632円54銭安の5万9284円92銭で続落しました。原油価格の高止まり、実体経済への影響、日米中銀のタカ派姿勢が重しになった一方、決算を手掛かりにした個別物色は続いたと報じられています。

4月28日はバリュー株が優勢で、日経平均は下落したものの、TOPIXは上昇しました。
わたしのポートフォリオは+2.15%でしたが、本日はTOPIXも反落、朝は-1.5%超でしたが、持ち直し、-0.87%で着地しました。

主要指数とポートフォリオの比較

指標 終値 前日比 騰落率
私のポートフォリオ 31,666,059円 -277,207円 -0.87%
日経平均 59,284.92円 -632.54円 -1.06%
TOPIX 3,727.21 -44.98 -1.19%

主要指数がいずれも-1%超の下落となるなか、私のポートフォリオは-0.87%にとどまり、日経平均・TOPIXの両方をアウトパフォームしました。バリュー株中心の構成がディフェンシブに機能した一日でした。

本日の購入銘柄

本日は中東情勢を背景にした原油高によりリスクオフの相場展開でした。なかでも下水道関連銘柄(前澤給装工業、前澤工業、栗本鐵工所など)の下げが目立ったため、これら下水道関連で含み損が大きい銘柄を中心にナンピン買いを実施しました。

あわせて、3,600円を割り込んだ東京建物、長期保有方針のセブン&アイ・ホールディングスパン・パシフィック・インターナショナルなども計11銘柄を買い増ししています。

💡 ナンピン買いとは:保有銘柄の株価が下がったときに買い増して、平均取得単価を下げる手法のこと。下落要因が一時的(業績悪化ではなく相場環境による下げ)なときに有効ですが、業績悪化が原因の下落で行うと損失を拡大させるリスクもあります。

銘柄(コード) 約定株数 約定単価 平均取得単価
ショーボンドHD(1414) 4 1,325 1,386
大末建設(1814) 2 3,425 3,617
セブン&アイ・ホールディングス (3382) 4 1,871 1,961
関西ペイント(4613) 2 2,342.5 2,460
合同製鐵(5410) 2 2,845 3,682
栗本鐵工所(5602) 4 1,503 1,572
前澤給装工業(6485) 6 1,493 1,568
前澤工業(6489) 4 1,810 1,992
トピー工業(7231) 2 2,947 3,109
パン・パシフィック・インターナショナルホールディングス(7532) 10 889.4 907
東京建物(8804) 2 3,599 3,691

私の高配当株ポートフォリオ実績

下落TOP5

銘柄(コード) 保有数 終値 前日比(%) 評価損益 配当利回り
南海辰村建設(1850) 300 425 -42(-8.99%) +32,100 1.41%
大林組(1802) 34 3,659 -185(-4.81%) +53,312 2.38%
JPX(8697) 100 1,863.5 -88.5(-4.53%) +43,850 3.27%
コマツ(6301) 100 6,557 -299(-4.36%) +293,900 2.9%
豊田通商(8015) 55 6,101 -278(-4.36%) +201,410 1.9%

上昇TOP5

銘柄(コード) 保有数 終値 前日比(%) 評価損益 配当利回り
NGK(5333) 20 4,961 +291(+6.23%) +59,700 1.61%
日置電(6866) 1 11,180 +540(+5.08%) +5,140 1.79%
ツガミ(6101) 2 4,690 +195(+4.34%) +3,150 1.54%
スカパーJSAT(9412) 100 3,450 +135(+4.07%) +278,900 1.39%
FPG(7148) 40 1,595 +62(+4.04%) -16,800 5.81%

ポートフォリオ全体

評価額 含み損益 含み損益(%) 前日比 前日比(%)
31,666,059円 +10,857,292円 +52.18% -277,207円 -0.87%

📚 初心者向けワンポイント解説:「アウトパフォーム」とは

アウトパフォーム(outperform)とは、自分の投資成績が市場平均(日経平均やTOPIXなどの指数)を上回ること。逆に下回ることを「アンダーパフォーム」と呼びます。

本日の私のポートフォリオは-0.87%で、日経平均(-1.06%)とTOPIX(-1.19%)の両方をアウトパフォームしました。下落相場でも下げ幅が小さく済めばアウトパフォームになるため、「勝ち負け」は必ずしも上昇率で決まるわけではありません。

高配当バリュー株は、グロース株に比べて値動きが穏やかで、下落相場で相対的に強い傾向があります。今回の結果はその特徴がよく表れた一日と言えるでしょう。

明日以降の注目ポイント

  • 中東情勢と原油価格の動向
  • 日米中央銀行の金融政策スタンス
  • 決算発表が続く個別銘柄の業績反応

まとめ

2026年4月30日は原油高と金利上昇懸念で続落しましたが、高配当バリュー株中心のポートフォリオは下げ幅を抑えることができました。下げ局面はナンピン買いの好機でもあり、本日は計11銘柄を買い増し。長期視点で配当を積み上げていく戦略を継続します。


📌 関連記事(今後追加予定)

  • 高配当株投資を始めた理由
  • 私が使っている証券会社の比較
  • 高配当バリュー株の選び方|初心者向け解説

免責事項:本記事は個人の投資記録であり、特定銘柄の売買を推奨するものではありません。投資はご自身の判断と責任で行ってください。

nestされたresourcesでのcontrollerの指定

nestされたresoucesでのController

1対多などの関連を持つmodelをそれぞれ直接操作したい時があります。
その場合は、それぞれのmodelに対してcontrollerを作成し、CRUDなどの操作を行います。

それとは別に1対多のリソースの関係をURLに反映したい場合もあります。
例えば、author has many postsだった場合、特定のauthorが持つpostsの一覧は以下のようなURLになると思います。

1
/authors/:author_id/posts

このURLを実現するために、routingの設定は以下のようにします。

1
2
3
resources :authors do
resources :posts
end

ただ、この設定をrails routesで確認すると、先ほど記載したURL(authorにぶら下がったposts)のControllerは、posts#indexとなってしまい、authorにぶら下げないpostsと同じControllerになってしまいます。

対処法

このままだとposts_controller.rbにいろんなメソッドを追加しないといけなくなってしまうので、authorにぶらさがるpostsを処理するControlerは別で定義したいと思います。

ディレクトリの作成

app/controllersの下にauthorsというディレクトリを作成します。このディレクトリの配下にauthorにぶら下がるリソースのControllerを作成していきます。

Controllerの作成

先ほど作成したauthorsの下にposts_controller.rbを作成します。
app/controllers配下のposts_controller.rbの違いとしては、namespaceを持っているということです。

1
2
3
4
module Authors
class PostsController < ApplicationController
end
end

routingの設定

現在のroutingの設定に、作成したControllerを指定します。

1
2
3
resources :authors do
resources :posts, controller: "authors/posts"
end

まとめ

以上でnestされたresourcesに対応するControllerを設定することができました。
こういったmodelの関連を表すURLはよく作成するので忘れないようにしたいと思います。

npm startでエラー 'React' must be in scope when using JSX

背景

Reactの本を買ったので、一年半ぶりにReactの記事を書きます。

書籍に書いてある通りcreate-react-appを実行して作成されたアプリケーションのディレクトリに移動します。

1
2
3
$ npx create-react-app my-react
$ cd my-react
$ npm start

すると、ブラウザに表示された画面は真っ白で、コンソールには

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> my-react@0.1.0 start
> react-scripts start
Starting the development server...
Failed to compile.

./src/App.js
Line 7:5: 'React' must be in scope when using JSX react/react-in-jsx-scope
Line 8:7: 'React' must be in scope when using JSX react/react-in-jsx-scope
Line 9:9: 'React' must be in scope when using JSX react/react-in-jsx-scope
Line 10:9: 'React' must be in scope when using JSX react/react-in-jsx-scope
Line 11:16: 'React' must be in scope when using JSX react/react-in-jsx-scope
Line 13:9: 'React' must be in scope when using JSX react/react-in-jsx-scope

Search for the keywords to learn more about each error.

と表示されてしまいました。

原因

調べてみると、src/App.jsの先頭行に以下の記述が必要だったようです。

1
import React from 'react';

一旦この行を追加してから実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ npm start

> my-react@0.1.0 start
> react-scripts start
Starting the development server...
Compiled successfully!

You can now view my-react in the browser.

Local: http://localhost:3000/
On Your Network: http://192.168.11.7:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.

確かに問題なく動作しました。
であれば、なぜその行の記述がなかったのでしょうか。

JSX Transform

React17から導入されたJSX Transfromでは、先ほど記述したimport文が不要になるというメリットがあるようです。
なにが変わったかというと、トランスパイル後のコードでReactを読み込まなくても動作するようになるということのようです。

では、なぜ、今回その行を追加しないとエラーになってしまったのでしょうか。

ESLint

調べてみるとESLintの設定でエラーを抑制するように書かれていました。
package.jsonを変更しながら、ruleの箇所を追加したり、extendsを変更したりしました。
しかし、結果的に修正できませんでした。

npm install react-scripts@latest

最後の方法として、react-scriptsを最新版にすることでした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ npm install react-scripts@latest
npm WARN deprecated @babel/plugin-proposal-private-methods@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
npm WARN deprecated @babel/plugin-proposal-numeric-separator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
npm WARN deprecated @babel/plugin-proposal-nullish-coalescing-operator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
npm WARN deprecated @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
npm WARN deprecated rollup-plugin-terser@7.0.2: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
npm WARN deprecated @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
npm WARN deprecated domexception@2.0.1: Use your platform's native DOMException instead
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
npm WARN deprecated workbox-cacheable-response@6.6.0: workbox-background-sync@6.6.0

added 567 packages, removed 766 packages, changed 382 packages, and audited 1555 packages in 1m

254 packages are looking for funding
run `npm fund` for details

8 vulnerabilities (2 moderate, 6 high)

To address all issues (including breaking changes), run:
npm audit fix --force

Run `npm audit` for details.

deprecatedは一旦置いておいて、その後にnpm startします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ npm start

> my-react@0.1.0 start
> react-scripts start

(node:79721) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:79721) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
Starting the development server...
Compiled successfully!

You can now view my-react in the browser.

Local: http://localhost:3000
On Your Network: http://192.168.11.7:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully

無事動作しました。

何が変わったのか

何が変わったのかをみてみると、package.jsonの差分は1行だけでした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git diff package.json
diff --git a/package.json b/package.json
index 28afd0a..bd487ff 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-scripts": "5.0.1",
+ "react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {

package-lock.jsonをみると、結構変わっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
$ git diff package-lock.json
diff --git a/package-lock.json b/package-lock.json
index ba7b477..f91a96e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-scripts": "5.0.1",
+ "react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
}
},
@@ -257,9 +257,9 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
- "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
+ "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
"@babel/helper-plugin-utils": "^7.22.5",
@@ -3427,9 +3427,9 @@
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
},
"node_modules/@rushstack/eslint-patch": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz",
- "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw=="
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.0.tgz",
+ "integrity": "sha512-Jh4t/593gxs0lJZ/z3NnasKlplXT2f+4y/LZYuaKZW5KAaiVFL/fThhs+17EbUd53jUVJ0QudYCBGbN/psvaqg=="
},
"node_modules/@sinclair/typebox": {
"version": "0.24.51",
@@ -5359,9 +5359,9 @@
}
},
"node_modules/autoprefixer": {
- "version": "10.4.16",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
- "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSY
Jx5GKcpRCzBh7RH4/0dnY+uQ==",
+ "version": "10.4.17",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
+ "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3B
hA+nLo5tX8Cu0upUsGKvKbmg==",
"funding": [
{
"type": "opencollective",
@@ -5377,9 +5377,9 @@
}
],
"dependencies": {
- "browserslist": "^4.21.10",
- "caniuse-lite": "^1.0.30001538",
- "fraction.js": "^4.3.6",
+ "browserslist": "^4.22.2",
+ "caniuse-lite": "^1.0.30001578",
+ "fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
@@ -5593,12 +5593,12 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.7",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz",
- "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==",
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz",
+ "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==",
"dependencies": {
"@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.4.4",
+ "@babel/helper-define-polyfill-provider": "^0.5.0",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -5625,12 +5625,27 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
+ "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz",
+ "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
"node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz",
- "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz",
+ "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.4.4"
+ "@babel/helper-define-polyfill-provider": "^0.5.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -5965,9 +5980,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001576",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz",
- "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==",
+ "version": "1.0.30001579",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz",
+ "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==",
"funding": [
{
"type": "opencollective",
@@ -8427,9 +8442,9 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ=="
},
"node_modules/follow-redirects": {
- "version": "1.15.4",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
- "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
@@ -13960,9 +13975,9 @@
}
},
"node_modules/postcss-modules-local-by-default": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz",
- "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
+ "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
"dependencies": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
@@ -15407,12 +15422,12 @@
}
},
"node_modules/safe-array-concat": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
- "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
+ "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==",
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1",
+ "call-bind": "^1.0.5",
+ "get-intrinsic": "^1.2.2",
"has-symbols": "^1.0.3",
"isarray": "^2.0.5"
},
@@ -16611,9 +16626,9 @@
}
},
"node_modules/terser": {
- "version": "5.26.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz",
- "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==",
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
+ "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",

多すぎてどこが影響しているのかわかりません…

まとめ

ESLintの問題だと思って色々試していましたが、そうじゃなかったかもしれません。
まだ初心者なので、実際の原因はわかりませんが、こういう解決方法もあるのだということを学びました。

参考文献

Ruby 3.3.0のインストールエラー

背景

Ruby3.3.0が2023年12月にリリースされました。
Railsを使った簡単なアプリを作成するために、せっかくなので、最新の3.3.0を使おうと思って、rbenvでインストールしようとしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ rbenv install 3.3.0
ruby-build: using openssl@1.1 from homebrew
==> Downloading ruby-3.3.0.tar.gz...
-> curl -q -fL -o ruby-3.3.0.tar.gz https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 21.0M 100 21.0M 0 0 15.6M 0 0:00:01 0:00:01 --:--:-- 15.6M
==> Installing ruby-3.3.0...
ruby-build: using readline from homebrew
-> ./configure "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.3.0" --with-openssl-dir=/usr/local/opt/openssl@1.1 --enable-shared --with-readline-dir=/usr/local/opt/readline --with-ext=openssl,psych,+
-> make -j 8
*** Following extensions are not compiled:
psych:
Could not be configured. It will not be installed.
Check /var/folders/vs/21c14yw15wngfp0fn50h2zkw0000gp/T/ruby-build.20240116222001.35871.p9E1v4/ruby-3.3.0/ext/psych/mkmf.log for more details.

BUILD FAILED (macOS 13.6 on x86_64 using ruby-build 20231225-2-g3b79c29)

You can inspect the build directory at /var/folders/vs/21c14yw15wngfp0fn50h2zkw0000gp/T/ruby-build.20240116222001.35871.p9E1v4
See the full build log at /var/folders/vs/21c14yw15wngfp0fn50h2zkw0000gp/T/ruby-build.20240116222001.35871.log

psychというextensionがコンパイルできなかったとして、エラーになってしまいました。
ログを見るとわかると思うのですが、インテルMacを利用しています。

原因

エラーメッセージをよく見ると、

1
2
3
4
*** Following extensions are not compiled:
psych:
Could not be configured. It will not be installed.
Check /var/folders/vs/21c14yw15wngfp0fn50h2zkw0000gp/T/ruby-build.20240116222001.35871.p9E1v4/ruby-3.3.0/ext/psych/mkmf.log for more details.

より詳細な情報がmkmf.logに記載されていると書かれているので、それを見てみます。
Rubyインストール前のrbenvでのコンパイル処理のディレクトリは/var/folders/あたりを利用しているんですね。

mkmf.log

1
2
3
4
5
6
pkg_config: checking for pkg-config for yaml-0.1... -------------------- not found

package configuration for yaml-0.1 is not found
--------------------

find_header: checking for yaml.h... -------------------- no

yaml.hが見つからないとなってエラーになっています。
ヘッダファイルが見つからない場合はだいたいlibヘッダファイル名のライブラリがインストールされていないのが原因です。

対策

調べてみるとRuby3.2.0以降ではlibyamlが必須になっているようでした。
homebrewでインストールします。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ brew install libyaml
Running `brew update --auto-update`...
(省略)
==> Downloading https://ghcr.io/v2/homebrew/core/libyaml/manifests/0.2.5
################################################################################### 100.0%
==> Fetching libyaml
==> Downloading https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:b49e62f014b3e7d85a16
################################################################################### 100.0%
==> Pouring libyaml--0.2.5.ventura.bottle.tar.gz
🍺 /usr/local/Cellar/libyaml/0.2.5: 10 files, 330KB
==> Running `brew cleanup libyaml`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

インストールできました。
再度Ruby3.3.0のインストールを行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ rbenv install 3.3.0
ruby-build: using openssl@1.1 from homebrew
==> Downloading ruby-3.3.0.tar.gz...
-> curl -q -fL -o ruby-3.3.0.tar.gz https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 21.0M 100 21.0M 0 0 6361k 0 0:00:03 0:00:03 --:--:-- 6371k
==> Installing ruby-3.3.0...
ruby-build: using readline from homebrew
ruby-build: using libyaml from homebrew
-> ./configure "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.3.0" --with-openssl-dir=/usr/local/opt/openssl@1.1 --enable-shared --with-readline-dir=/usr/local/opt/readline --with-libyaml-dir=/usr/local/opt/libyaml --with-ext=openssl,psych,+
-> make -j 8
-> make install
==> Installed ruby-3.3.0 to /Users/user/.anyenv/envs/rbenv/versions/3.3.0

NOTE: to activate this Ruby version as the new default, run: rbenv global 3.3.0

無事インストールできました。
エラーが起こったときとconfigureオプションも異なっていて、libyamlがインストールされたことで、--with-libyaml-dir=/usr/local/opt/libyamlが追加されています。

まとめ

久しぶりにRubyのインストールを行いましたが、躓いてしまいました。
3.2.0からの変更点だったということで、今更ここで躓く方はそれほど多くないと思います。
やはり新しい情報は常にキャッチアップしていかないといけないと思いました。

git diffで^Mが表示された時の対応方法(改行コードをCRLFからLFに変換)

背景

Gitを使っていると他の方が作成したファイルを変更することも多々あります。

今回のそのケースで、変更後git diffで変更点を確認すると、

1
2
3
+        },^M
+ {^M
}

という形で見慣れた^Mが表示されてしまいました。

最初はコピペ元が良くないのかと思っていたのですが、そうではなかったです。原因と対策を以下に記載します。

原因

原因は改行コードの不一致でした。元のファイルの改行コードはCRLFでわたしの環境ではLF(Mac)だったため、CRが足りずに^Mという表示がされてしまったようです。

対策

元のファイルの改行コードをLFに変換します。vscodeを使って行います。

まず、該当のファイルを開きます。ウインドウの右下に今開いているファイルの文字コードや改行コード(CRLF)が表示されています。

改行コードをクリックすると、ウインドウの上部に改行コードの選択と表示されて、LFとCRLFを選択できるようになります。そこでLFを選択し、保存します。

見た目は何もかわっていないのですが、git diffで確認すると、全ての行が変更されているのがわかります。

その後、変更を加えても^Mは表示されなくなりました。

まとめ

改行コードの違いについて理解しているものの、^Mが表示されるとなんでこうなった?と軽めのパニック的な感じになってしまいます。Windowsを使っている方と一緒にプロジェクトに取り組む場合はよく発生すると思います。

今後はこういったことのないようにこの記事を思い出して対応しようと思います。

ジェネレータで特定のディレクトリ配下にcontrollerを作成する

背景

APIの実装する場合など、pathにプレフィックスがつくことが多い(apiv1など)。その場合に、rails g controllerでそのディレクトリ配下にコントローラーを作成する方法がわからなかかったので調べてみました。

パスの指定方法

作成方法は2通りあるようです。
(今回は、app/controllers/api/v1/配下にtests_controller.rb作ることとします。)

/で区切るパターン

1
$ rails g controller api/v1/tests

namespace(::)で区切るパターン

1
$ rails g controller api::v1::tests

不要なファイルを生成しないための方法

今回はAPIなので、viewファイルなどは不要でした。なので、generatorのオプションでviewファイルを生成しないようにできないかを調べてみました。helpを見てみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
Usage:
rails generate controller NAME [action action] [options]

Options:
[--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated engines)
[--skip-collision-check], [--no-skip-collision-check] # Skip collision check
[--skip-routes], [--no-skip-routes] # Don't add routes to config/routes.rb.
[--helper], [--no-helper] # Indicates when to generate helper
# Default: true
-e, [--template-engine=NAME] # Template engine to be invoked
# Default: erb
-t, [--test-framework=NAME] # Test framework to be invoked
# Default: rspec

Rspec options:
[--request-specs], [--no-request-specs] # Generate request specs
# Default: true
[--controller-specs], [--no-controller-specs] # Generate controller specs
[--view-specs], [--no-view-specs] # Generate view specs
[--routing-specs], [--no-routing-specs] # Generate routing specs
[--helper-specs], [--no-helper-specs] # Indicates when to generate helper specs

Runtime options:
-f, [--force] # Overwrite files that already exist
-p, [--pretend], [--no-pretend] # Run but do not make any changes
-q, [--quiet], [--no-quiet] # Suppress status output
-s, [--skip], [--no-skip] # Skip files that already exist

Description:
Generates a new controller and its views. Pass the controller name, either
CamelCased or under_scored, and a list of views as arguments.

To create a controller within a module, specify the controller name as a
path like 'parent_module/controller_name'.

This generates a controller class in app/controllers and invokes helper,
template engine, assets, and test framework generators.

Example:
`bin/rails generate controller CreditCards open debit credit close`

CreditCards controller with URLs like /credit_cards/debit.
Controller: app/controllers/credit_cards_controller.rb
Test: test/controllers/credit_cards_controller_test.rb
Views: app/views/credit_cards/debit.html.erb [...]
Helper: app/helpers/credit_cards_helper.rb

今回は--no-helper--no-request-specsを指定してみます。

1
2
3
4
5
$ rails g controller api/v1/tests --no-helper --no-request-specs
create app/controllers/api/v1/tests_controller.rb
invoke erb
create app/views/api/v1/tests
invoke rspec

APIなのでviewsディレクトリも不要なのですが、ジェネレータのオプションで生成を制御できないようだったので、生成後削除しました。

まとめ

今回はジェネレータを使って特定のディレクトリ配下にcontrollerを作成する方法について調べてみました。
ヘルプを見ればわかる話でしたが、2通りの方法があることがわかりました。
また、ジェネレータ実行時のオプションについても調べました。

ジェネレータは便利ですし、カスタムジェネレータの作成もできます。
いろいろ便利なので、深く掘って理解したいと思います。

letter_openerの設定

ローカル開発環境でメール受信

ローカル開発環境でのメール受信はletter_openerを使います。プロジェクトの初期設定で必須になると思いますが、忘れがちなのでメモします。

gemの追加

letter_opener_webというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "letter_opener_web"
end
(その他のgemは省略しています)

bundle installします

1
$ docker compose run --rm web ./bin/bundle install

インストールができました。GemfileとGemfile.lockをcommitしておきます。

routesの設定

http://localhost:3000/letter_openerでアクセスできるようconfig/routes.rbに以下の行を追加します

1
2
3
Rails.application.routes.draw do
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end

ActionMailerの設定

最後にActionMailerの設定をします。config/environments/development.rbに以下の2行を追加します。

1
2
3
config.action_mailer.default_url_options = { host: "localhost:3000"}

config.action_mailer.delivery_method = :letter_opener_web

config.action_mailerが記載されている付近に追加します。

1行目の設定はこの設定がないと、メールにURLを記載する場合のドメインがわからずエラーになってしまいます。

2行目の設定は、letter_opener_webでメールを送るという設定で、この設定がないとhttp://localhost:3000/letter_openerにアクセスしてもメールが見れません(メール送信のログが表示されて送られているように見えるがどこにも届かない)

まとめ

Railsの開発環境構築はいろいろ設定することが多いですが、設定する機会が少なく忘れてしまいます。出だしで躓かないようメモしておきました。

annotateの設定

テーブルのスキーマがわからなくなるときの対応

Railsで開発を行なっているときに、まだ馴染みのないモデルに遭遇すると、どういったカラムがあるかなど、スキーマがわからずに困ることがよくあります。

db/schema.rbを確認すればわかるのですが、モデルのファイルを見ながらdb/schema.rbを見るのは不便です。

何かしらの方法でテーブルのスキーマを確認しつつモデル内でロジックを実装したい、その場合に利用すると便利なgemがannotateです。

gemの追加

annotateというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "annotate"
end
(その他のgemは省略しています)

bundle installします

1
2
3
4
$ docker compose run --rm web ./bin/bundle install
...
Fetching annotate 3.2.0
Installing annotate 3.2.0

インストールができました。GemfileとGemfile.lockをcommitしておきます。

annotateの設定ファイルの作成

annotateの設定ファイルを生成するgeneratorがあるので実行します。

1
2
$ docker compose run --rm web ./bin/rails g annotate:install
create lib/tasks/auto_annotate_models.rake

annotateのカスタマイズ

カスタマイズに関しては、先程作成したタスクのAnnotate.set_defaultsメソッドの引数を編集することで行います。

わたしが変更した内容を記載します。

position_in_xxxx

xxxxにはclassfactoryなどが入ります。それぞれのファイルのどの部分にannotateによるドキュメントを追加するか、という設定になります。
コードの後ろに追加した方が見通しがいいかなと思い、afterに変更しています(デフォルトはbefore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       'routes'                      => 'false',
'models' => 'true',
- 'position_in_routes' => 'before',
- 'position_in_class' => 'before',
- 'position_in_test' => 'before',
- 'position_in_fixture' => 'before',
- 'position_in_factory' => 'before',
- 'position_in_serializer' => 'before',
+ 'position_in_routes' => 'after',
+ 'position_in_class' => 'after',
+ 'position_in_test' => 'after',
+ 'position_in_fixture' => 'after',
+ 'position_in_factory' => 'after',
+ 'position_in_serializer' => 'after',
'show_foreign_keys' => 'true',

with_comment

trueだとカラム名の横にそのカラムに設定されているコメントが追加されます。ですが、カラムを削除する際に、annotateが正しく動作しなかったので、falseに設定します。

1
2
3
4
5
6
       'wrapper_open'                => nil,
'wrapper_close' => nil,
- 'with_comment' => 'true'
+ 'with_comment' => 'false'
)
end

動作確認

ここで正しく動作するか確認します。

1
2
3
$ docker compose run --rm web ./bin/bundle exec annotate

Annotated (ファイル数): ファイル名, ....

このように表示されればOKです。ファイル数にはコメントが追加されたファイル数、ファイル名にはコメントが追加されたファイルのパスが表示されます。

まとめ

annotateはいつも使っている便利なgemです。慣れている人なら馴染み深いものではありますが、始めたばかりの人にとっては知らないけど知っているととても便利なものという感じかと思います。ぜひ使ってみてください。

参考URL