OmniAICreator/Qiita-1.07M huggingface dataset:
railsアプリをDocker化する

## はじめに
チーム開発において重宝するDocker、Docker-composeを用いると
メンバー間で同じ実行環境をスムーズに用意できます。
今回railsアプリを例に実際にDocker化して他の方が簡単にアプリの実行環境を
用意できるようにしていきます。

## 手順
まずアプリのコード一式を保存したディレクトリ直下に以下のファイルを
新たに用意してください。

* Dockerfile
* docker-compose.yml

今回アプリをDocker化するにあたって以上の二つのファイルを使っていきます。
DockerfileのみでもDocker化自体は可能です。
ですがdocker-compose.ymlはDocker-composeを導入するためのファイルで
Docker-composeを使用することによってより簡単にDocker化を実現できます。
それでは各ファイルに必要事項を記載していきます。

### Dockerfile
まずDockerfileを以下のように記載してください。
```
FROM ruby:latest
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
WORKDIR /rails-docker
ADD Gemfile Gemfile.lock /rails-docker/
RUN bundle install
ADD . /rails-docker
```

* ```FROM ruby:latest```→ベースイメージを指定
* ```RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs```→パッケージリストを更新して必要なパッケージをインストール
* ```WORKDIR /rails-docker```→コンテナ内での作業ディレクトリを指定
* ```ADD Gemfile Gemfile.lock /rails-docker/```→指定したホスト内のファイルをコンテナ内にも作成。
* ```RUN bundle install```→Gemfileに記載されたGemをインストール
* ```ADD . /rails-docker```→アプリのディレクトリ直下のファイルやディレクトリをコンテナに作成

### docker-compose.yml
次にdocker-compose.ymlを以下のように記載してください。
```
version: '3'
services:
  db:
    image: postgres:12
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/rails-docker
    ports:
      - "3000:3000"
    depends_on:
      - db
```
* ```version: '3'```→Docker-composeのバージョンを指定。現状は３で問題なし。
* ```services:```→これ以降に処理の内容を記載
* ```db:```DB用のコンテナを用意
* ```image: postgres:12```バージョン12のPostgresSQLのイメージを用意
* ```environment:POSTGRES_PASSWORD: password```→コンテナ内の環境変数を設定
* ```web:```→アプリ用のコンテナを用意
* ```build: .```→ビルドコンテキストを指定
* ```command: bundle exec rails s -p 3000 -b '0.0.0.0'```→コンテナ内で記載したコマンドを実行
* ```volumes:- .:/rails-docker```→指定したホストのディレクトリを指定したコンテナのディレクトリへマウントする。ファイルやディレクトリへの変更は共有される。
* ```ports:- "3000:3000"```→ホストとコンテナのポート番号を設定
* ```depends_on: - db```→コンテナ間の依存関係を設定。今回はDBが起動した後にアプリが起動する。

### database.yml
今回DBにPostgreSQLを使用しているのでdatabase.ymlへの一部変更が必要です。
以下のようになります。
```
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
```

### コマンド入力
ファイルへの記載が完了したら最後にコマンドを実行して
コンテナの作成と起動をしていきます。
以下の順でコマンドを入力してください。

イメージ作成
```
docker-compose build
```

コンテナ作成、起動
```
docker-compose up
```

DB作成
```
docker-compose run web rails db:create
```

DB更新
```
docker-compose run web rails db:create
```

以上のコマンド入力が完了したらhttp://0.0.0.0:3000
へアクセスしてアプリの起動が成功しているかを確認してください。

## 最後に
以上、railsアプリをDocker化する手順についての説明になりました。
web開発の現場では必須の知識になりますのでぜひ使ってみてください。
開発生産性の未来に参加してきました。

Findyさん主催のこちらのイベントに参加してきました。

https://findy.connpass.com/event/298196/

AとBのブースに分かれていて、Aブースはオフライン限定、Bブースはオンラインとのハイブリットという開催でした。

メインの話題は、エンジニアの生産性の定義や、可視化、改善につなげるところという話でした。

僕は9割くらいはAブースの方に参加していました。その中で自分の中での新しい発見をメモしておきます。

## 生産性の定義は文脈によって異なる

Aブースの3セクション目の話でした。

大企業の組織構成の中で、生産性という言葉はどのレイヤーで語られるかで意味が異なってくるというものでした。そして、そのレイヤーの接続部分が生産性のつなぎ目という見方は、組織全体を見れてなかった自分には衝撃でした。

細かく見てみます。

開発者目線での生産性というとInputとして、要件を受け取りOutputとして、書いたコードの量であったり、PRの数であったり、納期に間に合わせた、もしくは早く終わらせたといった目線になります。

しかし、その一つ上のレイヤーである、EMの立場からするとInputとして、それぞれの開発者の上記の工数を受け取りOutputとして、できた機能などが出てきます。

そして、その一つ上のレイヤーになってきて、PMやPdMとなってくるとInputとして、上記のできた機能を受け取りOutputとして、お金といったものを生み出すというようなところになってきます。

このようにInputとOutputの連鎖があるということです。接続部があってそれぞれのレイヤーで練られたものがOutputされ、次のInputに引き渡されていきます。。

これは見え方がまるっと変わってくる、言われてみれば当然ですが、それぞれの立場を理解していないと考えもしないことだと思いました。

僕自身は、一開発者という枠にとどまりたくない人間なので、こういうところも気をつけていかないと今後は困ることもあるだろうなというのを理解しました。

## コンテキストスイッチの可視化が難しい

生産性として一番分かりやすいものはPRの数やコードの量かもしれません。
しかし、それは一概にそれだけで評価はできないものでもあります。

コンテキストスイッチ問題というのは、エンジニアであれば誰もが経験することでしょう。作業中に声をかけられて集中が都度都度切れることで、作業の進捗が著しく遅れてしまうという問題です。

これは、コンテキストスイッチがどの程度損害を出しているのかを可視化したりすることは難しいものというのを表しています。特にこれは、伝言ゲームになりうる問題でもあります。人から伝えられたものを頭に留めておくというだけで、生産性に大きく影響を与えるような無視できないものになります。

可視化ができない以上対策としては、集中時間のような時間を設けてその間は連絡を取らないようにするといった対応が必要になってきます。

## 暗黙的不安

Aブースの5セクション目の話でした。

人は未知なものには不安を感じるものになります。開発者も例外なく、さまざまな不安に苛まれます。

自分が貢献できているのか失敗への不安進捗への不安

僕もエンジニアの端くれなのでこの不安は非常に分かります。基本的に仕事でやることは全てが未知のものになるので、この不安は乗り越えても乗り越えてもやってくるものだと思います。

この不安を以下に取り除けるかがEMの仕事であり、役割なんだという話を聞きました。しかし、EMには開発者の不安が届いてこないというのもEMの不安に繋がるということもあるそうです。

こういった不安は生産性というところに大きな損失をもたらします。

これらの解決方法として、スライドにあった不安の依存関係を作るというのはとても良い方法だと思いました。つまり、どんな不安があるかを洗い出すだけでなく、それらの繋がりを持つ不安と不安を繋ぎ合わせることで、どれが根幹となる不安なのかが洗い出されて、それを解決することで芋づる式に解決できる問題も出てくるというのが、スライドから読み取れました。

その会社では、ユーザーストーリーを細かくするというのを徹底したようです。不要なユーザーストーリであれば、削除すればいいだけなので、ひたすらに細かく細かくを意識して行ったようです。初速は遅くなるかもしれませんが、不安が減ることでみんなが気軽にユーザーストーリを作ることができることで生産性が上がるということでした。そして、ユーザーストーリの作成にも一緒に作成することをしたり、ルールで縛るということをしないようにし、方向性だけ示すということを行うことで不安を取り除くことができたようです。

大事なのは、方向性というところで「理想の未来のディティールを描く」ということ。そしてそれを広めるということでした。シンプルなものでもいいので、こういった改善を改善と信じて行うことがEMであったり、マネージャーには必要なスキルなのだなというのを実感しました。

## オフショアの苦労

Bブースの6セクション目の話でした。

やはり文化の違いというのは様々な問題を抱えるみたいですがその中の生産性の問題について取り上げていました。

例えば、1週間音沙汰がなく、突然巨大なPRが送られてくるなど。国の文化の話なのか、その会社特有のものなのかまでは厳密には分からないですが、ありそうな話ではあるなと思って聞いていました。

当然、1週間音沙汰がないというのは進捗が見えないので不安に感じますし、そんな巨大なPRをレビューするなんてこともできないわけですよね。そういった背景もあって、まずは日本のPRを参考にしてもらうということをしてもらったそうです。

そして海外にとって日本の労働環境というのに不安を感じているようでした。残業がすごいとか、ルールが厳しいといった労働環境に関する不明確な点がそういったPR事情に繋がってきているようでした。

PRの分割というのはこの場合、とても効果的だなというのを感じました。PRを分けることを評価の対象とするのは、このオフショア開発環境においては非常に重要になりそうです。その会社では、ただの受託のような関係にならないように共に進んでいくというのを意識して、伝えて進めていったというのも、生産性に繋がったとのことです。

オフショアではこういった問題を抱えていますが、良いことも多く日本より平均年齢が若いことから、優秀で吸収の速い若い人が多いというのも魅力の一つですし、チームビルディングが日本に比べると優れているというのを聞きました。誰かと不穏な空気になると飲みに行き仲良くなるそうです。

僕もオフショアには興味ある分野なので非常に興味深く聞くことができました。

## まとめ

今回のテーマでもある、生産性というところに焦点を置いて話をされていたので、組織作りやチームビルディングという文脈が多く登場しました。

僕もチームビルディングには興味があるところなので、メンタリティの部分で参考になることも多かったですし、実際に実践してみると良さそうという考えも多かったです。

やっぱり定期的にいろんな会社の組織戦略であったり、大きな会社の考え方に触れるというのは良い刺激になりモチベーションにも繋がりました。

またこのような大きいイベントには参加してみたいと思います。
【1日目】フロントエンジニアがRailsを習得するまで

# 「rails 学習法」でググる

とりあえずググって上から10個くらい記事を読みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629706/eb1d7a79-884f-d7af-f5d9-08609231b6b4.png)

---

# 参考になりそうなサイト
1. railsチュートリアル（結構難しいらしい）
https://railstutorial.jp/chapters/beginning?version=7.0

2. progate（簡単）
https://prog-8.com/courses

3. ドットインストール（簡単）
https://dotinstall.com/

4. Railsガイド
https://railsguides.jp/

5. Ruby on Rails Guide
https://guides.rubyonrails.org/

---

# 進め方
結論自分は以下をいきなり進めようと思いました。

- railsチュートリアル（結構難しいらしい）
https://railstutorial.jp/chapters/beginning?version=7.0

分からなかったらprogateやドットインストールで基礎を学ぼうかなと。

- progate（簡単）
https://prog-8.com/courses

- ドットインストール（簡単）
https://dotinstall.com/

補助的な意味で以下を保険として。

- Railsガイド
https://railsguides.jp/

- Ruby on Rails Guide
https://guides.rubyonrails.org/

---

# railsチュートリアルの進め方

以下の記事に3周目安で進めるといいと書いてあったのでそれにならいます。
https://runteq.jp/blog/programming-school/gakusyu/10266/

上記の記事を参考にしつつ、僕は以下の手順で進めようと思います。

## 1周目

まずは読んで全体像を把握するだけ。
自分は1発目で理解しようとする癖があるので、これは肝に銘じます。
２周目の時に理解に注力できるようにほんわかと何をやっているのか抑えるイメージ。

## 2周目

ここで手を動かし始めます。
やってることを出来るだけ理解しながら、コードを書いてとりあえず動くものを完成させます。

## 3周目

2周目で作成したアプリとは別のものを作成します。
とは言っても少しパラメータを変えたり、新しいファイルを作ってみたりする程度です。
あくまで理解に注力します。

2周目で理解が浅いところを意識して時間を使います。
それ以外のところはおさらいする程度でサクッと進めていきます。

---

# 次の記事

https://qiita.com/kuboryu/items/dfd4c4e40da31b9a8449
PythonでDictにJavaScriptのPropertyっぽくアクセスしたい

## Abstract
PythonでDictの要素にアクセスするためには`t_dict["name"]`みたいな書き方をする
が，地味にダブルクオーテーションをつけるのがめんどくさい

`@dataclass`などを用いて，データ構造を最初から決めて置くのが良い作法だが，いちいちデータ構造を設定しないと使えない
例えばJSON形式で帰ってくるWEBAPIのデータを読み出すために，不必要な情報まで記述したくはない（と書いたものの，ちゃんと調べてないので必要なところだけ書いて，残りを捨ててくれるのかもしれない？）

ということで，雑にDictにアクセスしたい人のためのラッパーを書いてみた


## Code
```python
class DictWrapper(dict):
    def __getitem__(self, __key):
        __t = super().__getitem__(__key)
        if isinstance(__t, dict):
            return DictWrapper(__t)
        if isinstance(__t, list):
            return ListWrapper(__t)
        return __t
    __getattr__ = __getitem__

class ListWrapper(list):
    def __getitem__(self, __i):
        __t = super().__getitem__(__i)
        if isinstance(__t, list):
            return ListWrapper(__t)
        if isinstance(__t, dict):
            return DictWrapper(__t)
        return __t
```

## Example
```python
import json

with open("./dat.json") as f:
    d = DictWrapper(json.load(f))
# YOLP Weather Data
print(d.ResultInfo.Count)
print(d.Feature[0].Geometry.Coordinates)
print(d["Feature"][0].Geometry["Coordinates"])
```
- YOLPのAPIを叩いてて思いついたので，YOLPの気象APIを叩いたときのサンプル
- こんな感じで，要素名にダブルクオーテーションだの，カッコだのをつけなくても動いてくれる
- 3つ目みたいな，キモい書き方も許容される(ようにした)

## Mechanism
- まずクラスにおけるアトリビューションアクセス時に呼ばれる組み込み関数`__getattr__`を魔改造する必要がある
- その前に，そもそもの`dict`クラスの要素取得時に呼ばれる組み込み関数`__getitem__`を魔改造している
- ここでのアクセスのうち，`dict`の場合は`DictWrapper`を，`list`の場合は`ListWrapper`を，それ以外はそのまま返すようにしている
- これで，`dict`内部に`dict`を持つような再起構造であっても，`DictWarpper`を返すようになる
- 同様に`list`についても，dict in list に対応するために，`list`クラスのアクセスを改造し，`DictWarpper`を返すようにしている．
    - popとかの関数で呼び出すときの動作はチェックしてないので，overloadしてないかも
        - というか，そのレベルだったらちゃんとクラスを書いてもろて
 
## Reference
- おおかた実装してから，[ちょっといい感じのdictと、その活用方法](https://qiita.com/utkamioka/items/d2c31dff5dfafbb5f12b) というQiita記事を見つけた
    - これは`json.laod`のフックして，Mapというクラスを全体に当てているみたい
    - そのため，既にあるDictをどーこーするときには使えない
        - とはいえ，この人と同じでJSONをどうにかしたいだけだったので，こっちのほうがすごくきれいな実装だと思う
PowerShellでローカルタイムを計算してみる

PowerShellでGet-Dateコマンドレットを実行すると現在端末に設定されているタイムゾーンの時刻を返してくれますが。

今回はこれを各国のローカルタイムに変換してみます。

## System.TimeZoneInfoのConvertTimeBySystemTimeZoneIdを使う

[TimeZoneInfo.ConvertTimeBySystemTimeZoneId メソッド](https://learn.microsoft.com/ja-jp/dotnet/api/system.timezoneinfo.converttimebysystemtimezoneid?view=net-7.0)

.NETではConvertTimeBySystemTimeZoneIdを利用して下記のように、変換したい日付とタイムゾーンIDを渡すると変換できる。（UTCとEST）

```PowerShell
# UTCに変換
[System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId( (Get-Date), 'Greenwich Standard Time')

# ESTに変換
    [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId( (Get-Date), 'Eastern Standard Time')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/9d978c31-6750-334d-454b-ddcde6d2d06c.png)

## タイムゾーンIDの一覧を確認してみる

変換に利用するタイムゾーンIDの一覧を確認するには下記コマンドから確認できる。

```PowerShell
# GetSystemTimeZonesメソッドを実行
[System.TimeZoneInfo]::GetSystemTimeZones()

# Get-TimeZoneコマンドレットのListAvailableオプションを利用
Get-TimeZone -ListAvailable
```

## 総評

PowerShellの力というか.NETの力といった感じですが。
PowerShellは簡単に.NETを利用できるのはやはり便利だと感じます。
仮想DOMとDOMの違い

## はじめに
この記事では、Web開発における重要な概念であるDOM（Document Object Model）と仮想DOMについて解説します。これらの違いを理解することで、より効率的なWebアプリケーションの開発が可能になります。

## DOMとは
DOMは、HTMLやXML文書のプログラムによるインターフェースです。WebブラウザがHTMLを読み込むと、それを解析してDOMツリーを構築します。このDOMツリーを通じて、JavaScriptなどの言語が文書の内容や構造、スタイルを操作できるようになります。

<img width="400" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582738/543ffa7c-aaf0-a846-e64b-4783082c9db1.png>



#### DOMのコード例
```html
<!DOCTYPE html>
<html>
<head>
    <title>Qiita最高</title>
</head>
<body>
    <h1>Hello World</h1>
    <p>これはサンプルの段落です。</p>
</body>
</html>
```
このHTMLは、DOMツリーに変換されると、html、head、title、body、h1、pといったノードに分解されます。


## 仮想DOMとは
仮装DOMはメモリ上にある軽量のコピーみたいなものです。コンポーネントや要素の状態が変わると、仮装DOMが更新されて、その後で、実際のDOMとの違いを効率よく計算し、必要な部分だけを本物のDOMに反映させることができます。これによって、不必要な再描画が減り、パフォーマンスが向上します。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582738/543bf28c-99d5-0f8c-4548-a6e232b82ff5.png">


<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582738/5fe35883-d1ba-ba01-17b1-7c8d1a8a9bcf.png">

#### 仮想DOMのコード例（React）
```javascript:qiita.rb
import React from 'react';

function App() {
  return (
    <div>
      <h1>こんにちは、世界！</h1>
      <p>これはサンプルの段落です。</p>
    </div>
  );
}

export default App;
```
このコードは、仮想DOMを使用してUIを描画します。Reactは内部で仮想DOMを管理し、必要な部分のみを実際のDOMに反映させます。


## DOMと仮想DOMの違い
1\. パフォーマンス
DOMの操作はコストが高いが、仮想DOMを使うと変更がメモリ上で行われるため、パフォーマンスが向上する。
2\. 効率的な更新
仮想DOMは、実際のDOMへの変更前に差分を計算し、最小限の更新で済むようになる。
3\. フレームワークとの統合
仮想DOMは、Reactのようなフレームワークで広く使われており、開発者にとってシンプルにコードを書ける。




## まとめ
DOMと仮想DOMは、Web開発において非常に重要な概念です。特にReactなどのフレームワークを学びたい方は、これらの違いを理解することで、より効率的でパフォーマンスの高いWebアプリケーションを開発することができると思います。
自己紹介（初投稿）

### 自己紹介
初めまして、"あいら"です。
地方の平凡な大学4年生で、24卒です。情報系の学部を専攻しています。

研究室の活動では、機械学習の分野を学んでおりまして、皮肉テキストの分類の精度向上を目指しています。

卒業後はWebエンジニアとして就職します。内定者インターンでは、CakePHPとAWSを使ったアプリケーション開発を学びながら取り組んでいます。

---

### 就職
　就職先はベンチャー企業でWeb系のマーケティング会社です。現在、自社プロダクトを発展させていく段階にあるようです。

　私自身どちらかというと消極的な人間なので、ベンチャーは合わないかもしれません。ただエンジニアという職業柄を考えるとベンチャー企業にいることでより成長できるのではないかという想いを持ちました。もちろん自分軸だけではなく、会社としても魅力を感じましたので、就職先として選んだ次第です。

---

### 趣味
　エンジニアっぽくないかなって思うんですが、K-POP聴くのが好きです。エンジニアでもけーぽオタいますか？いたら声かけてください〜:blush:

他にもお金にまつわること（ポイ活・株等）を勉強するのも好きです:moneybag:

実家で猫を飼っているので、猫大好きです:heart_eyes_cat:

---

### 今後
　記事を書くことで、自分の学びをアウトプットさせていきたいです。研究のことや、内定者インターンで学んだことについて記載していきます。

社会にしっかりと貢献できるようなエンジニアになりたいです！

これからよろしくお願いします🐥
ローカル環境にLaravelのプロジェクトを作成し、実行するだけ


開発環境はWindows10 Pro(64bit) + PHP 8.1.9 + Composer version 2.4.1。

1 コマンドプロンプト上でカレントディレクトリをCドライブ直下に移動
```bat
cd c:\
```

2 コマンドプロンプト上でLaravelのプロジェクトを実行
```bat
composer create-project laravel/laravel laravel_test_repo
```

:::note info
インストールが実行されるので、しばらく待機する。
Laravelのバージョンは10.2.9。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739418/f7d8d072-f93f-c0fd-a670-322663e63721.png)

:::


3 インストールが終了したら、コマンドプロンプト上で、プロジェクトのディレクトリへ移動する
```bat
cd laravel_test_repo
```

4 コマンドプロンプト上で、組み込みサーバーを実行する
```bat
php artisan serve
```

5 Webブラウザ上にて、localhost:8000へアクセスすると、以下のようなページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739418/2cae0c32-130f-5202-daf5-159c738edba5.png)
【GitHub Actions/Python】自動pytestの際に、testsディレクトリ内のモジュールをimportする

## 状況
このようなコードでGitHub Actionで自動テストを運用しておりました。
```yml:python-tester.ymlname: unit-test

on:
  push:
    branches: ["dev", "main"]
  pull_request:
    branches: ["dev", "main"]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11"]

    steps:
      - uses: actions/checkout@v3
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v3
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install pytest
          python -m pip install -e .
      - name: Test with pytest
        run: |
          pytest
```

ここで、テストコード用に書いたモジュールを`tests`フォルダに入れておいたところ、`no "tests" module found`エラーが。

## 解決
単純に、レポジトリのルートディレクトリにPythonのPATHを通すべきだっただけですね。
```diff_yaml:python-tester.yml
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: unit-test

on:
  push:
    branches: ["dev", "main"]
  pull_request:
    branches: ["dev", "main"]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11"]

    steps:
      - uses: actions/checkout@v3
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v3
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install pytest
          python -m pip install -e .
      - name: Test with pytest
+       env:
+         PYTHONPATH: ${{ github.workspace }}
        run: |
          pytest
```

これで解決。

## 最後に
:::note
いいね頂けると泣きながら喜びます><
:::
【React】画面がリロードされた際に処理を実行する方法

以下のように記述することで画面がリロードされる際に実行される処理を定義できます。

```js
  const handleBeforeUnload = () => {
    console.log('beforeunload')
  }
  
  useEffect(() => {
    window.addEventListener('beforeunload', handleBeforeUnload)

    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload)
    }
  }, [handleBeforeUnload])
```

https://developer.mozilla.org/ja/docs/Web/API/Window/beforeunload_event
PowerShellからfind.exeを実行する時に注意点

[MSLearn - find](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/find)

find.exeはWindowsのコマンドプロンプトで指定したファイル内テキストをパターン検索してくれるコマンドとなります。

このコマンドをコマンドプロンプトから実行する場合は特に気を付ける必要はないのですが。
PowerShellから実行する場合はちょっと注意が必要なので本記事ではその点について説明する。

## find.exeでは<"string">のように文字列をダブルクオートで囲む必要がある

[find.exe Syntax](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/find#syntax)

find.exeのSyntaxを読むと、下記赤枠のように<"string">と記載があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/5df9341b-6ed7-832e-2d39-898898976ed2.png)

これはfind.exeで検索を行う場合に、文字列指定は`"String"`といったように、ダブルクオートで囲む必要を表しています。

それぞれhello(hello.txt),world(world.txt),helloworld(helloworld.txt)のようにテキスト(ファイル名）したテキストとファイル名が同一のファイルを用意して、helloという文字列で検索をかけてみます。

```cmd
rem 文字列 hello を *.txtを対象に検索
find.exe "hello" *.txt
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/00f927ff-0189-ba69-7047-211d9b43edc9.png)

コマンドプロンプトでは上記のように検索できました。

この時点でPowerShellを普段ご利用の方はいやな予感がしてきているかと思いますが。

同様のコマンドをPowerShellから実行すると下記のようにエラーになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/516925ff-807a-1851-0b0c-ee15ccaa5631.png)

この動作が何に起因するかといえば、下記のドキュメントにあるように

[about_Quoting_Rules](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4&viewFallbackFrom=powershell-7.1)

PowerShellではダブルクオートで囲まれた文字列は展開してしまうしようとなっているため。

find.exeでは文字列として"String"といった形で渡す必要があるるが、PoweShellでは展開されてダブルクオートがなくなってしまう事に起因していそうです。

回避するためには下記のようにシングルクオートでさらに囲んで、文字列が展開されないようにエスケープしてあげればOK

```Powershell
find.exe '"hello"' *.txt
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/8cf28de7-9093-fc50-5737-610e8d29524c.png)

## ちなみにfindstr.exeではダブルクオートは必要ない

[MSLearn - findstr.exe](https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/findstr#syntax)

似たコマンドとして`findstr.exe`がありますが、こちらはSyntaxを見てみるとfind.exeと異なり、`<strings>`といった具合でこちらのコマンドはダブルコーテーションでくくる必要がないため、PowerShellから実行する際は、こちらを利用すれば手間は少ない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/654e9114-4155-966f-b917-91bde1033664.png)

## そもそもSelect-Stringでよくない？

PowerShellのコマンドレットには、`Select-String`というコマンドレットが用意されてる。

このコマンドレットはfindstr.exeと同様にファイルを文字列検索する機能を提供している。

## 総評

PowerShellを使っているとダブルクオートの部分で失敗する事があるかと思います。

PowerShellからネイティブコマンドを実行するに注意する一つのポイントかと思います。
VOICEPEAKに論文を読んでもらう

## 背景
作業中に論文や教科書を読んでもらいたいので、以下のような条件をみたす text-to-speech のソフトウェアを探していた。
- 従量課金でない（論文や教科書は文字数が多いので）
- コマンドラインで実行できる
- 声に不自然さがない

[VOICEPEAK <ruby>彩澄<rt>あすみ</rt></ruby>りりせ](https://www.ah-soft.com/voice/ririse/) がこれらの条件をみたしていたので、これを購入した。ただしコマンドラインは正式版として提供されているのではないらしい。参照：[Voicepeak コマンドのオプションと使用例](https://kumama1453.blog.fc2.com/blog-entry-1360.html)

出力した音声はスマホなどで聞くことを想定している。スマホは、音声ファイルよりも動画ファイルのほうが扱いやすいことも多い（例：YouTube の非公開動画として上げておく）ので、今回は動画の作成まで行う。

![voice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55753/979fa6c7-da3e-01ff-10e3-0eeb322d401c.png)


## 検証環境
- WSL2 上の Ubuntu 20.04
- [Homebrew](https://brew.sh/) をインストール済み

## 準備
必要に応じて `poppler` や `ffmpeg` をインストールする。
```
brew install poppler  # PDFからテキストファイルを作成する場合に必要
brew install ffmpeg   # 動画にする場合に必要
```
また動画化する場合に立ち絵を使用したい場合は [公式ページ](https://www.ah-soft.com/voice/ririse/) からダウンロードしておく。WSL 側で使いやすいようにコピーしておく。

```bash
cp /mnt/c/Users/USER/Downloads/ririse_seihuku_illust/ririse_seihuku_illust/彩澄りりせ_制服イラスト/001.png .
chmod -x 001.png
```

## 手順
1. PDFからテキストを抜き出す。
    ```bash
    pdftotext -f FIRST_PAGE -l LAST_PAGE PDF_FILENAME TEXT_FILENAME
    ```
1. 必要に応じてテキストを編集する。特に日本語以外が苦手なので、日本語読みしたカタカナ表現などに置き換える。もしこの処理を本格的にやりたければ [cmudict](https://github.com/cmusphinx/cmudict) を使うといい。

1. VOICEPEAK を使って、テキストファイルから WAV ファイルを作成する。
    ```bash
    /mnt/c/Program\ Files/VOICEPEAK/voicepeak.exe -t TEXT_FILENAME -o WAV_FILENAME
    ```
1. WAV ファイルが複数の場合は1つにまとめる。
    ```bash
    sox INPUT_WAV_FILENAME1 INPUT_WAV_FILENAME2 OUTPUT_WAV_FILENAME
    ```
1. WAV ファイルを元に動画 (MP4) を作成する。参照：[FFmpegで音声ファイルと画像1枚から動画を作成してみた](https://dev.classmethod.jp/articles/ffmpeg-create-movie-by-audio/)
    ```bash
    ffmpeg \
        -loop 1 \
        -r 30000/1001 \
        -i 001.png -i WAV_FILENAME \
        -vcodec libx264 \
        -acodec aac \
        -strict experimental \
        -ab 320k -\
        ac 2 -\
        ar 48000 \
        -pix_fmt yuv420p \
        -shortest \
        VIDEO_NAME
    ```

## 出力例
https://x.com/pecorarista/status/1729513075445829883

## 感想
日本語に限れば十分なクオリティだと思う。一方で、繰り返しになるが、日本語以外は難があり、前処理で頑張る必要がある。日本語以外が出てこない論文や教科書というのは少数派だと思うので、小説などのほうが向いているかもしれない。
OpenSiv3D開発でdoctestを利用する

# はじめに
C++向けのユニットテストフレームワークdoctestをSiv3Dの開発で使い始める方法をメモしておきます。

# doctestのインストール
https://github.com/doctest/doctest

# 作成手順
#### doctest.hをdoctest\doctest\doctest.hからコピーして、プロジェクトの直下に配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289319/a189e80a-d6e1-041c-bc3a-4bd51e9c8340.png)

#### テストコードを書きます
```cpp:AddTest.h
#define DOCTEST_CONFIG_IMPLEMENT
#include "../doctest.h"

namespace
{
	int add(int a, int b) { return a + b; }
}

TEST_CASE("testing the add function") { CHECK(add(1, 2) == 3); }

TEST_CASE("will fail") { CHECK(add(1, 2) == 0); }
```

#### Main.cppの void Main()の中でテストを実行するようにします

```cpp:Main.cpp
#if USE_TEST
#include "Tests/AddTest.h"

class TestRunner
{
public:
	static int run()
	{
		// コンソールを出す必要がある
		Console.open();

		doctest::Context context;

		// overrides
		context.setOption("no-breaks", true); // don't break in the debugger when assertions fail

		int res = context.run(); // run

		if (context.shouldExit()) // important - query flags (and --exit) rely on the user doing this
			return res; // propagate the result of the tests

		// テスト実行
		bool testSuccess = res == 0;
		if (!testSuccess)
		{
			// テスト失敗時

			// 失敗に気づきやすいようにキー入力を待つようにする
            // 失敗時にはゲーム画面は表示されず、コンソール画面のみ表示される
			static_cast<void>(std::getchar());
		}

		return testSuccess;
	}
};
#endif

void Main()
{
#if USE_TEST
	TestRunner::run();
#endif

	// 以下略

```

#### 結果
テストが通っていないので、以下のようなコンソール画面のみが表示されます。
```powershell
[doctest] doctest version is "2.4.11"
[doctest] run with "--help" for options
===============================================================================
(パス)\AddTest.h(13):
TEST CASE:  will fail

(パス)\AddTest.h(13): ERROR: CHECK( add(1, 2) == 0 ) is NOT correct!
  values: CHECK( 3 == 0 )

===============================================================================
[doctest] test cases: 2 | 1 passed | 1 failed | 0 skipped
[doctest] assertions: 2 | 1 passed | 1 failed |
[doctest] Status: FAILURE!
```
テストが通っている際には、テスト結果のコンソール画面とゲーム画面の両方が表示されます。
この例では、`USE_TEST`のプリプロセッサによる条件分岐でテストを実行するかどうかを制御しています。

#### 備考
・doctestはシングルヘッダーなので、使用するのが簡単です
・AddTest.hなどのテストコードを書いたファイルはヘッダーファイルである必要があります。cppファイルにすると、コンパイルの対象から外さない限り、インクルードした際に実装が2つあることになってしまいます。こちらの記事が大変参考になりました。

https://qiita.com/sksmnagisa/items/16d6782ff4e61fe9218c

・追加でテストコードを書く際にはMain.cppにインクルードするヘッダファイルを増やしていきます。


# 参考記事
https://qiita.com/tyanmahou/items/be3f3494b2c690270ccb

TestRunnerの書き方などを参考にさせていただきました。

https://zenn.dev/nonanonno/scraps/3115057f950822

テストコードなどを参考にさせていただきました。
Laravel9でweb.phpでのルートにつけるnameメソッドの正体がようやくわかった件

## Laravel9でweb.phpでのルートにつけるnameメソッドの正体がようやくわかった件

お恥ずかしながら､あまり考えることなく､
そういうものなんだ程度に理解していた`nameメソッド`の正体がようやくわかったので備忘録として残します｡


Laravelでの`route/web.php`を書く際一般的には以下のように書ける｡

```php
Route::get('/pubmed-scraping/scrape', [PubmedScrapingController::class, 'scrape']);
```

呼び出すときも普通にリンクを指定してあげれば良くて､
```php
<a href="/pubmed-scraping/scrape" class="btn btn-outline-secondary">要約ページへ行く!!</a>
```

こんな感じになると思う｡
だけど､公式などを見てみると､ルートの書き方で末尾に`->name('hoge')`と書いていることが多い｡

よくわからんけど､そう書くのね｡
位の認識だったのだが､ようやくわかりました｡

ここでnameメソッドで指定してあげることで､
aタグなどを使う際にわざわざリンクを指定する必要なく､nameメソッドに書いたものを書けばよくなるみたいです｡

こんな感じ｡
```php
Route::get('/pubmed-scraping/scrape', [PubmedScrapingController::class, 'scrape'])->name('scrape');
```

```php
<a href="{{ route('scrape') }}" class="btn btn-outline-secondary">要約ページへ行く!!</a>
```

可読性もあがるし､URLが変わってもRouteだけ直せばよいので
運用も楽になりますね｡
勉強になりました｡｡!!
M5AtomS3 で M5Stack-Avatar を使う

## 本記事について

M5AtomS3 で M5Stack-Avatar のサンプルを動かすと顔が画面に収まりません。(目だけが見える)

顔が画面内に表示されるようにします。

## 環境

- M5AtomS3
- M5Stack-Avatar 0.9.1

## 結論

![スクリーンショット 2023-11-29 21.09.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/bcb535b6-db20-2998-92e7-a792e87758b2.png)

```main.cpp
#include <M5Unified.h>
#include <Avatar.h>

m5avatar::Avatar avatar;

void setupAvatar()
{
  avatar.setScale(0.4);
  avatar.setPosition(-56, -96);
  avatar.init();
}

void setup()
{
  auto cfg = M5.config();
  M5.begin(cfg);
  USBSerial.begin(115200);
  setupAvatar();
}

void loop()
{
}

```

これで行けました。

### Scale

M5Stack-Avatar のサイズは 320x240 を想定して作られています。

[Face.cpp](https://github.com/meganetaaan/m5stack-avatar/blob/0.9.1/src/Face.cpp#L37)

M5AtomS3 の解像度は 128x128 なので、横幅を合わせるために 0.4 倍にしています。

### Position

```
void Avatar::setPosition(int top, int left)
```

[setPosition](https://github.com/meganetaaan/m5stack-avatar/blob/0.9.1/src/Avatar.cpp#L185)

Scale は 320x240 で考えた時の中心を起点として働きます。
そのため、M5AtomS3 の中心から顔の中心までの距離を考えて、その分だけ左上にずらしています。

```
(320 - 128) / 2 = 96
(240 - 128) / 2 = 56
```

## 失敗した方法

320x240 ではなく 128x128 で設定したらどうなるかやってみました。

```
// 横幅128におけるFaceを作成する。もともとは320x240の設定のため、横幅にあわせて0.4倍に縮小する。
// 小数点は四捨五入

m5avatar::Face *face = new m5avatar::Face(
  new m5avatar::Mouth(20, 36, 2, 24),
  new m5avatar::BoundingRect(59, 65),
  new m5avatar::Eye(3, false),
  new m5avatar::BoundingRect(37, 36),
  new m5avatar::Eye(3, true),
  new m5avatar::BoundingRect(38, 92),
  new m5avatar::Eyeblow(13, 0, false),
  new m5avatar::BoundingRect(27, 38),
  new m5avatar::Eyeblow(13, 0, true),
  new m5avatar::BoundingRect(29, 92)
);

face->getBoundingRect()->setSize(128, 128);
avatar.setFace(face);
```

表示はできたのですが、アニメーションが320x240想定の動きなため単純なScaleになりませんでした。

https://github.com/meganetaaan/m5stack-avatar/blob/0.9.1/src/Face.cpp#L88

具体的には breath アニメーションの影響値 (マジックナンバーの3) の影響です。

このあたりを頑張る意味はあまりないと思うので setScale , setPosition で対応するのが良さそうです。

## サイズの調整

少し小さいので、目と口は大きくしてもいいかなと個人的には思いました。

```.cpp
void setupAvatar()
{
  avatar.setScale(0.4);
  avatar.setPosition(-56, -96);

  // 目と口のサイズを1.5倍にする
  Face* face = avatar.getFace();
  face->setLeftEye(new Eye(12, false));
  face->setRightEye(new Eye(12, true));
  face->setMouth(new Mouth(50, 90, 6, 60));
  avatar.init();
}
```
Go：分かりにくいContextを簡単なコードで解説

# Contextの基本
- **Contextの目的**：context.Contextは、ゴルーチンの実行に関連する設定やシグナルを伝達するためのものです。これには、処理のキャンセル、タイムアウトの設定などが含まれます。
- **キャンセルシグナルを送る**：あるゴルーチンが他のゴルーチンに「もう止めて」と伝えることができます。これは、不要な作業を止めてリソースを節約するのに役立ちます。
- **タイムアウトを設定する**：特定の時間が過ぎたら、自動的にいくつかの作業を止めるように設定できます。

# Contextの使い方
```go
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// Contextを作って、3秒後に自動で止めるように設定
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	// ここで5秒待ってからメッセージを出そうとする
	select {
	case <-time.After(5 * time.Second):
		fmt.Println("5秒経過: メッセージ表示")
	case <-ctx.Done(): // でも、Contextが3秒で止めるから、こっちが先に来る
		fmt.Println("キャンセルされました")
	}
}
```

## 実行結果
```bash
$ go run main.go
処理キャンセル
```

## 簡単にコードの説明
- 最初に、**context.WithTimeout**(**context.Background(), 3*time.Second**)で「3秒後に自動で止めてね」という設定をしたContextを作ります。
- **defer cancel**()は、「この関数が終わる時に、設定したContextをキャンセルしてね」という意味です。
- **select**は、「2つのことを待つけど、どっちか先に起きたらそれをやる」という意味です。
  - case <-time.After(5 * time.Second)は「5秒待つ」ということ。
  - case <-ctx.Done()は「Contextがキャンセルされた（3秒経った）らこっち」ということ。
- この例では、5秒待つ前に3秒でContextがキャンセルされるので、「キャンセルされました」と表示されます。これは、Contextを使ってプログラムの一部を自動で止める例です。

## ctx, cancel := context.WithTimeout(~~)について
- **ctx, cancel := context.WithTimeout**(**~~**)のこの行は、タイムアウト付きのContextを作成し、それを制御するためのキャンセル関数を返します。
- **ctx（**Context**）**：ctxは、作成されたContextのインスタンスです。このContextは、設定されたタイムアウト（この例では3秒）が経過すると自動的にキャンセルされるように設定されています。タイムアウトが経過すると、ctx.Done()チャネルにシグナルが送られます。
- **cancel**（**キャンセル関数**）：cancelは、Contextを手動でキャンセルするための関数です。defer cancel()とすることで、main関数が終了する際に自動的にこのキャンセル関数が呼ばれるように設定されています。

## defer cancel()について
- **deferキーワード**：deferキーワードは、関数が終了する際に特定の関数を実行するために使われます。つまり、現在の関数の残りの部分がすべて実行された後で、deferで指定された関数が実行されます。
- **cancel()関数**：context.WithTimeout関数は、Contextをキャンセルするためのcancel関数も返します。このcancel関数を呼び出すと、Contextがキャンセルされ、関連するゴルーチンなどの処理が中断されることを意味します。
- **使い方と意味**：defer cancel()は、現在の関数（この場合はmain関数）が終了するときにcancel関数を自動的に呼び出します。これにより、Contextが確実にキャンセルされ、プログラムがきれいに終了します。

## case <-について
- **selectステートメント**：selectステートメントは、複数のチャネル操作を待ち受けて、準備ができた操作から順に実行するために使われます。
- **case <-**：この構文は、チャネルからのデータの受信を待ち受けるために使われます。case <-ctx.Done():は、「ctx.Done()チャネルからデータ（シグナル）を受信したら、このケースのコードを実行する」という意味です。

## time.Secondについて
- **時間の単位**：time.Secondは、時間の単位を表します。具体的には、1秒を意味します。
- **使い方**：time.Secondは、時間を指定する際の単位として使われます。例えば、3*time.Secondは3秒を意味します。

# まとめ
プログラムの一部が長く時間がかかる場合や、特定の時間が過ぎたらやめたい場合に、contextを使うと便利です。
これにより、無駄な処理を省いたり、プログラムが固まるのを防いだりすることができます。
Ruby芸160チャレンジ(#2)：ディレクトリ内のファイル全てに処理を適用

## この記事は何
[shellgei160を通じて言語習得 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/learning-via-shellgei160)に参加しています。

書籍「シェル芸ワンライナー160本ノック」の例題をRubyで解いてみて、Rubyの学習に役立てようとするものです。

例題はこちらのリポジトリで公開されているものに限ります。
https://github.com/shellgei/shellgei160

## 実行環境など
- Docker image: ruby:3.0.2
- 上記リポジトリをクローンした上で、リポジトリのルートディレクトリ直下に`answer-ruby`ディレクトリを作り、その中に解答となるファイルを作成していきます。

## 今回のテーマ
https://github.com/shellgei/shellgei160/blob/master/answer/2.md

```bash
$ time ls *.png | sed 's/\.png$//' | xargs -P2 -I@ convert @.png @.jpg
```

ちなみに、これは次のような処理を行っています(by ChatGPT)

> このコマンドは、画像変換のための一連の処理を行います。具体的には、以下のステップがあります：
> 1. `ls *.png`: カレントディレクトリ内のすべての `.png` ファイルをリストします。
> 2. `sed 's/\.png$//'`: 各 `.png` ファイルの拡張子 `.png` を取り除きます。これにより、ファイル名の拡張子が `.png` から `.jpg` に変更されます。
> 3. `xargs -P2 -I@ convert @.png @.jpg`: `xargs` を使用して、`convert` コマンドによる画像変換を実行します。`-P2` オプションは、同時に最大2つのプロセスを起動することを指定しています。`-I@` オプションは、`xargs` が置換文字 `@` を使用して引数を渡すことを指定しています。各 `.png` ファイルに対して、`convert` コマンドで画像を変換し、`.jpg` ファイルに保存します。
> 簡潔に言えば、このコマンドはカレントディレクトリ内の全ての `.png` ファイルを `.jpg` ファイルに変換します。同時に最大2つのプロセスを使用して変換を行います。

上記相当の処理をRubyで考えてみます。なお、画像変換の部分は行わず、ファイル名を変更するだけにします。

## 解

```ruby
# ここが一番意外なんだけど、このパスはファイル保存場所からの相対パスではなく、
# 実行場所からの相対パスになる
target_dir = './qdata/2/img'

def png2jpeg(filepath)
  # ここに変換処理を書く
  # mini_magickを使うと簡単にできるらしいが、今回はパス！
  # とりあえず、ファイル名を変更するだけにする

  # basenameの第2引数に拡張子を指定すると、拡張子を除いたファイル名を取得できる
  # 指定しない時は拡張子を含むファイル名が返る
  puts "#{File.basename(filepath, '.*')}.jpg"
end

files = Dir.glob("#{target_dir}/*.png")
files.map { |file| png2jpeg(file) }
```

## 所感
- File.basename、Dir.globの使い方、なるほど。
DMARCについて調べたよ

# DMARCについて調べたよ

## はじめに
- GmailでSPF、DKIMが登録されていないアドレスからの受信は5000件、併せてDMARCの設定がされていない場合は10000件までの制限がかかる
  - 設定は誰かの手順を参考にしたらきっとできるだろう。でも、どの設定がどんな役割をしているのか。把握するため調べたことをまとめる
  - spf,dkimについて調べた記事は以下のURLから
      - SPF
          - https://qiita.com/sugichan55/items/c145939cb6fa51f0b40f
      - DKIM
          - https://qiita.com/sugichan55/private/ec9c6677a0dc0f2c9221

## DMARCとは
- DMARCはSPF,DKIMの認証を補強する仕組みとして作られた。
- SPF,DKIMでは、認証で失敗したとしてもメールをどのように扱うか（迷惑メールに入れたり、削除したり…）は受信者によって委ねられる
  - つまり、認証したとしても受信ボックスに入ってしまい受信者が開いてしまう場合があり得るということ
- DMARCでは受信者に対して認証に失敗した場合にどのような処理をしたらよいかＤＮＳ上で公開してお願いをすることができる。
  - 受信者は認証に失敗した時にDNSを参照して処理の決定を行うことができる。
- SPFやDKIMでは出来なかった認証に失敗した場合に送信者側が知る術がなかったが、DMARCでは受信者が送信者に対して通知するレポートを送ることができる。
  - メリット：メールアドレスが正しく運用されているか、迷惑メールへの対策を進めることができる

## DMARCってどんな仕組み？
※イメージ図
![dmarc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266246/f44fe68a-ba51-f6c7-a4c1-a7d9f680cabb.png)
- SPFやDKIMの判定がFAILだった場合にポリシーチェックを行う
- 受信者側のメーラーがポリシーをチェックを行い、対応をうかがう
- ポリシーを元にメールを振り分ける（ことができる）
    - ポリシーを参考にするかどうかはメーラーの設定による

## 設定値を確認してみよう

|タグ|タグの意味|説明|
|--|--|--|
|v|バージョン|現在は「DMARC1」のみ使用できる|
|p|ポリシー|「none」アクションなし、「quarantine」隔離、「reject」拒否　の3種類。<br>どれかを指定すること|
|sp|サブドメインポリシー|サブドメインに適用するポリシーを指定<br>メインドメインとは異なるレベルのセキュリティ要件を持つ場合にspタグを使ってポリシーを定義することができる|
|rua|集約レポートURI|DMARCの集約レポートを受信するアドレスを指定できる|
|ruf|失敗レポートURI|個々のメールがDMARCレポートを受信するアドレスを指定できる|
|pct|適用率|DMARCポリシーを適用するメールの割合を指定できる<br>値は0～100で指定する。<br>デフォルトは100|

## まとめ
- DMARCって、SPFやDKIMと組み合わせた時に本領発揮するんだよね。これらと組んで、メールセキュリティをグッと高めてくれるんだ
    - SPFやDKIMが送り手の確認をしてくれるのに対して、DMARCはその結果を使って「これどうする？」って具体的な指示をくれる感じ
- DMARCのおかげで、セキュリティ対策が受け手だけじゃなくて、こっちからもできるようになったんだ。これって、双方にとって良いことだよね
    - こちらから先手を打つことで、勘違いや詐欺を防げるから、お互いに安心できるね
- DMARCの設定って、SPFやDKIMに比べるとちょっと複雑。サブドメインのこととか、どれくらい厳しくするかとか、考えることがいっぱいあるね
    - 細かい設定は面倒かもしれないけど、ちゃんとやればメールのセキュリティがぐんと上がる。たまにチェックして、調整するのが大事だね！
カットオールで見る深さ優先探索【Minecraft】

# 深さ優先探索とは
1. 探索を開始するノードから目的のノードを子に持たないノード(行き止まりのノード)に行きつくまで伸びる
1. 行き止まりのノードから最も近い未探索の子を持つノードから探索を再開する

という流れによる探索を行うアルゴリズムのことを指します。

https://ja.wikipedia.org/wiki/%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2

けんちょんさんによる分かりやすい解説↓

https://qiita.com/drken/items/4a7869c5e304883f539b

https://qiita.com/drken/items/a803d4fc4a727e02f7ba

# カットオールとは
俗にいう「木こり mod」です。木を一括で破壊してくれるやつですね。
上記の解説に「木」という用語が出てくること、ビジュアル化しやすいことから DFS 解説の題材に選びました。

# 動作風景
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/ZEqvMBo0iJs?si=fuZezCx6aJ3ipavQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


# コード
```java
public class Cut implements Listener {
    @EventHandler
    public void onBlockBreak(BlockBreakEvent event) {
        Player cutter = event.getPlayer();
        Block target = event.getBlock();

        if (!isLog(target)) return;

        Set<Block> blocks = new HashSet<>();
        Set<Block> visited = new HashSet<>();
        List<Block> stack = new ArrayList<>();
        blocks.add(target);
        search(target, blocks, visited, stack);

        blocks.forEach(block -> {
            block.getDrops().forEach(item -> cutter.getWorld().dropItem(cutter.getLocation(), item));
            block.setType(Material.AIR);
        });
    }

    private boolean isLog(Block block) {
        return block.getType().name().matches("^([A-Z_]+)_(LOG|STEM)$");
    }

    private boolean isLeaf(Block block) {
        return block.getType().name().matches("^([A-Z_]+)_(LEAVES|WART_BLOCK)$");
    }

    private void search(Block start, Set<Block> blocks, Set<Block> visited, List<Block> stack) {
        int x = start.getX();
        int y = start.getY();
        int z = start.getZ();
        World world = start.getWorld();

        stack.remove(start);
        if (isLog(start) || isLeaf(start)) {
            blocks.add(start);
            visited.add(start);
        }
        for (int dx = -1; dx <= 1; dx++) {
            for (int dy = -1; dy <= 1; dy++) {
                for (int dz = -1; dz <= 1; dz++) {
                    Block block = world.getBlockAt(x+dx, y+dy, z+dz);
                    if ((isLog(block) || isLeaf(block)) && !stack.contains(block) && !visited.contains(block)) stack.add(0, block);
                    if (!stack.isEmpty()) search(stack.get(0), blocks, visited, stack);
                }
            }

        }
    }
}
```
pythonでMeCabを使う

# MeCabのインストール
## pythonでのインストール
```:install
pip install mecab-python3
```
## PC本体のインストール
http://taku910.github.io/mecab/#download

ここからダウンロードしてインストールするとwindowsの人は初期設定だと
`C:\Program Files (x86)\MeCab\bin`に`mecab.exe`があるとおもいます

# 使ってみる
## 情報を出力
```python:nomal.py
import MeCab

tagger = MeCab.Tagger("")
input = "記事を書く"
output = tagger.parse(input)

print(output)
```
```:出力
記事    名詞,一般,*,*,*,*,記事,キジ,キジ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
書く    動詞,自立,*,*,五段・カ行イ音便,基本形,書く,カク,カク
EOS
```
## わかち書き
>わかち書き（わかちがき）とは、文章において語の区切りに空白を挟んで記述することである。分かち書き、分ち書き、別ち書き、分書とも表記する。分別書き、放ち書きとも。
>wikipediaより
```python:wakati.py
import MeCab

tagger = MeCab.Tagger("-O wakati")
input = "記事を書く"
output = tagger.parse(input)

print(output)
```
```:出力
記事 を 書く
```
## 品詞分類
|品詞|品詞(細分類1)|品詞(細分類2)|品詞(細分類3)|活用形|基本形|読み|発音|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
```python:node.py
import MeCab

tagger = MeCab.Tagger("")
input = "記事を書く"
node = tagger.parseToNode(input)

while node:
    elem = node.feature.split(",")
    print(elem)
    node = node.next
```
```:出力
['BOS/EOS', '*', '*', '*', '*', '*', '*', '*', '*']
['名詞', '一般', '*', '*', '*', '*', '記事', 'キジ', 'キジ']
['助詞', '格助詞', '一般', '*', '*', '*', 'を', 'ヲ', 'ヲ']
['動詞', '自立', '*', '*', '五段・カ行イ音便', '基本形', '書く', 'カク', 'カク']
['BOS/EOS', '*', '*', '*', '*', '*', '*', '*', '*']
```
## 応用
### 特定の品詞をスキップ
```python:skip.py
import MeCab

tagger = MeCab.Tagger("")
input = "私は、記事を書くよ！"
node = tagger.parseToNode(input)

cansel_words = ["助詞","記号"]

while node:
    if node.surface != "":
        elem = node.feature.split(",")
        if elem[0] not in cansel_words:
            print(node.surface)
    node = node.next
```
```:出力
私
記事
書く
```
### 特定の品詞だけ
```python:tokutei.py
import MeCab

tagger = MeCab.Tagger("")
input = "私は、記事を書くよ！"
node = tagger.parseToNode(input)

cansel_words = ["動詞"]

while node:
    if node.surface != "":
        elem = node.feature.split(",")
        if elem[0] in cansel_words:
            print(node.surface)
    node = node.next
```
```:出力
書く
```
Ruby芸160チャレンジ(#1)：複数行のテキストからサブセットを含む行の抽出

## この記事は何
[shellgei160を通じて言語習得 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/learning-via-shellgei160)に参加しています。

書籍「シェル芸ワンライナー160本ノック」の例題をRubyで解いてみて、Rubyの学習に役立てようとするものです。

例題はこちらのリポジトリで公開されているものに限ります。
https://github.com/shellgei/shellgei160

## 実行環境など
- Docker image: ruby:3.0.2
- 上記リポジトリをクローンした上で、リポジトリのルートディレクトリ直下に`answer-ruby`ディレクトリを作り、その中に解答となるファイルを作成していきます。

## 今回のテーマ
https://github.com/shellgei/shellgei160/blob/master/answer/1.md

```bash
$ grep '\.exe$' files.txt
```

上記相当の処理をRubyで考えてみます。

## 解

```ruby
files = File.open('qdata/1/files.txt', 'r') do |f|
  f.readlines.map(&:chomp)
end

re = /\.exe$/

# せっかくなので色々なパターンを試してみる

puts '## use select'
puts(files.select { |file| file.match(re) })

puts '## use grep'
puts files.grep(re)

puts '## use keep_if'
puts(files.keep_if { |file| file.match(re) })

puts '## use delete_if'
puts(files.delete_if { |file| !file.match(re) })

puts '## use find_all'
puts(files.find_all { |file| file.match(re) })

puts '## use map'
puts(files.map { |file| file.match(re) ? file : nil })

puts '## use inject'
puts files.inject([]) { |result, file| file.match(re) ? result << file : result }
```

## 所感
- テキストファイルの各行を読み込む処理に手間取った
- サブセットとのマッチには正規表現を使う。ふむふむ。
- `files.grep`、そういうのもあるのか…！
- いろいろな形式で書けすぎるのも大変だった
AWS CodeBuild でコンピューティングに AWS Lambda を利用

# 内容

従来は EC2 上でコンテナイメージが実行されていたが、CodeBuild でコンピューティングに Lambda を使用してビルドやテストを実行できるようになった。

Lambda はほぼ瞬時に起動するため、より高速なビルドが可能な点が利点かと思います。

Lambda でコンピューティングを実行すると、1 秒あたりの使用量に対して課金されるため、コストを最適化できる点もあげられていますが、個人的にはよっぽどな環境ではない限り、差ほどのコスト減はないのかと思っています。

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-codebuild-lambda-compute/


# サポートされているツール、ランタイム

AWS CLI v2、AWS SAM CLI、git、go、Java、Node.js、Python、pip、Ruby、.NET


# 制限事項

- バッチビルド
- ビルド間のキャッシュ
- タイムアウトによるランタイムの分単位の制限
- ルートユーザーの許可を必要とするツール

その他にもいくつか制限事項があるので、ドキュメントを確認し、制限事項にひっかる場合は、EC2 タイプを利用すること。

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/lambda.html


# 検証

Lambda と EC2 で比較検証してみた。

共通の設定として、

codecommit に　適当な python ファイルを準備し、
codebuild で source に codecommit を指定。

codebuild には簡易的に次のものを指定。

```buildspec.yaml
version: 0.2

phases:
  build:
    commands:
      - echo "Running tests"
      - python -m unittest discover .
```

## Lambda

環境
![スクリーンショット 2023-11-28 19.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/0b9a0fd9-f63c-8de7-248c-b906027539e5.png)

結果
![スクリーンショット 2023-11-28 20.05.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/300c7cb9-3389-5970-66b3-584bff8459f6.png)

## EC2

環境
![スクリーンショット 2023-11-28 20.11.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/a26f2ec3-65fa-6c60-6622-1a425c5f4c03.png)
![スクリーンショット 2023-11-28 20.11.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/442c2179-f5b2-1e56-958d-5c3361b584af.png)

結果
![スクリーンショット 2023-11-28 22.27.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/73c085e8-1af9-738d-27e1-4182abfdaaa3.png)
【MySQL x RUST 】MySQL の DateTime 型を Rust の NaiveDateTime 型として抜き出す

Hi, there！ Rust が大好き SAmmys だよ！

今日はタイトルの通り、MySQLにDateTime型で格納されている情報をRustでNaiveDateTime形で取り出す内容だよ。
・・・え、それだけ？の内容だけど備忘録として残しておくよ。

前提として、データを取り出す元のテーブルは以下のように作成したんだ。

```SQL: SQL
CREATE TABLE files (
    created_at DATETIME,
    prefix VARCHAR(255),
    name VARCHAR(255),
    INDEX idx_created_at (created_at)
);
```

そして、RUSTのプログラム本体は以下。（sqlxクレートを使ってるよ）

```rust: main.rs
…（前略）…

let query_result = sqlx::query_as!(
    FileInfo,
    "SELECT * FROM files WHERE name = ?",
    file_name
).fetch_one(&data.db).await;
```

そして、ここで使用しているFileInfoのStructは以下の通り。

```rust: models.rs
use chrono::{prelude::*, naive::NaiveDate};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Default)]
pub struct FileInfo {
    pub created_at: NaiveDateTime,
    pub prefix: String,
    pub name: String,
}
```

そしたらね、エラーとしてこういった内容うが出てきたのさ。

```rust: ビルドエラー時のログ
error[E0277]: the trait bound `NaiveDate: std::convert::From<std::option::Option<NaiveDateTime>>` is not satisfied
   --> src/handler.rs:390:40
    |
390 |                       let query_result = sqlx::query_as!(
    |  ________________________________________^
391 | |                         FileInfo,
392 | |                         "SELECT * FROM files WHERE name = ?",
393 | |                         filename
394 | |                     )
    | |_____________________^ the trait `std::convert::From<std::option::Option<NaiveDateTime>>` is not implemented for `NaiveDate`
    |
    = help: the trait `std::convert::From<NaiveDateTime>` is implemented for `NaiveDate`
    = note: required for `std::option::Option<NaiveDateTime>` to implement `Into<NaiveDate>`
    = note: this error originates in the macro `$crate::sqlx_macros::expand_query` which comes from the expansion of the macro `sqlx::query_as` (in Nightly builds, run with -Z macro-backtrace for more info)
```

Rustからしたら、
「DB に null が入ってるかもしれないんだから、Option<> 付けて null の対応に備えておけよ！」
ってことなんだね。

結局、Struct の宣言を以下に修正すれば終了だったよ、って話。

```rust: models.rs
use chrono::{prelude::*, naive::NaiveDate};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Default)]
pub struct FileInfo {
    pub created_at: Option<NaiveDateTime>,
    pub prefix: Option<String>,
    pub name: Option<String>,
}
```

今回のとは話が逸れるけど、REST API でクライアントからマルチパートアップロードで受け取ったバイナリデータを 6MB ごとにプールさせて AWS の S3 にマルチパートアップロードすることもできたよ。
つまり、メモリが 1 GB のマシンに REST APIサーバーを格納していても、例えば 20 GB のファイルを REST API を経由して S3にマルチパートアップロードアップロードできる、ってことね💡
こういう低レイヤーの制御ができるRust、本当に大好き💛
それでいてメモリも安全💛
それでいて処理も高速💛

あー大好きRust！！！
docker-composeで立ち上げたmysqlに接続できなかった

docker-composeでmysqlのコンテナを立ち上げて、`mysql -h 127.0.0.1 -P 3306 -u root -p`で接続しようとしたらエラーで入れなかった。

2つ違うので引っかかった……。

# 一つ目のエラー
まず突き当たったのがこちら
```
ERROR 2002 (HY000): Can’t connect to local mysql server through socket ‘/var/run/mysqld/mysqld.sock’
```


### 解消方法
流用したmy.cnfにこんな感じでソケットの場所の指定が書いてあった。
```my.cnf
socket=/var/lib/mysql/mysql.sock
```
この指定を消すことで、接続できるようになった。
こいつのせいでdockerのコンテナ内にソケットファイルが作られず、永遠にログインできなかった。

いろいろ調べた挙句、アホな理由だった。


# 二つ目のエラー
一つ目を解消して出てきたのが、こんなエラー。
```
RROR 1045 (28000): Access denied for user ‘root’@‘localhost’ (using password: YES)
```

### 解消方法
こんな感じでrootのパスワードを設定していたのだが、このパスワードがよくなかった。
```docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.2
    environment:
      MYSQL_ROOT_PASSWORD: 'piyoPa6i4m$s'
    volumes:
(略)
```
どうも`$`が入っていると変数として認識されるらしく、`$`なしのパスワードに変えたところ接続できるようになった。

ちなみにdocker-composeを立ち上げたときに不穏なWarningが出ていて、これがヒントだった。
（でも、その時はWARNだし……と無視していて痛い目を見た……）
```
WARN[0000] The “s” variable is not set. Defaulting to a blank string.
```


$マークのエスケープ方法はこちらに書いてくれていた。

https://qiita.com/takyam/items/8e942139a2b68429aafa
「The application should not provide its own launch screen」のワーニングに対するとりあえず対応


↓のように「The application should not provide its own launch screen」のワーニングが出たとき、とりあえず消す方法。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629308/b103f3fa-6b9d-903f-078e-46a91b806bf4.png)

Activity名の「Splash」の部分を別の適当な名前に変更する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629308/8873b889-6e04-e84a-b7e9-bebebbb59a52.png)
C++からPythonのライブラリ関数を呼び出す／複素数関数

# ソースコード

```cpp:main.cpp

#include <iostream>
#include <Python.h>

int main(int argc, char *argv[])
{
    Py_Initialize();
    PyObject *mpmath = PyImport_ImportModule("mpmath");
    PyObject *zeta = PyObject_GetAttrString(mpmath, "zeta");
    PyObject *pArgs = PyTuple_New(1);
    PyTuple_SetItem(pArgs, 0, PyComplex_FromDoubles(1.0, 1.0));    
    PyObject *ret = PyObject_CallObject(zeta, pArgs);

    const Py_complex a = {};
    if (PyArg_Parse(ret, "D", &a))
    {
        std::cout << a.real << std::endl;
        std::cout << a.imag << std::endl;
    }

    return 0;
}

```

APIドキュメントへのリンク：
[`PyImport_ImportModule`](https://docs.python.org/3/c-api/import.html#c.PyImport_ImportModule)
[`PyObject_GetAttrString`](https://docs.python.org/3/c-api/object.html#c.PyObject_GetAttrString)
[`PyTuple_New`](https://docs.python.org/3/c-api/tuple.html#c.PyTuple_New)
[`PyComplex_FromDoubles`](https://docs.python.org/3/c-api/complex.html#c.PyComplex_FromDoubles)
[`PyTuple_SetItem`](https://docs.python.org/3/c-api/tuple.html#c.PyTuple_SetItem)
[`PyObject_CallObject`](https://docs.python.org/3/c-api/call.html#c.PyObject_CallObject)
[`PyArg_Parse`](https://docs.python.org/3/c-api/arg.html#c.PyArg_Parse) ([フォーマット指定文字](https://docs.python.org/3/c-api/arg.html#strings-and-buffers))

# コンパイルのコマンド例

環境依存なので適宜修正して使用してください。

```bat:compile.bat

g++ main.cpp -o call_py_func_from_c -LC:\Users\（ユーザー名）\AppData\Local\Programs\Python\Python38\libs -lpython38 -IC:\Users\（ユーザー名）\AppData\Local\Programs\Python\Python38\include

```

`-Lライブラリのパス`
`-lライブラリのファイル名` ・・・(拡張子`.lib`部分を除く)
`-Iインクルードパス` ・・・Python.hがあるフォルダを指定

# 環境

Windows11
Python3.8
Mingw64 (g++を導入するため)

使用するライブラリは`pip`でinstallしておく必要あり。

# 参考サイト

https://docs.python.org/3/c-api/

https://qiita.com/NaokiAkai/items/0cf2b1d96b1a42c5a2f1

https://qiita.com/BabyJiG/items/9975195565df74debf6c
audio/x-mulaw形式の音声をWavで保存する

Twilioの音声データは`audio/x-mulaw`形式、8000Hz, 8bit, 1チャンネルの固有の型を持っています。

特に`x-mulaw`形式がわからなく、死ぬほどハマったので共有します。

# 結論
`x-mulaw`形式の方法があるのでそれに従います。
```jsx
const WaveFile = require('wavefile').WaveFile;

// Base64エンコードされたオーディオデータをデコード
let decodedData = Buffer.from(base64String, 'base64');

let wav = new WaveFile();
// 8ではなく、”8m”
wav.fromScratch(1, 8000, '8m', decodedData);

// μ-law形式としてWAVファイルを保存
wav.toMuLaw();
fs.writeFileSync('output.wav', wav.toBuffer());
```

<details><summary>自らWav Headerを作成する場合</summary>

```jsx
const fs = require('fs');
const WaveFile = require('wavefile').WaveFile;

const fileStream = fs.createWriteStream("sample.wav", {flags: 'a'});
// Wavファイルのヘッダーを作成。8000Hz, 8bit, 1チャンネル
const header = createWavHeader(8000, 8, 1);
fileStream.write(header);

~~~~~~~~~~~

// streamのpayloadから音声データを取得
base64String = msg.media.payload
// Base64エンコードされたオーディオデータをデコード
let decodedData = Buffer.from(base64String, 'base64');
fileStream.write(buffers);

~~~~~~~~~~~~~
fileStream.end();

//Headerにデータ容量を書き込む。なくても良い。
const dataSize = fileStream.length; 
header.writeUInt32LE(dataSize + 36, 4);  
header.writeUInt32LE(dataSize, 40);


function createWavHeader(sampleRate, bitDepth, channels) {
    const blockAlign = channels * bitDepth / 8;
    const byteRate = sampleRate * blockAlign;
    const header = Buffer.alloc(44);

    header.write('RIFF', 0);
    header.writeUInt32LE(36, 4); 
    header.write('WAVE', 8);
    header.write('fmt ', 12);
    header.writeUInt32LE(16, 16);
    header.writeUInt16LE(0x0007, 20); // AudioFormat PCMなら１, mulawは0x0007
    header.writeUInt16LE(channels, 22);
    header.writeUInt32LE(sampleRate, 24);
    header.writeUInt32LE(byteRate, 28);
    header.writeUInt16LE(blockAlign, 32);
    header.writeUInt16LE(bitDepth, 34);
    header.write('data', 36);
    header.writeUInt32LE(0, 40);

    return header;
}
```
    
</details>



:::note info
弊社Passinate Geniusでは一緒に働く仲間を募集しています！興味をお持ちいただける方は、[ホームページ](https://passionategenius.com/ "Passinate Genius")まで！

:::
Salesforceで複数のタブで項目を出し分ける方法

通常Salesforceでは１レコードに１ページレイアウトしか表示できません

![ページ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/53825e6b-7717-ab70-5775-80f6dd26b3d1.png)

しかしアクションを作成すればタブごとに項目を配置して「ページレイアウトの小分け」のような設定が可能です

参考ヘルプはこちら
https://help.salesforce.com/s/articleView?id=sf.bi_explorer_add_action_to_object.htm&type=5


まずはアクション種別は「レコードの更新」でAAというアクションを取引先に作成します

![AA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/b60b0e08-d12a-3579-0347-ec372cdbbbeb.png)

次に取引先のLightningページを編集します
![ページ２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/f56f752c-e4cb-3aac-77a9-9c25b5c46bab.png)


そしてタブを作成し左のコンポーネントから「関連レコード」を画面に表示させます
この時更新アクションは先ほど作成したアクションを選択しましょう
![更新アクション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/e3537ad1-fbd9-ae96-6967-278be35374de.png)


これで完成です
通常のページレイアウトではない状態で項目の配列が可能です
このようなアクションを作成しページレイアウトに表示させればページレイアウト小分け表示みたいなことが可能です
![完成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/1b22d399-5808-2e0c-0c92-8c7692c29637.png)