【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

RSpecの初期設定

RSpecの初期設定

RailsのプロジェクトでテストフレームワークはRSpecを使っています。

プロジェクト開始時に設定するのですが、あまり設定することもないので、毎回調べて時間がかかってしまいます。なので、備忘録として手順を記載します。

gemの追加

まずgemを追加します。FactoryBotも利用するのでついでに追加します。

1
2
3
4
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
end

bundle installします

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

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

RSpecの設定

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

1
2
3
4
5
$ docker compose run --rm web ./bin/rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

出力のカスタマイズ

出力をドキュメント形式にするために、.rspecに以下の行を追加します。

1
2
3
$ vi .rspec
--require spec_helper
--format documentation

(2行目を追加)

動作確認

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

1
2
3
4
5
$ docker compose run --rm web bundle exec rspec
No examples found.

Finished in 0.00087 seconds (files took 0.171 seconds to load)
0 examples, 0 failures

このように表示されればOKです。

binstubを作成する

毎回bundle exec rspecとタイプするのは面倒なので、binstubを作成して./bin/rspecとシェルの補完機能も使いながらタイプ数を減らしたいと思います。

1
$ docker compose run --rm web bundle binstubs rspec-core

bin/rspecというファイルが作成されました。

今度はbinstubを利用して実行してみます。

1
2
3
4
5
$ docker compose run --rm web ./bin/rspec 
No examples found.

Finished in 0.0012 seconds (files took 0.15613 seconds to load)
0 examples, 0 failures

同じように実行できました。

RSpecを利用するよう設定

generatorで自動生成するファイルをRSpecのファイルにしたいので、利用するテストフレームワークがRSpecだということをRailsに伝えます。

1
2
3
4
5
6
7
8
9
$ vi config/application.rb
...(省略)
config.generators do |g|
g.test_framework :rspec,
view_specs: false,
helper_specs: false,
routing_specs: false
end
...(省略)

上記のコードを追加することで、generatorがRSpecのファイルを生成してくれます。

まとめ

今後もテストフレームワークはRSpecを利用していくと思うので、導入手順をまとめました。

参考図書にリンクを記載したeveryday-railsですが、著者の伊藤さんが日本語版独自の改訂を続けてくれています。一度購入するとその後の改訂版は無料で受け取ることができます。とてもためになる書籍なので読んでみることを強くおすすめします。

参考図書

docker compose buildでpermission deniedが出た時の対応

docker compose buildでエラー発生

久しぶりにDockerfileを編集したので、イメージをビルドしようと思い、以下のコマンドを実行しました

1
$ docker compose build web

(docker-compose.yml上でwebというサービスが設定されている前提です)

何もなければこのまま終わるはずですが、エラーが発生して実行できてないようです。

1
2
$ docker compose build web
open /User/user/.docker/buildx/current: permission denied

対処法

いろいろ調べたのですが、手軽な対処法がありました。環境変数を設定し、buildxを利用しないようにするというものです。docker compose buildを実行するターミナルで以下のコマンドを実行します。

1
$ export DOCKER_BUILDKIT=0

docker compose buildを試します。

1
2
$ docker compose build web
buildが実行できるログ

まとめ

buildxはDocker Desktopを利用していると自動的に有効になるようです。原因がわからない場合は一旦buildxを無効にするのが簡単な対応方法です。

参考図書