OmniAICreator/Qiita-1.07M huggingface dataset:
ViTのSAをゲート付きMLPで認識するモデル【gMLP】




**原論文**
Pay Attention to MLPs
https://arxiv.org/pdf/2105.08050.pdf

**日本語解説**

Attentionは不要!?Google BrainによるTransformer型モデルに対抗できるgMLPを詳細解説！
https://deepsquare.jp/2021/05/gmlp/


# 結論
ViTのSAをゲート付きMLPで認識すると性能向上する．



# 概要
画像認識のスタンダードモデルにはCNNやViTがあるが，その成功の秘訣はCNNやViTにあるのか？？ViTのSAをゲートを使用したMLPで特徴量の空間方向の認識を行う．
その理由として，MLPは普遍的な近似器であることが知られており，理論的には，その静的なパラメータの決定により，どんな関数でも表現できるはずだと考えられる．
ViTはグローバルで動的なパラメータ，CNNはローカルで静的なパラメータである．ViTとCNNは2つの異なる部分がある．MLPはグローバルで静的なパラメータであり，ViTとCNNの間のような役割がある．
これにより，DeiTと同程度の精度を達成する．



# モデル構造
主にViTのモデル構造を使用し，Encorderの部分を以下に示す．紫のチャンネル認識MLPで空間認識のSpatial Gating Unitを挟む構造であり，チャンネル→空間→チャンネルの認識である．ちなみに，ViTは空間→チャンネルの認識である．Activationは活性化関数を示す．


![スクリーンショット 2023-12-01 15.23.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457775/20a6d7a0-7d41-232a-6892-c6c5912a5b52.png)



以下の式に示す．
```math
\begin{align}
Z = σ(XU),　　　\tilde{Z} = s(Z),　　　Y = \tilde{Z}V
\end{align}
```
入力XでUは１つ目のチャンネルMLP，σは活性化関数のGELU，s()はSpatial Gating Unit，Vは2つ目のチャンネルMLPを示す．左から順番に進行しているとみたら分かりやすいと思う．


## Spatial Gating Unit
Spatial Gating Unitの最初の処理として，特徴量をチャンネル方向に2分割する．その片方を上図の右ルートに入力する．Spatial Projでは下式に示す$f_{W,b}$で空間認識MLPをする．この計算した特徴量と計算していない特徴量の要素積をとることでゲート付きMLPになる．例えば，以下の式で，重みWを0に非常に近い値，bを1に等しくすると，s()の出力は入力値とほとんど変化せずに恒等関数の役割をして学習が安定する．
学習初期段階では，重みが変更されず，空間認識で恒等関数の状態が続き，2つのチャンネル認識MLPのみ機能し，各トークンは独立して処理される．学習過程で空間認識が徐々に注入される．
また，特徴量を分割して処理する理由としては，実験的に有効であったためである．本来の目的として，特徴量の重みつけであるためSENetと似た手法である（SENetはチャンネル方向の重みつけであるが，gMLPは空間方向の重み付けであることに注意）．



```math
\begin{align}
f_{W,b}(Z) = W Z + b \\
s(Z) = Z　\odot f_{W,b}(Z) 
\end{align}
```

↓雑だけど，Spatial Gating Unitのイメージ図
![スクリーンショット 2023-12-01 16.32.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457775/6450686c-dda2-07a3-e384-ca7d09e995b4.png)



# 結果
ImageNet-1kの画像分類精度はDeiTと同程度の精度，従来のMLP手法を超える精度を達成した．


また，空間MLPの行列の可視化を以下に示す．結果はCNNと同様に浅い層で局所的な認識，深い層で大局的な認識に重点を置いている傾向にある．また，CNNと異なり，正方形ではないカーネルの使用で汎用性の高い構造をMLPは持つ．

![スクリーンショット 2023-12-01 17.40.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457775/1e64ba01-b331-02df-5f6b-7acca00243d3.png)




# まとめ
今回は，ViTのSAをゲート付きMLPで認識するモデルについて説明した．空間認識はSAでも畳み込みでもないMLPでも可能であることが分かった．今後，MLPモデルが成功する未来があるかもしれない．
E2Eテストフレームワークの比較・ちょっと試した結果

## 概要
既存の人気のE2Eテストのフレームワークを調査を行って、コードを書いてみました。

## 内容
|  | Selenium | Puppeteer | Playwright | Cypress |
| --- | --- | --- | --- | --- |
| サポート<br>言語 | Java<br>Python<br>C#<br>Ruby<br>JavaScript<br>Kotlin | JavaScript/TypeScript | JavaScript/TypeScript<br>Python | JavaScript/TypeScript |
| ブラウザー | Chrome<br>Firefox<br>IE<br>Safari | Chrome<br>Firefox | Chrome<br>Edge<br>webkit(Safari)<br>Firefox | Chrome<br>Firefox<br>Edge<br>Electron |
| 環境構築 | Driverを更新する必要 | テストケースを書くために、他のテストフレームワークと組み合わせてインストールが必要 | 基本的にPlaywrightだけのインストールで十分 | 基本的にCypressのインストールだけで十分 |
| スピード |  ★☆☆  | ★★★ | ★★★ | ★★☆ |
| 参考資料多くさ | ★★★ | ★★☆ | ★☆☆ | ★★☆ |
| 勉強しやすさ<br>(個人の感じ) | ★☆☆ | ★★☆ | ★★☆ | ★★★ |
| メリット | <ul><li>参考資料が多い</li><li>複数な言語・ブラウザーをサポート</li></ul> | <ul><li>環境構築が簡単</li><li>ドキュメントが完備</li><li>screenshot取ることが可能</li><li>テストする時に、jsを簡単に動く</li><li>HTTPリクエストの結果を取ることができる</li></ul> | <ul><li>環境構築が簡単</li><li>ドキュメントが凄く完備</li><li>動画を取ることができる</li><li>Puppeteerのコピーみたいですが、Puppeteerより機能が豊富</li></ul> | <ul><li>環境構築が簡単</li><li>ドキュメントが凄く完備</li><li>UI画面がある</li><li>動画が取る可能</li><li>レポート出力可能</li><li>ロード完了まで自動的に待つ</ul> |
| デメリット | <ul><li>driverを更新する必要</li><li>書きにくいところが多い：例えば、Screenshotを取るなどの機能を実現する場合は、自分で書くか・別のモジュールを利用する必要がある</li><li>動くが遅い</li></ul> | <ul><li>ChromeとFirefoxだけをサポート</li><li>テストケースを書くため、他のテストフレームワークと組み合わせてインストール必要</li></ul> | <ul><li>IEサポートしてない</li><li>出る時間が遅いので、比較的に資料が少ない</li></ul> | <ul><li>複数タブがサポートしてない</li><li>IE、Safariがサポートしてない</li><li>CI連携が有料（月500個のテストケースまでは無料）</li></ul> |
| 自分で試して作ったサンプル | https://github.com/Doris927/selenium-js-example | https://github.com/Doris927/puppeteer-example | https://github.com/Doris927/playwright-example | https://github.com/Doris927/cypress-example |
| ドキュメント | https://www.selenium.dev/ja/documentation/ | https://github.com/puppeteer/puppeteer | https://playwright.dev/ | https://www.cypress.io/ |


## まとめ
試しに書いてみた感じだと、Puppeteer・Playwrightはエンジニア用のE2Eテストフレームワークで、Cypress・SeleniumはQA（Quality Assurance|品質保証）の方に向いている印象でした。

## 参考資料
- [Seleniumブラウザー自動化プロジェクト](https://www.selenium.dev/ja/documentation/)
- [puppeteer/puppeteer: Node.js API for Chrome](https://github.com/puppeteer/puppeteer)
- [JavaScript Component Testing and E2E Testing Framework](https://www.cypress.io/)
- [Cypressしか勝たん！～テスト担当者ファーストなE2Eテストツール～](https://microayatron.com/setting-up-cypress-environment/)
- [2021年12月現在、PlaywrightとPuppeteerのどっちを使うべき？](https://qiita.com/YusukeIwaki/items/f4616094c5572f1f7d25)
- [Puppeteer使いがPlaywrightを使ってみて感じた利点](https://zenn.dev/84q/articles/ed6f66ab80c7ce)
ChatGPTとGitHubCopilotを導入してみての感想

# はじめに
今回、Progakuのアドベントカレンダー初日の記事を担当しました。

Prgakuというプログラミング学習コミュニティに関しては下記を参照してください。

https://note.com/tokuyuuuuuu/n/nd5d777751b60


2023年もそろそろ終わりを迎えようとしていますが、僕が今年一番業務上で大きく変化があったのがChatGPTやGitHubCopilotというツールの使用し始めたことです。

まだまだ自分も使い始めたところで探り探りな状態ですが、自分の普段の使い方を記していきます。

# ChatGPTについて

ChatGPTに関してはおそらく既に多くの方が使っているかと思います。

自分は先月課金し始めましたが、僕自身使用率が高いわけでは無いので無料でも良いかなっと思っています。

使い方としては、

### 1.コードのリファクタリング

「なんかしっくりこないな」とか「もう少し良い書き方出来そう」ってコードが出来た時に、コードを載せてリファクタリングしてもらうように依頼しています。

### 2.エラーの解決

***「ググる」という行為はChatGPTに移行した***と思います。

勿論、ドキュメント読んだりもしますが、初手はChatGPTに書くのが定番となりました。

初めにChatGPTに聞いて、レスポンス返ってくる間に自分でも調べてみるって流れです。

### 3.ド忘れしたことを聞く

このオプションってどんな記述だっけ？？
pg_restoreのコマンドってどう書くっけ？？

みたいに***知らない訳じゃないけどふと使う時に覚えてないみたいなことをよく聞きます。***

個人的にはこれが一番割合高いんじゃないかなと思います。

ただ、これもGitHubCopilotを導入してから少し頻度が減った気がします・・・

# GitHubCopilotについて

こちらは気にはなってたんですが腰が重くて導入してなかったのですが、コミュニティメンバーのライブコーディングを見て先月導入し始めました。

正直、使用感は個人的にとても良いです！

とはいえ、まだまだ使用歴が浅いので使いこなせてる感はそこまでかもですが、下記に自分が便利だと思ったことを記していきます。

### 1.コメントを記載するとコードを提案してくれる

コメントをつけるとそれに沿ったコードの提案をしてくれます。(下記gif参照)
 
![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474880/8a5cf22f-1dc1-314d-5dd4-746df91fcd64.gif)

### 2.メソッド名からコードを察してくれる

Userモデルにfirst_nameとlast_nameというカラムをもっていたとして、フルネームを取得するメソッドを作ったデモです。

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474880/3540ac1e-057f-61bf-b8ea-04e203950a6b.gif)

この例は少しコードのクオリティは微妙ですが(汗)、「def full」と「first_name」を打つだけで保管してくれるのはすごいと思います。

ここら辺はもっと使っていけば精度も上がってくるんじゃないかなっと。

後は、テストやマイグレーションファイルを書くときも結構役に立ってます。

# 導入してみた感想とまとめ

別に補完して貰わなくても書けるし
課金してまでは良いかなぁ

っと思っていた自分ですが、実際に導入してからは開発スピードがグンと上がりました。

単純なコードであればあるほど書くのが面倒くさいですが、AIを使用することによって自分のタイピング時間というのが減りました。

　また、***自分で考えるべき箇所に頭を使うリソースを避けるので、作業が楽になりました。***

ただ、特に初学者の方に勘違いしてもらいたくないのは、***あくまでこれらはサポートツールとして使わないといけない***です。

前提として補完されたコードが何書いてるかわからないとか、動いたから良いよねみたいな事は絶対にNGです。

そこさえ気をつければ、エンジニアの皆さんは積極的に導入していくと良いんじゃないかなと思いました！
企業によっては会社アカウントで使わせてくれるみたいですね〜

ここまでAIを褒めちぎってきましたが、AIによってエンジニアが不要になるとか、仕事奪われるみたいな話もSNSで見かけますがそれに関してはまだまだ難しいんじゃないかなって思います。(この話はまた別記事にでもしますかね)

ここまでご購読いただきありがとうございました！
無効なメールアドレスの作り方

:::note info
※ 私が株式会社愛宕 Advent Calendar用に書く記事は主に社内向けに共有しておきたいけど勉強会をするまでもないちょっとしたTipsにしたいと思います。
:::

今日の話題は、メールアドレスの無効化についてです。

## なぜ無効なメールアドレスが必要なのか
何らかのシステムでメールアドレスを登録しているものとします。
メールアドレスにはユニーク制約がついていて同じメールアドレスは登録できないのが一般的です。

このようなシステムで、一度あるメールアドレス(`hoge@example.com`)を登録したユーザーがアカウントを削除したとします。
この時データベースから完全に削除できるなら問題ないですが、要件によってはフラグを立てたり、削除年月日を更新するだけで実際の削除は後回しにする場合もよくあります。
このような場合、以前のユーザーがもう一度アカウント登録して同じメールアドレス(`hoge@example.com`)を登録しようとするとユニーク制約エラーになって登録できません。

## 解決
この問題を解決するのに簡単な方法はメールアドレスの末尾に`.invalid`を付ける方法です。
`.invalid`は[RFC2606](https://datatracker.ietf.org/doc/html/rfc2606)で予約されており、一目で無効であることが判別できて絶対に繋がらないことになっているドメインです。
これをメールアドレスに付けると、先ほどの例に従うと`hoge@example.com.invalid`となってメールアドレスの形式を守りながら、実際には送信できないメールアドレスとなり、さらに元のメールアドレスとは違う値なのでユニーク制約の問題も回避できます。

ちなみに、今回例示として使った`example.com`もRFC2606で予約されているものです。
DatabricksのPandas UDFによるtransformerモデルの分散処理

こちらのセクションをウォークスルーします。

https://docs.databricks.com/ja/machine-learning/train-model/huggingface/model-inference-nlp.html#use-pandas-udfs-to-distribute-model-computation-on-a-spark-cluster

[Pandas UDF](https://qiita.com/taka_yayoi/items/b65197128ee698d87910)を用いることで、pandas.Seriesやpandas.DataFrameに対する処理を記述することができ、さらにこれをSparkで並列処理することができます。この仕組みはTransformerモデルにも適用できます。

以下の例ではTransformersのpipelineを用いて、[英語からフランス語への翻訳を行うパイプライン](https://huggingface.co/tasks/translation)を作成しています。

```py
import pandas as pd
from transformers import pipeline
import torch
from pyspark.sql.functions import pandas_udf

device = 0 if torch.cuda.is_available() else -1
translation_pipeline = pipeline(task="translation_en_to_fr", model="t5-base", device=device)

@pandas_udf('string')
def translation_udf(texts: pd.Series) -> pd.Series:
  translations = [result['translation_text'] for result in translation_pipeline(texts.to_list(), batch_size=1)]
  return pd.Series(translations)
```

```py
texts = ["Hugging Face is a French company based in New York City.", "Databricks is based in San Francisco."]
df = spark.createDataFrame(pd.DataFrame(texts, columns=["texts"]))
display(df.select(df.texts, translation_udf(df.texts).alias('translation')))
```

翻訳されました。今回は2レコードだけでしたが、レコード数増えてもSparkの並列処理の恩恵を受けられると言うことですね。バッチで大量のドキュメントを翻訳すると言うユースケースで効果が出そうです。
![Screenshot 2023-12-01 at 17.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/268ce40c-f746-73fa-dbc6-3c2271f5050c.png)

さらには別のモデルとタスクを指定することで、NER(Named Entity Recognition: 固有表現抽出)を行うこともできます。

```py
from transformers import pipeline
import torch
device = 0 if torch.cuda.is_available() else -1
ner_pipeline = pipeline(task="ner", model="Davlan/bert-base-multilingual-cased-ner-hrl", aggregation_strategy="simple", device=device)
ner_pipeline(texts)
```

```
[[{'entity_group': 'ORG',
   'score': 0.99933606,
   'word': 'Hugging Face',
   'start': 0,
   'end': 12},
  {'entity_group': 'LOC',
   'score': 0.99967843,
   'word': 'New York City',
   'start': 42,
   'end': 55}],
 [{'entity_group': 'ORG',
   'score': 0.9996372,
   'word': 'Databricks',
   'start': 0,
   'end': 10},
  {'entity_group': 'LOC',
   'score': 0.999588,
   'word': 'San Francisco',
   'start': 23,
   'end': 36}]]
```


### Databricksクイックスタートガイド

[Databricksクイックスタートガイド](https://www.amazon.co.jp/dp/B09V1YXFVQ/)


### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)
Dockerとdocker composeとdevcontainerで作るNext.js環境

# 概要

可能な限り簡単にDocker+docker compose+devcontainerでNext.js環境を立ち上げます。

1. docker-compose.ymlファイルを用意する
1. Dockerfileファイルを用意する
1. devcontainerを起動する
1. `npx create-next-app`を実行する
1. ファイルを移動する

## docker-compose.yml
ファイルの永続化はdevcontainer起動時に良しなにしてくれるので省略。

```yaml
version: "3"
services:
  web:
    build: .
    container_name: "nextjs-web"
    ports:
      - "3000:3000"
```

## Dockerfile
とりあえずイメージだけ指定する。

```dockerfile
FROM node:lts-iron
```

## devcontainer起動

### F1⇒開発コンテナー:コンテナーで再度開く

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/33fb036d-bed0-1766-7b5e-08fa940b0ed2.png)

### 'docoker-compose.yml'ファイルから⇒OK

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/96c322d4-16bb-65dc-984f-8bd0f4218261.png)

### `npx create-next-app`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/c94a4e05-8252-b4f5-d369-945e173719d2.png)

### ファイルの移動

```bash
mv my-app/* .
rm -fr my-app/
```

:::note info
create-next-appは既存ディレクトリがあると作成できないので、一時的なディレクトリに作成している。このままで良い場合はファイルは移動しなくてもOK。
:::

:::note info
`.eslintrc.json`, `.git`, `.gitignore`は移動されないので、必要に応じて移動させる
:::

## 雑感

最後のファイル移動がイケてない気がするが、簡単に環境を作ることができた。

以上。
foreach前のifによる空確認は不要なのか

先程、コードレビューで指摘が入っていて、ふと気になったので。

# コード指摘部分と指摘（だいたいこんな感じ）
```php
if (!empty($tags)) {
	foreach ($tags as $tag) {
    	// 処理 
	}
}
```

**指摘**
> この下にあるのがforeachなのでこのif($tags)は不要だと思います

割りと臆病なので、変数の存在確認やら空確認を冗長に挟んでしまう癖があった。

冗長なものと必要なものに関しては大体理解しているつもりであるものの、これに関しては必要なものという認識だった。
10年弱、foreach前の配列の空確認に関しては毎度おこなっていたので、「あれこれいらないの？」と結構カルチャーショック。

「foreach前 if 存在確認 不要」でggってもピンとくる記事がないので、ChatGPTに聞いた。

# ChatGPTに質問してみた

>質問「foreach前にifで配列の空確認するって無意味？」

>ChatGPT
>
>foreachの前にif文で配列の空確認を行うことは、一般的には無駄ではありません。>これは、プログラムの健全性やエラーハンドリングの一環として有用です。
>
>例えば、以下のようなケースを考えてみましょう:

```php
// ここで配列が空でないかを確認する
if (!empty($myArray)) {
    foreach ($myArray as $item) {
        // 配列の各要素に対する処理
    }
} else {
    // 配列が空の場合の処理
    echo "配列は空です。";
}
```

>このようなコードを追加することで、配列が空である場合に特定の処理を行うことができます。例えば、ユーザに対して何かしらの通知を行ったり、エラーを適切にハンドリングしたりできます。
>
>ただし、状況によっては不要な冗長性を生むこともあります。たとえば、foreachループ内の処理が空の場合でも問題ない場合や、既に空であることが分かっている場合などは、このような確認は省略することもあります。バランスを考えつつ、コードの可読性やエラーハンドリングの観点から適切なアプローチを選ぶことが重要です。

この回答を見るに、空の場合になにか処理が必要な場合もあるので一概に無駄じゃないよ、
でも不必要なのにif文かますと冗長性を生むことがあるよ、との回答だった。

# まとめ
てことはやっぱり今回のレビューに関しては指摘のおっしゃるとおりということになるのかな。
勉強になりました。


「違うよ」とか「俺はこう思う」とかご意見や指摘があれば、コメント欄にてお願い致します。

・・・というかテキトーに質問したのにちゃんと答えてくれるChatGPTってやっぱり凄いんですね。
未だ自然言語を理解できるAIは存在しないらしいけど、ホントかいな。
【メモ】Laravel　初心者まとめ

## LaravelAPIメモ
### 概要
- LaravelのAPIを作成する際に、考えたことや学んだことなどを簡単にまとめる
- 覚書程度であるため、詳細を見たい場合は参考のサイトなどに飛ぶこと

### Laravel関係　コマンド
```sh:コマンド
# Laravelプロジェクトの作成
# フォルダ名を[.]にすることで、カレントディレクトリに作成が可能
# 用例：composer create-project laravel/laravel laravel-api-create-test
composer create-project laravel/laravel フォルダ名

# サーバーの起動
php artisan serve

# portオプション
php artisan serve --port = 8080

minatomi. １週間で基礎から学ぶLaravel入門 (p.21). minatomi. Kindle 版. 
# app/Models配下にファイルを作成
# -mでマイグレーションファイルも同時に作成
# 用例：php artisan make:model Student -m
php artisan make:model ファイル名 -m

# app/Http/Controllers配下に、新しくコントローラーファイルを作成
# 用例php artisan make:controller ApiProductController
php artisan make:controller ファイル名

# make:controller　optionあり
# モデルと関連付けて作成することが可能
# 用例：php artisan make:controller ApiProductController --model=Product
--model=モデル名

# request配下にrequestファイルを作成
# 用例php artisan make:request StoreProductRequest
php artisan make:request ファイル名

# app/Rules配下に、ruleに関するファイルを作成
# 用例：php artisan make:rule AllowedFields
php artisan make:rule ファイル名
# テーブルの作成
php artisan migrate

# データベースに初期データを追加するファイルを作成
# 詳細については、Laraverl seedで検索
php artisan make: seeder ファイル名

# seedファイルの実行コマンド
php artisan db: seed

# routeingを確認
php artisan route: list
```
#### 参考サイト

https://kinsta.com/jp/blog/laravel-api/

https://www.twilio.com/ja/blog/building-and-consuming-a-restful-api-in-laravel-php-jp

### Laravel API 記法
```php:api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Symfony\Component\HttpFoundation\Response;
use App\Http\Controllers\ApiCardFormatController;

// 認証をつける場合は、ミドルウェア
// 未だ理解しきれていないため、今後まとめる
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

// 直接ルートに関数を記述することで、レスポンスを行う
// JavaScriptのコールバック関数に類似
// Node.js Express.jsの記法に大分近い
Route::get(
    'hello',
    function (Request $request) {
        return response()->json(['message' => 'Hello World!'],Response::HTTP_OK);
    }
);
// 別ファイルで定義したルートを使用する場合
// useで宣言したのちに、使用したい関数を指定することで使用可能
// 可読性が上がるため、基本はこちらを利用するはず
Route::post('card/format/create', [ApiCardFormatController::class, 'index']);

// APIエンドポイントにおいて、すべてのエンドポイントに合致しなかった場合に処理を行う関数
// 404を返す
Route::fallback(function () {
    return response()->json(['message' => 'Not Found'], Response::HTTP_NOT_FOUND);
});
```
Response::HTTP_NOT_FOUNDのようにResponseクラスに定数で、ステータスコードがあるため直感的に理解ができてよい。

### テーブル操作
```php
// Hogeテーブルに入っているレコードをすべて取得する
hoge = Hoge:: all();
```
### LaravelのStd.out的なもの
```php
dd($ 変数);
```


### 用語
#### マイグレーションファイル
- データベース操作をまとめるファイル
- テーブルごとに設定を行うためのファイル
- マイグレーションファイルを実行することで、テーブル操作を行う。

### まとめ
今後も追記していく
Keycloakの管理コンソール画面をみてみる（利用者向けコンソール編）

:::note info
※本記事は、Keycloakのバージョンアップに伴い、「[Keycloakの管理コンソールの機能をみてみる（利用者編）、ユーザー・アカウント・サービス](https://qiita.com/masuo3/items/41cba64b6dc33b7f2b20)」の記事を最新化したものです。
:::
# やること
keycloakには
・管理者向けコンソール
・利用者向けコンソール
があります。

今回は、利用者向けコンソールの各項目をみていきます。


# 前提
こちらを実施していることが前提になります。

https://qiita.com/l_katayose/items/0e942a160fa65ed32b1d

今回セットアップした環境は以下です。
・keycloak 23.0.7
・Google Chrome 119.0.6045.200
Windows PowerShellでKeycloakサーバーを立ち上げ、Chromeで管理者向けコンソールにアクセスしました。

# 利用者向けコンソールの概要
利用者向けコンソールとは、ユーザー単位で作成される設定画面のことです。
利用者向けコンソールにアクセスする場合、以下にアクセスしユーザーでログインしてください。
http(s)://[ホスト]（:[ポート]）/realms/[レルム名]/account/

事前に作成したtestユーザーでログインした場合以下の画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551808/51d24466-f922-9ff0-c6fa-82f3f0d61ac4.png)


# Personal Info
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551808/6602a001-cd1e-b26e-fa57-a4de75409cfe.png)
個人情報を設定できるページです。具体的に、以下の項目があります。
|項目名  |説明  |
|---|---|
|ユーザー名  |ユーザー名を設定します。|
|Eメール  |メールアドレスを設定します。|
|名|名を設定します。|
|姓  |苗字を設定します。|
|Select a locale|言語を選択できます。事前に管理者コンソールの"レルムの設定"で利用したい言語を追加する必要があります。|

# Account security
### Signing in
ユーザーの認証に関するページです。パスワードの更新や、二段階認証の設定を行うことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551808/95d8230b-a32a-75a2-7e58-78ed85243877.png)
### デバイス・アクティビティー
ログイン中のセッションをデバイス単位で管理できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551808/7128a41e-6ffb-e343-a2be-cb34a96a91e3.png)



# アプリケーション
現在のユーザーでアクセスできるアプリケーションの一覧です。このtestユーザーでは利用者向けコンソールにのみアクセスできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551808/bd581087-82b6-6061-9cee-136755462a10.png)

# 参考
 - [Keycloakの公式ドキュメント](https://www.keycloak.org/documentation.html)
 - [Openstandiaによる日本語ドキュメント](https://keycloak-documentation.openstandia.jp/)
Pythonで「どのようなオブジェクトが真(True)や偽(False)と評価されるのか」の動作を確認してみた

# 概要

Pythonで「どのようなオブジェクトが真(True)や偽(False)と評価されるのか」の動作を確認してみました。以下のページを参考にしました。

https://www.javadrive.jp/python/if/index3.html

# 実装

以下のファイルを作成しました。
```sample.py
print(bool(True))
print(bool(False))
print(bool(None))
#print(bool(NotImplemented))
print(bool(0))
print(bool(5))
print(bool(0.0))
print(bool(0.1))
print(bool(0j))
print(bool(""))
print(bool("Hello"))
print(bool(()))
print(bool(("Blue","Red")))
print(bool([]))
print(bool(["Apple", "Orange"]))
print(bool({}))
print(bool({"A":"Apple","O":"Orage"}))

name = ""
if name:
    print("名前は" + name + "です")
else:
    print("名前は未登録です")

name = "Yamada"
if name:
    print("名前は" + name + "です")
else:
    print("名前は未登録です")
```

以下のコマンドを実行しました。
```
$ python3 sample.py 
True
False
False
False
True
False
True
False
False
True
False
True
False
True
False
True
名前は未登録です
名前はYamadaです
```

# まとめ

何かの役に立てばと。
分割圧縮ファイルの解凍

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class FileProcessor {

    public static void main(String[] args) {
        splitAndCombineFiles("/input/sample.docx", "/split", "/output/sample.docx");
    }

    private static void splitAndCombineFiles(String inputPath, String splitFolderPath, String outputPath) {
        try {
            // Split the original file
            splitFile(inputPath, splitFolderPath);

            // Combine the split files
            combineFiles(splitFolderPath, outputPath);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void splitFile(String inputPath, String splitFolderPath) throws IOException {
        File inputFile = new File(inputPath);
        byte[] fileContent = readBytesFromFile(inputFile);

        int sections = 3;
        int bytesPerSection = fileContent.length / sections;

        for (int i = 0; i < sections; i++) {
            int startIndex = i * bytesPerSection;
            int endIndex = (i == sections - 1) ? fileContent.length : (i + 1) * bytesPerSection;

            byte[] sectionContent = new byte[endIndex - startIndex];
            System.arraycopy(fileContent, startIndex, sectionContent, 0, sectionContent.length);

            writeBytesToFile(splitFolderPath + "/split" + (i + 1) + ".bin", sectionContent);
        }
    }

    private static void combineFiles(String splitFolderPath, String outputPath) throws IOException {
        List<byte[]> splitContents = new ArrayList<>();

        // Load split files
        for (int i = 1; i <= 3; i++) {
            byte[] content = readBytesFromFile(new File(splitFolderPath + "/split" + i + ".bin"));
            splitContents.add(content);
        }

        // Combine split contents
        byte[] combinedContent = concatenateByteArrays(splitContents);

        // Save combined content to output file
        writeBytesToFile(outputPath, combinedContent);
    }

    private static byte[] readBytesFromFile(File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }

            return bos.toByteArray();
        }
    }

    private static void writeBytesToFile(String filePath, byte[] content) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(content);
        }
    }

    private static byte[] concatenateByteArrays(List<byte[]> byteArrays) {
        int totalLength = 0;
        for (byte[] array : byteArrays) {
            totalLength += array.length;
        }

        byte[] result = new byte[totalLength];
        int currentIndex = 0;

        for (byte[] array : byteArrays) {
            System.arraycopy(array, 0, result, currentIndex, array.length);
            currentIndex += array.length;
        }

        return result;
    }
}
OpenShiftのコンテナーレジストリー用プルシークレットの登録

# 目的
OpenShift に Cotinaer Registryログイン用のログイン情報を保管する方法の備忘録です。

# Podmanログイン

podman login すると json ファイルにログイン情報が残ります。これを使ってOpenShiftに登録できます

```
podman login docker.io
```

jsonファイルがどこに作成されるかは以下に記載がありました。

[podman-login — Podman documentation](https://docs.podman.io/en/stable/markdown/podman-login.1.html)

```
${XDG_RUNTIME_DIR}/containers/auth.json on Linux, and
$HOME/.config/containers/auth.json on Windows/macOS. 
The file is created by podman login. 

If the authorization state is not found there, 
$HOME/.docker/config.json is checked, which is set using docker login.
```

Dockerの情報は見つかるのですが、podmanで場所がわからず探すことになりましたが、マニュアルに記載ありました。


# OpenShiftに登録

ファイルが見つかれば、登録はすぐにできます。

```
oc login -u kubeadmin
oc set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson=$HOME/.config/containers/auth.json
```

# まとめ

OpenShiftに Container Registryログイン用の情報を登録できました。
ライブラリのイマを追う時は、まずスターターテンプレートをみよう

開発に長く関わっていると、package.jsonの中身は随分かわっていきますよね。メインで使ってるライブラリだけではなく、 `typescript` などのdevDepenciesも気づいたらメジャーバージョンがあがってたりします。
ただ、じゃあすべてを最新版ににすればいいかというと、依存するメインライブラリが `dependencies` にいれているパッケージのバージョンを見る必要があります。けれど、それをすべて確認するのは現実的ではありません。

そこで私は、定期的に生成するCLIテンプレートをみるようにしています。タグにいれている、 `ionic` 、 `capacitor` 、 `angular` 、 `NestJS` は（私が採用してるというだけの選択ではあるのですが）、CLIでスターターテンプレートを使ったプロジェクト作成ができます。言い換えると、CLI内、もしくはレポジトリとしてスターターテンプレートを持っているわけです。なので、これをみることで、メンテナが推奨している構成を確認することができます。

いろいろ言ってもわかりにくいと思うので、例えばIonic Angularプロジェクトのスターターテンプレートは以下にあります。

https://github.com/ionic-team/starters/tree/main/angular-standalone

このディレクトリ以下に、 `base` 、 `official` があります。Ionic CLIは、プロジェクト作成するとまず `base` をコピーしてきて、そこにCLIで選択したテンプレート（`tabs` や `blank` など） を `official` から引っ張ってきて上書きするようになっています。なので、例えば依存関係をみたい時は `base` の `package.json` ですね。

https://github.com/ionic-team/starters/blob/main/angular-standalone/base/package.json

また、 `angular.json` や `tsconfig.json` も時代とともに推奨の構成が変わっていますので、どう変化しているかを追うと面白いです。

Angularのngコマンドで生成されるものは以下にすべてまとめられています。

https://github.com/angular/angular-cli/tree/main/packages/schematics/angular

これをみると、Ionic AngularとAngularの推奨構成の違いをみたりすることもできて面白いです。

Capacitorプラグインのテンプレートであったり：

https://github.com/ionic-team/create-capacitor-plugin/tree/main/assets/plugin-template

NestJSのTypeScriptプロジェクトのテンプレートであったり：

https://github.com/nestjs/typescript-starter

どうしてもプロジェクトを長期でまわしていると、イマの推奨構成から外れていってしまうことがあるので、定期的にみて、どのような構成をイマの推奨構成としているかを確認することはプロジェクトの質の向上につながると思います。

ぜひご活用ください。
トラックボールマウスを布教された結果

## 概要
これが
<img width="600" alt="前.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626221/e9603dae-6708-a816-9b73-40fe7ab7efea.png">

こうなった
<img width="600" alt="後.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626221/cc8e9873-d982-c48e-55e7-1c217a362d10.png">

## すべての始まり
ある日、ちょっとした打ち合わせから自席へ戻った時のことです。

普段使っている会社支給のマウスが隅へと追いやられ、
代わりにロジクールのMX ERGOが置かれていました。

<img width="400" alt="nankairu.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626221/1a6b3cf0-fd63-86ab-1d53-cd8a6501abf9.jpeg">

（傾斜スタンドに立てられている状態でした）

恐る恐る使ってみたところ、全く違和感なく操作でき、
その日のうちに手に馴染んでしまいました。

## マウスの次は…？
気が付くと、休憩時間を利用して会社付近の家電量販店を練り歩き、
様々なキーボードを試打していました。

打ち心地とコスパの良さから、現在はエレコムのV-custom VK200Tを使用しています。
若干底打ち音が気になるので、静音化リングを取り付けて対策しています。
<img width="600" alt="key.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626221/1094c3b9-122d-089e-1528-2861654e40d1.jpeg">

また、キーボードに合わせてFILCOの分離型リストレストを導入しました。
手首への負担が明らかに減っています。

## 開発マシンの追加導入に合わせて
会社からIntel NUCが別途支給され、
これまで使用してきたノートPCと合わせて2台体制での開発が可能になりました。

これに合わせて、USB・HDMI切り替え器（GREATHTEK製の2入力1出力タイプ）を導入しました。
<img width="600" alt="kirikae.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626221/9301f37e-caf3-b2a8-7917-d3d25c3c402d.png">
（上に乗っかってるのが切り替え器、下がNUC）

接続している機器をボタン一つで切り替えてくれるので重宝しています。
（リモートデスクトップ接続という選択肢もありましたが、カクつくのが嫌でやめました）

また、メインの開発をNUCで行うようになったためモニター配置を変更し、
ノートPCはサブ器としてスタンド上に置くことにしました。
ベタ置きするよりもスペースに余裕ができて、非常に快適です。

## まとめ
新卒入社してほぼ1年間、会社支給のデバイスだけの状態だったところから、
トラックボールマウスきっかけで、およそ半年の間にデスク周りの環境が一変しました。

何でも恐れずに試してみるのは良いことだと身をもって実感しました。
きっかけをくれた同チームのメンバーには、感謝してもしきれません。

これで日々の開発も爆速になっている…（はずです）

## お知らせ
DONUTSでは新卒中途問わず積極的に採用活動を行っています。
詳細はこちらをご確認ください。

https://www.donuts.ne.jp/recruit/
VSCodeを使ったRustのデバッグ環境に関するメモ

ちょっと思い立ってRustを勉強するかと取り組み始めたもののデバッグ環境構築で手こずった件の覚書。

作業ディレクトリをユーザードキュメント以下ではない任意の場所に配置した際にCodeLLDBでのデバッグ起動がうまくできなかった。
ひとまず期待した状態にはなったので記録しておく。

もっと調べると正規の作法がありそう。検索するにもキーワードをうまく用意できず…。EdgeのCopilotあたりを使うにしても自分の指示能力が低くて嫌になる。

## 症状

CodeLLDBを使ったデバッグ起動ができない。ビルドまでは行ってくれる。

試した ./vscode/launch.json への記述はよく見かける記事のようにcargoエントリを使った指定。

[Launching a New Process @ codelldb/MANUAL.md](https://github.com/vadimcn/codelldb/blob/v1.10.0/MANUAL.md#launching-a-new-process)

なおプロジェクトがユーザーディレクトリの Documents 以下にある場合は期待通りに起動できる。

## 環境

OSはWindows 11。Dドライブの任意の開発作業用ディレクトリ以下にプロジェクトを生成。

使用しているツール類は次のような感じ。

| | | |
|---|---|---|
|Visual Studio Code|1.84.2| |
|Visual Studio Build Tools 2022|17.8.2| |
|rustc|1.74.0| |
|cargo|1.74.0| |
|rust-analyzer|v0.3.1748|VSCode extension|
|CodeLLDB|v1.10.0|VSCode extension|

## 何が起きている？

これを書いている時点では結局解明していない。
きちんとログを追えばおそらくわかるはず。そこまでのやる気が…

ビルド成果物のパス探索の問題と予想していてCodeLLDBの設定で対応できると考えている。

## 対応

### launch.jsonの用意

CodeLLDBのドキュメントを見ると単純にデバッグ起動する書き方は実はcargoエントリを使わない。

[Starting a New Debug Session @ codelldb/MANUAL.md](https://github.com/vadimcn/codelldb/blob/v1.10.0/MANUAL.md#starting-a-new-debug-session)

こちらの書き方であればデバッグ起動できるのでアレンジを試みる。

なお公式ドキュメントにある\<executable file\>は\<executable file\>のことなのでパスを明示する必要がある。
公式サンプルの書き方ではプロジェクトの起点ディレクトリ直下に実行ファイルが配置されている想定なのでcargoを使ったデバッグビルドのディレクトリの階層の追記が必要。

そして次のようになる。
今回はアプリケーションとプロジェクトディレクトリを同じ名前とする想定の書き方。

```json:./vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug executable",
      "program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
      "args": [],
      "cwd": "${workspaceFolder}",
      "preLaunchTask": "rust: cargo build"
    }
  ]
}
```

最後のpreLaunchTaskというエントリがポイントになる。

公式サンプルのlaunch.jsonの延長ではビルド済みの実行ファイルを起動してくれるだけでビルドは行ってくれない。

毎回別途ビルドするのはさすがにだるい。

そこで実行前に別のタスクを起動しそのタスクでビルドを行うことにする。
タスクの起動指定が**preLaunchTask**。

ここでしているタスク「rust\: cargo build」は実はVSCode向けに用意されたcargoの標準タスクの一つ。

記述内容はGUIを通してテンプレートをもとに実体化することで確認できるがさっくり説明すると次の内容。

```json
{
    "type": "cargo",
    "command": "build",
    "problemMatcher": [
        "$rustc"
    ],
    "group": "build",
    "label": "rust: cargo build"
}
```

**label**をlaunch.jsonのpreLaunchTaskで指定していることになる。

## 実行

これらの用意ができていればF5キーでのデバッグ実行でビルドからのアプリケーション起動が行えるはず。

## おまけ:テンプレートからtasks.jsonを生成

Terminal -> Run Task -> cargo と選択していくと次のようなダイアログが表示される。


<img alt="create-task-entry.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67680/5069cc62-a923-a5d0-1f13-2a5007ea1644.png" width="400px">

その中の「rust: cargo build」の行の右側の歯車アイコンをクリックする。

すると ./vscode/tasks.json にCargoビルドのエントリが追加される。
【Azure認証】シークレット値を使ったトークン取得

# はじめに
業務でシークレット値を使ったトークン取得を実装したため、忘れないようにメモです。

# やりたいこと
Document Intelligenceの結果を返してくれるWebAPIを作ったので、これを他のアプリから呼び出して使いたい…
でもDocument IntelligenceにはAuthorization属性がついている…
(どちらも.NET6でつくったC#Webアプリ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/3998c581-781e-000f-28ad-58c616f144f8.png)


# アプリケーション登録
認証をかけたWebAPIをアプリ登録します。(上の図だとDocument Intelligence)
今回Azure AD B2Cの環境で作りましたが、もちろんAzure ADでも大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/cba8757c-11b1-1e15-2a76-330be628966d.png)


## シークレットの作成

認証で必要になるため、「新しいクライアントシークレット」を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/c5a16666-36cd-7362-5386-0dda0ef1b7af.png)

## APIの公開
APIの公開からアプリケーションIDのURIを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/96f289c4-825a-4c37-1509-96851633196e.png)

アプリケーションにする設定は以上です。

# コード
認証をかけたWebAPI(Document Intelligence WebAPI)を使いたいアプリ側に以下のコードを追加します。

ドメイン・テナントID・クライアントID・生成したシークレット値は👆で登録したアプリの値です。

```json:appsettings.json
  "DocumentIntelligence": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "ドメイン",
    "TenantId": "テナントID",
    "ClientId": "クライアントID",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath ": "/signout-callback-oidc",
    "ClientSecret": "生成したシークレット値"
  }
```

```c#
private async Task<string> GetTokenAsync()
{
    try
    {
        ConfidentialClientApplicationOptions applicationOptions;
        applicationOptions = new ConfidentialClientApplicationOptions();
        _configuration.Bind("DocumentIntelligence", applicationOptions);

        IConfidentialClientApplication app;
        app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(applicationOptions).Build();
        var acquireToken = app.AcquireTokenForClient(new List<string> { "https://{domain}/{clientId}/.default" });
        var authenticationResult = await acquireToken.ExecuteAsync();
        return authenticationResult.AccessToken;
    }
    catch (Exception)
    {
        throw;
    }
}
```

実行すると、これでトークンが取得できました。

# 参考
- https://learn.microsoft.com/ja-jp/entra/msal/dotnet/getting-started/instantiate-confidential-client-config-options
【本質的内容】自分の想定していた処理とは違う処理に出会したときの正しいスタンス

Androidエンジニアの上司からの課題をやっていて自分の書いたコードが自分の想定していた処理と違う処理をしていた。

具体的には、『都道府県を選択して都道府県の緯度・経度をAPIに投げると現時点での選択された都道府県の天気と気温が表示されるアプリ』といったもの。

実際に表示はされたが、リクエストボタンを連続タップするとタップした回数分TextViewに現時点での選択された都道府県の天気と気温が表示されてしまった。

この時、自分は「その事象が【なぜ(WHY)】起こっているのか？」に興味を持たずに

「【どうやったら(HOW)】その事象を止めることができるか？」ということを知りたくてググってしまった。

その一連の流れを見た上司は

『いきなり直接的な調べ方をするのではなく、まずその事象が「【なぜ(WHY)】起こっているのか？」に興味を持つべき。』

『原因調査のためにデバッグして処理の流れを追うべき。』

『原因が分かってもいないのに問題を解決しようとするのは間違っている。』

とアドバイスして頂いた。

中級者以上の方にとっては当たり前のこと過ぎて勉強にならんと思えることかもしれないが、改めて本質的だなと思ったのでメモ✍️
Amazon Redshift Serverless with AI-driven scaling and optimizations (Preview) を使ってみた

[以前の記事](https://qiita.com/s-niim/items/ae477dcd80c8e6cfd1f7)で、Redshift Serverlessを使った時、なかなか思うようにスケールしてくれないと書きました

先日re:Invent2023で"Redshift Serverless with AI-driven scaling and optimizations (Preview)"が発表されました。
そこで、早速使ってみて前回からスケールの挙動が変わったのか確認しました。

# Amazon Redshift Serverless with AI-driven scaling and optimizations について

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/amazon-redshift-serverless-ai-driven-scaling-optimizations-preview/

Monday Night Liveでの発表の箇所はこちら
https://www.youtube.com/watch?v=pJG6nmR7XxI&t=3053s


・クエリの複雑さやデータ量からワークロードパターンを学習する
・1日を通してリソースを継続的に調整し、実際のワークロードのニーズに基づいて容量を自動的かつプロアクティブに調整する
・AIによって強化された最適化と予測を導入する

うまくやってくれるようではありますが、実際にどのように動くのでしょうか、早速確認します。

# "AI-driven scaling"を有効にしたRedshift Serverlessを作成する

通常のRedshift Serverlessのコンソール画面からCreate　Workgroupすると以下のようにCapacityとしてBase Capacityを設定する画面になります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/a97e2db3-fd58-add6-f7ad-78bd3258c5db.png)

Redshift Serverlessのコンソールから上部の「Create Preview　Workgroup」選択すると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/d2bc5f86-81de-a9af-6bfd-0cb3cfb8357e.png)

以下のように、BaseCapacityではなく、「Price-performance targets」を選択する画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/f8cea679-432f-5655-63ba-ce801494177e.png)

選択できる値は、1-100まで25刻みの5通り、デフォルトで50 balancedが選択されています

またこのPreview　WorkgroupではオプションとしてRPUの最大値が設定できるようになっています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/1b437d03-f2a4-70b6-d6b0-003ef51d4754.png)


ネットワークや名前空間の設定は従来とかわらないようです。

Price-performance targets : 50
Limits ： なし
のデフォルトで作成します。

# ベンチマークの準備

以前と同様AWSのブログで紹介されているTPC-DSベースのRedshift用のDWHベンチマークをセットアップします。
https://github.com/awslabs/amazon-redshift-utils/tree/master/src/CloudDataWarehouseBenchmark/Cloud-DWB-Derived-from-TPCDS

手順は割愛しますが、1TBのケースを使います。

# ベンチマークの実行

まず10並列で試行します。
Price-performance targets : 50
Limits ： なし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/5dfa99b7-66b9-dab6-9217-48435e1f5b36.png)


30分ほど実行したところ、前回とは異なり、コンソールのPerformance Metricsで最大RPU143までスケールするのが確認できました。

前回スケールしなかった5並列も試行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245869/b60dc0c7-7285-600f-5c5f-d5d06ba440e9.png)

今度は最大RPU307までスケールして20分かからず完走してしまいました!
上のQueryRuntimeにおいて、RPUがスケールしたところで各SQLの実行時間がグッと短くなっているのがみて取れます。


# 感想・考察
今回の試行では10並列より5並列の方が大きくスケールする結果となりましたがこれが「AI」の学習結果でしょうか。便利ではありますが、ブラックボックス部分にどのくらい期待を置いてよいのか、という懸念も感じました。
「AI」の学習結果であれば、試行回数・時間で異なる結果が出る可能性はあると思いますが、少なくともAI-DrivenではないServerlessよりは積極的にスケールしようとしているように見えており、私的には望ましい挙動になってきていると感じています。

またもろもろ条件を変えて継続調査しますが、"Preview"がとれるのが待ち遠しいです!
【UiPath Apps】アプリのボタンからOrchestratorのキューにアイテムを追加

UiPath Appsのイベントでキューに追加する方法の備忘録
    アプリのボタンを押してキュートリガーを発火し、プロセスを起動させるために今回の機能を実現しました。

<H4>手順</H4>

1.  【Orchestrator】キューを作成
2.  【Orchestrator】キューのスキーマ設定
3.  【Apps】アプリ内にキューを追加
4.  【Apps】ボタンのルール作成

<H2>キューを作成</H2>

公式のドキュメントをもとにキューを作成

https://docs.uipath.com/ja/orchestrator/standalone/2023.4/user-guide/managing-queues-in-orchestrator

今回はMy Workspaceの中にDemoQueueを作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/6b7e746f-fec8-2733-42a2-b4d610e018cf.png)

<H2>キューのスキーマ設定</H2>

Apps上でキューを利用するためにはキューのスキーマを設定する必要性があります。

試しに公式のドキュメントにあるサンプルスキーマを利用してJsonファイルを作成。
Jsonファイルの作成には以下のサイトを利用しました。

https://docs.uipath.com/ja/apps/automation-cloud/latest/user-guide/referencing-a-queue-in-your-app

https://json-generator.com/

:::note info
サンプルのスキーマにはRequiredの項目があり、ここに入っている項目はキューアイテムの更新が必須になります。（どのようなエラーが出るかは下で説明しています。）
今回は"name"を削除して利用しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/c294725c-0f63-7a12-4cc1-cec23b8454a5.png)
:::


作成したschema.jsonを、キューの[スキーマ定義]>[固有データJSONスキーマ]に配置

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/67f6bf5d-e287-f8ff-a628-6e57f935567b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/fb22414d-beab-0b5f-ec95-dd9bf650822d.png)

<H2>アプリ内にキューを追加</H2>
Appsを開き、[コントロールを追加]のドロップダウンから[キュー]を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/ce03a4c8-2471-fbe3-fe49-0247195e2664.png)

テナントを選択し、作成したキューをクリックして追加。Jsonファイルで指定したスキーマが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/1b2563e0-fd26-8651-4447-b8ff208b44ff.png)


<H2>ボタンのルール作成</H2>
Apps上でアプリを作成し、適当なボタンを配置。
ボタンの[イベント]から[ルールを作成]をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/250a9e83-d326-21d0-6cf5-fa21e1a3e3bc.png)

[キューに追加]のルールを追加し、[使用するキュー]として先ほど追加したキューを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/eab8d4c6-1e28-1a3e-2d17-798e7f3564bb.png)

:::note info
キューの項目に値を追加したい場合には以下の設定も行ってください。スキーマのRequiredに設定された項目は必ず値を上書きする必要性があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/44295541-4eb7-5b79-f0cd-5a61613393f5.png)
:::


これですべて工程が完了です。

<H2>プレビュー</H2>

配置したAdd Queue Itemのボタンをクリックすると新たにアイテムが追加されたことが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/b042e235-fd6a-42b1-a60d-8e64b18b0057.png)


:::note info
もしボタンを押してキューを追加しようとしたときに、以下のエラーが出たらスキーマを定義したファイルを見直してください。

日本語「このキュー アイテムは指定の入力スキーマに違反しています。」
English　”The queue item violates the specified input schema”

私は最初にサンプルを使った時にRequiredの項目が設定されていたことに気づかずに、このエラーが出ました。Requiredに設定されている項目では、[キューに追加]のルールで項目を上書きすることが必須になります。




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575031/badccae7-9a05-7d0f-e1e9-2f1daa1e86b5.png)

:::


<H2>活用方法</H2>

今回はサンプルをもとに作成しましたが、スキーマとルールに変更を加えることで実現できることはさらに広がります。私は、ログインしているアカウントのメールアドレスとアプリ側から受け取った値をアイテムの項目に追加して、キュートリガーで呼び出したプロセスに渡し、メール送信を自動化を実現しました。
ただ、プロセスを起動するだけであればApps側のルールで実現できるので、キューを追加してキュートリガーから呼び出す必要があるかは状況によって使い分ける必要性がありそうです。
ダメな設計を振り返る〜SOLID 単一責任の原則の観点から〜

この記事は[設計こばなし Advent Calendar 2023](https://qiita.com/advent-calendar/2023/software_design_talk)の1日目です。

# 概要
過去（15年前の2008年。開発経験6年目の28歳のとき）におこなった実際の開発案件を思い出し、ダメな設計をSOLID原則の単一責任の原則の観点から振り返る。

# 開発対象
開発対象はつぎの要件のWindows Formアプリケーション

## 要件
* Windows Formアプリケーション
* 言語: C#
* 無線でターゲット装置の温度、無線強度を受信する
* 無線接続状態（接続・未接続）を表示する
* 温度、無線強度をサードパーティ製のグラフ描画ライブラリを使い折れ線グラフを描画する
* 無線通信設定（周波数帯の選択、無線伝送速度設定）
* 無線セキュリティ設定（暗号化の有無、暗号キー）
* グラフ表示更新間隔設定（100ms〜1000ms）

## ブロック図
![block_png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/89acefc8-93ac-2520-e07f-5fc367997702.png)

# ダメな設計
クラス図はつぎのとおり。

## クラス図
![class.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/aa6bf9f9-4079-a275-769a-1f63b7aaa5ac.png)

# 改善点
クラス図から設計の改善点を考えた。

## マネージャーという名の神クラス
まずマネージャーというクラス名が抽象的すぎる。
マネージャーという名のもと、管理するものがあれもこれもと集約してしまう。
神クラスになっていく兆候と思った。
クラスの責務が明確になる命名を心がけようと思った。

## 設定クラスが単一責務になっていない
設定クラスに設定項目のすべてが集約している。
単一責務の原則はクラスを変更する理由はひとつであること、と言っている。
このクラスは設定項目が分類されず集約されている。例えばグラフ表示更新間隔に関するロジックが変更になったときに設定クラス全体に変更影響が及ぶ可能性がある。
設定項目の関心が分離されていない、密結合な構造とも言えそうだ。
無線関連の設定項目（周波数帯の選択、無線伝送速度設定）、画面表示の設定項目（グラフ表示更新間隔）などと意味のあるグループに分類・分割し、それぞれを疎結合な構造にすることをこころがけようと思った。

# 感想
振り返ると当時はどう要件を実現するか実現手段に手一杯で高凝集、疎結合な設計にする、という観点まで考えることができなかった。
【PostgreSQL】postgres_fdwを使ってみよう


## fdwについて
fdwとはForeign Data Wrapperの略
外部にあるデータにアクセスできるようにするための拡張機能
他にもOracleに対して同様のことができるoracle_fdwなどもある。

![postgres_fdw_流れ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572072/cd8f7987-c018-7b3b-3d26-731fa379a7c2.png)

## postgres_fdwの使用手順

![postgres_fdw.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572072/b2cc449b-d0eb-3004-5d9e-e10ab633e35c.png)

① CREATE EXTENSION
postgres_fdwをインストールする。

``` sql
CREATE EXTENSION postgres_fdw;
```

インストールの確認
`installed_version`にバージョン番号があればインストール完了
``` sql
SELECT * FROM pg_available_extensions WHERE name IN ('postgres_fdw');
     name     | default_version | installed_version |                      comment                       
--------------+-----------------+-------------------+----------------------------------------------------
 postgres_fdw | 1.0             | 1.0               | foreign-data wrapper for remote PostgreSQL servers
(1 row)
```

② CREATE SERVER
接続する外部サーバーへの接続情報と利用するfdwを定義
``` sql
CREATE SERVER [サーバー名] FOREIGN DATA WRAPPER postgres_fdw;
```

③ CREATE USER MAPPING
リモートテーブルには、権限を持つユーザー名でアクセスする。
MAPPINGで外部サーバーとローカルのユーザーを紐づける。

``` sql
CREATE USER MAPPING FOR [ユーザー名] SERVER [サーバー名]
```

④ CREATE FOREING TABLE または IMPORT FOREIGN SCHEMA
外部テーブルを定義する。
リモートテーブルと同じ定義をしておく

- CREATE FOREING TABLEの場合

``` sql
CREATE FOREIGN TABLE table_name (
  id                     BIGINT,
  name                   VARCHAR(40)  NOT NULL,
  ~~~
) SERVER server_name
OPTIONS (table_name 'remote_tbl_name');
```

## 注意点
### 外部の統計情報が使われない
デフォルトでは外部テーブルの統計情報を利用しないため、もし利用したい場合は`use_remote_estimate`を`true`にしておくことでpostgres_fdwがリモートのEXPLAINコマンドを発行します。

### 外部の拡張機能が使われない
外部テーブルを参照する際に、デフォルトでは外部テーブルで利用されている拡張機能が使用されない。
利用する場合は以下のように`extensions`の指定をする。

``` sql
CREATE SERVER [サーバー名] FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (
  ~~~,
    extensions '利用したい拡張機能名'
);
```

## 参考
[PosgreSQLのFDWを使ってデータベース間で透過的にクエリする](https://lab.mo-t.com/blog/postgresql-fdw)
[外部データとの連携 ～FDWで様々なデータソースとつなぐ～](https://www.fujitsu.com/jp/products/software/resources/feature-stories/postgres/article-index/fdw-overview/)
Draft.jsでカーソル位置にEditorStateを挿入する

## 背景

Draft.jsで、既に入力済みであるEditorStateのカーソル位置に、別のEditorStateを挿入したい。
それを以下のコードで解決。

```react
import { EditorState, Modifier } from "draft-js";


// 挿入するテンプレートの内容を取得
const insertContent = insertEditorState.getCurrentContent();

// 現在のEditorStateを取得
const editorState = currentEditorState;
// 現在のEditorStateのcontentを取得
const currentContent = editorState.getCurrentContent();
// カーソル位置を取得
const selectionState = editorState.getSelection();

// カーソル位置にテンプレートの内容を挿入
const newContent = Modifier.replaceWithFragment(
  currentContent,
  selectionState,
  insertContent.getBlockMap()
);

// エディターの内容を再生成
const newEditorState = EditorState.push(
  editorState,
  newContent,
  "insert-characters"
);
```
eachメソッドの使い方

## `each`メソッドについて
[each](https://docs.ruby-lang.org/ja/latest/method/Hash/i/each.html)は繰り返しメソッドのひとつです。
ブロック内の処理をオブジェクトの要素分だけ繰り返すことができます。
オブジェクト内の要素の計算などもできます。
___
#### 使い方
```
オブジェクト.each do |変数|
　　実行したい処理内容
end
```
doを省略することもできます。
```
オブジェクト.each { |変数|
 実行したい処理内容
}
```
___
#### eachを使ってオブジェクト内の要素の計算をしてみよう！
```
array = [1, 2, 3]

sum = 0
array.each　do |x|
 sum += x
end

puts sum
```
```
実行結果
6
```
___
|変数|の縦棒`「|」`はバーティカルバーと呼ばれる記号です。
Rubyのputs, print, pメソッドの違い

Rubyには出力の際によく使うメソッドがあります。
どれも結果を出力するメソッドではありますが、それぞれ出力結果に違いがあります。

## ◉ [puts](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/puts.html) メソッド
渡されたオブジェクトを出力します。

**末尾には改行が追加**されるため、表示される内容は1行ごとに改行されます。

```
puts "Hello"
puts "Ruby"
puts "World"
```

-出力結果-
```
Hello
Ruby
World
```

putsメソッドの戻り値は `nil` です。

```
irb(main):001:0> puts "Hello"
Hello
=> nil
```


## ◉ [print](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/print.html) メソッド
putsメソッドとは異なり、**改行を追加しません**。
そのため、printメソッドを使うと、出力される内容は1行にまとまります。

```
print "Hello"
print "Ruby"
print "World"
```

-出力結果-
```
HelloRubyWorld
```

printメソッドの戻り値も `nil` です。

```
irb(main):001:0> print "Hello"
Hello=> nil
```


## ◉ [p](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/p.html) メソッド
渡されたオブジェクトを詳細に表示します（改行も含む）。
```
p "今日の最高気温は”
p 10
p "度です"
```

-出力結果-
```
"今日の最高気温は"
10
"度です"
```

pメソッドの戻り値は、**そのオブジェクト自体(文字列や数値型など）** です。
```
irb(main):001:0> p "Hello"
"Hello"
=> "Hello"
```

---
思い通りの結果にならない場合は、出力に使うメソッドが違う場合があります。確認してみてください。
YouTubeのAI新機能を今すぐ試せる

本記事は、[lifehacker](https://lifehacker.com/you-can-try-these-new-youtube-ai-features-right-now-1851000046)により翻訳された記事です。
![QQ20231201151433.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298092/b26ed920-6e97-625b-0892-0e3949ebba9f.png)

Googleはすべての製品にAI機能を次々と導入しています：Docs、Sheets、GmailなどのアプリにBardが登場し、数週間前にはGoogle Mapsに人工知能が登場しました。そして今、YouTubeにもAI革命が訪れようとしています。

11月7日、Googleはテスト中の2つの新しいAI YouTube機能を発表しました。これらの機能は当初、少数のテスターを対象に展開されるため、すぐに見られるかどうかはいまだにわかりません。しかし、同社はYouTube Premium加入者にもテストを開放しています。YouTubeの料金を支払っていれば、これら2つのAI機能をいち早く試すことができます。
## AIでコメントを要約する
YouTubeの新しいAI機能にアクセスすると、コメントの上に「✨テーマ」ボタンが表示されます。これをタップすると、YouTubeは自動的にコメントを「テーマ」に分類します：複数のコメントが同じようなことについて話している場合、それらは一緒に同じテーマの1つに終わるでしょう。Googleはこの機能によって、バイラル動画の扱いにくいコメント欄を読みやすくなることを期待しています。

発表の中で、グーグルはMrBeastの動画「7 Days Stranded at Sea」を例に挙げました： この動画のトピックを選択すると、「みんな鳥のブライアンが大好き」、「レーザービームをもっと動画に登場させるべき」、「潜水艦がない」、「7日間のチャレンジをもっと 」といったテーマに分類されたコメントが表示されるかもしれません。
## ChatGPTがYouTubeに登場
次に追加するのは、実に理にかなっています。YouTubeは、視聴中のビデオについて質問できるAIボットをテストしています。このようなツールは、ChatGPTそのものを含めてすでにたくさんありますが、ここでは、再生を中断することなく、ボットをプレーヤーに埋め込むという利便性が得られます。

ボットを呼び出すには、新しいAskボタンをタップし、質問してください。ビデオで何が起こっているのかチャットしたり、似たようなビデオのおすすめを聞いたり、教育的なクリップであれば、知識を試すクイズを聞いたりすることができます。
## YouTubeの新AI機能を試すには
YouTube Premiumに加入している方は、[youtube.com/new](youtube.com/new)にアクセスしてください。ここで、新しいAI機能を選ぶことができます。iOSとAndroidでのみ利用可能です。

以上、YouTubeのAI新機能について紹介しました。今流行っているAIトレンドに関心を持つ方に、AI技術で画像の背景を一瞬で透過させる[BGremover](https://bgremover.vanceai.com/ja/)を試すことをおすすめします。
ZOZOTOWN HOME面のモジュール運用を BizDev 促進させてた話

# はじめに
こんにちは。推薦基盤ブロックの宮本です。（早いもので新卒４年目です）
ZOZOTOWNの商品ページにおける「あなたにおすすめ」枠の推薦ロジックのリプレイスや、ホーム画面のユーザーパーソナライズ化に関するプロジェクトに携わっています。
最近では、開発だけでなくプロダクトマネジメント業務を担当することも多くなりました。
入社当初から「開発だけでなくPdMよりの仕事をいつかやりたい」と考えていたため、現在かなり楽しく働かせてもらってます！
最高の環境を提供してくれている弊社・弊チームに感謝です！頑張ります！

# 本記事では
本記事では、私たちHOME画面のプロダクトマネジメントチーム（自称）が2年ほどかけて築き上げた「**ZOZOTOWN HOME 面のモジュール運用を BizDev 促進させた話**」について紹介します。
この記事は私一人の成果ではなく、弊社の多くのメンバーの協力のもと執筆させていただいています。ご理解いただければ幸いです。

# 前提
それでは本題に入っていきます。
ZOZOTOWNのHOME画面には図のように「タイトル、一覧ページ、コンテンツ（商品）」の3つのパーツで構成されたモジュールが多数存在しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616917/5ccd1d68-eb29-f1cc-b4ba-044735e4dd45.png)

モジュールは大きく以下の4つに分類されます。

| モジュール名   |     説明      | 
| --- | ----------- | 
| 導線モジュール   　 | 閲覧した商品を軸としたリターゲティング系モジュール| 
| 企画モジュール　　  　　　 | トレンド商品や企画商品など企画チームが考案したモジュール |
| 広告モジュール　| 広告商品用モジュール　| 
| レコメンドモジュール　| ユーザーごとにパーソナライズされたモジュール　| 

この4つのモジュールのうち、「企画モジュール」は企画チームによって定期的に更新されます。


# 課題
企画チームにて、日々どういったモジュールがユーザーにささるのかを検討していただいているのですが、検討するにあたって以下の４つの課題がありました。

1. 良いモジュール・悪いモジュールを定量的に判断する基準がない
2. モジュールを評価するためのデータが足りていない
3. 定常的にモジュールに関する数値を確認できるレポートが存在しない
4. モジュールの運用改善の仕組みが整備できていない


# 取り組み
１〜３の課題については以下の記事で詳細に取り上げているためご覧ください。

https://techblog.zozo.com/entry/zozotown-home-data-driven

ここでは4つ目の「**モジュールの運用改善の仕組みが整備できていない**」についての取り組みを紹介します。

実際の取り組みとしては以下2点です。

- 各チームの責務の明確化と意思決定フローの整備
- 月１回のモジュール定例を開催

### 各チームの責務の明確化と意思決定フローの整備

HOME画面の改善に関わるチームは多数存在していましたが、各チームの役割が不透明でした。また「新しく企画を考えたけど、どのチームと連携すればいいのかわからない」、「この企画の最終決定者ってだれだっけ」など、企画からリリースまでの意思決定フローの整備もされていなかったため、プロジェクトを進める上でスムーズに進めることができず立ち止まることが多かったです。

そこで、まずはHOME画面のモジュール改善に関わるチームとその役割を明確にしました。具体的な役割は割愛しますが、以下の5つの担当に分けております。

- プロダクト責任者
- プロダクト推進チーム
- 企画チーム
- 開発チーム
- 分析チーム

また、以下のような意思決定のフローチャートを事前に準備しておくことで、スムーズに進めることができるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616917/2cfff5e2-24b8-35be-0d42-66023c4a1ac8.png)


### 月１回のモジュール定例を開催
正直なところ、この定例を開催し且つ価値ある定例にするためにかなり苦労しました。
課題感としては、定例を開催しても参加者のほとんどは受動的参加になってしまい、ただただプロダクト推進チームが資料をまとめて報告する会になってしまいそうであったことです。
以下が定例開催にあたって工夫した点です。

- 各チーム参加型の定例とし、実績を共有してもらう
- 企画・開発問わずモジュールに関する共有事項はすべてこの定例で共有する
- 実績共有のフォーマットを用意する
- 実績共有しやすいダッシュボードに日々改善する
- 定例がマンネリ化しないために常にアップデートし続ける
- 自分が率先して質問する（１定例１回以上）
- 良かったモジュールは企画担当者と一緒に最大限喜び、褒める


# 結果
毎月２０〜３０名ほどHOME面に関わるメンバーが参加してくれるようになりました。（初回は緊張しましたが、こんなに参加してくれるとは思ってなかったためすごく嬉しかったです）
定例を開催して最も良かった点は、**各チーム間の情報の共有がスムーズになり認識齟齬がかなり少なくなったこと**です。何気ないことですが、１ヶ月間もあれば各チームの状況はかなり変わりますので、その情報が一つの資料にまとめられていると振り返りやすく見落としづらくなります。（slackだと情報が日々流れていくため見落としやすい）
また、企画担当者から実績を共有してもらうことで今月は良かったのか悪かったのかがわかり、次はどういったことをやっていけば良いのかの共通認識が取れたことも定例を開いて良かった点です。個人的にはこれがかなり有り難く、企画担当者が持っているファッションやトレンドに関する知見などをたくさん学ばせてもらってます。


# まとめ
この記事では、モジュールの運用改善の仕組みを整備するために「各チームの責務の明確化と意思決定フローの整備」と「月１回のモジュール定例を開催」についてお話ししました。
これからもBizDev促進できるような取り組みを社内で実施していきたいと思います。
ページを読み込んだ時にグレーアウトしたボタンを表示

vue3でとあるフォームを作成。
同意するボタンにチェックが入力された時に送信できる。

## やりたいこと
フォームを表示するときに、デフォルトで送信ボタンを非活性（グレーアウト）したい。
同意するにチェックを入れた時に送信ボタンを活性（グレーアウト解除）にする。
CSSで初期値として送信ボタンをグレーアウトするは、別の制御の関係で避けたい。

## まず思いついたこと。

vueのライフサイクルを考慮して、mountedで送信ボタンをグレーアウトするようにすればいいのでは？

```vuejs
<template>
    <div>
    	<input type="checkbox" id="agree" value="agree" v-model="agree" />同意する
    </div>
    <button class="SendBtn" v-on:click="sendActionMethod" :disabled="disabledFlg">送信</button>
</template>

<script>
export default {
    props: {
        
    },
    data() {
        return {
            disabledFlg: false,
			agree: false,
        }
	},
	watch:{
    	agree: async function(){
			// 送信ボタンを活性にする
        }
	},
	mounted(){
        // ↓
        // 送信ボタンを非活性（グレーアウト）
    },
}
</script>
```

## 結果

DOMが有効になっておらず、送信ボタンのDOMが取得できないので、styleを動的に変更できない。

## 次に

this.$nextTickを使えばDOM更新後に実行されるらしい。

```vuejs
<template>
	<div>
    	<input type="checkbox" id="agree" value="agree" v-model="agree" />同意する
    </div>
    <button class="SendBtn" v-on:click="sendActionMethod" :disabled="disabledFlg">送信</button>
</template>

<script>
export default {
    props: {
        
    },
    data() {
        return {
            disabledFlg: false,
			agree: false,
        }
	},
	watch:{
		agree: async function(){
			// 送信ボタンを活性にする
        }
	},
	mounted() {
        // ↓
		this.$nextTick(() => {
			// 送信ボタンを非活性（グレーアウト）
		})
	}
}
</script>
```

## やっぱり
状況変わらず。

## もうだめだ
考え方を変えて、disableの時にグレーアウトにするだけなので、CSS側で制御するほうが良いのでは？
CSSでボタンがdisabledのときの記述を追加。

```css
<style scoped>
.SendBtn:disabled {
  background-color:gray;
}
</style>
```

## できました
無事にグレーアウトしたボタンが表示されました。
結果的にとてもシンプルな解決策でした。