OmniAICreator/Qiita-1.07M huggingface dataset:
UiPath：カスタムアクティビティとNuGet Package Explorer

UiPath：カスタムアクティビティとNuGet Package Explorer

## 皆もすなるQiitaといふものを、儂もしてみむとてするなり。

と言う訳で、こん＊＊は！はなっち！です。

★★★この記事はUiPathブログ発信チャレンジ2021サマー、7/3の記事です。★★★

今回は、作った「カスタムアクティビティ」をPUBLISHした後の注意事項を軽く記しておきます。

□「カスタムアクティビティ」は、よし！これを作ってやる！と思ってつくる場合もありましょうが、大体は現場で困ったロジックを、みんなで共有しよう！どうやって共有する？ライブラリって機能があるよ、それをやってみよう！
ってノリの場合もあるかと思います。

当然ですが、社内内部向け「カスタムアクティビティ」を社内パソコンで作った場合は、その時のUiPathのバージョンで作成され、その時のPUBLISH版数、WindowsログインIDがnupgkファイルに記録されます。

その社内向け「カスタムアクティビティ」が好評であったので、UiPath MarketPlaceで公開してみたいなと思ったら、例えば個人パソコンでも維持できたらいいなと思ったら、フォルダを一切合切移行する訳ですね。

それで、新しいカスタムアクティビティを組み込んだり、機能改善したりして、新たにPUBLISHしたとします。その時にUiPathのバージョンは、Project.json中に保有しているので、そのレベルでPUBLISHされ、新しいPUBLISH版数、個人パソコンでのWindowsログインIDが記録されます。

この新しいnupkgファイルを、社内に展開したとします。新しいnupkgを、UiPathフォルダ配下にコピーして、UiPath Studioの「パッケージを管理」画面で新しい「カスタムアクティビティ」をインストールしようとすると...
既にインストールしている「カスタムアクティビティ」と別の「カスタムアクティビティ」として認識されてしまいます。

そうなんです。「カスタムアクティビティ」は名称だけでなく、WindowsログインIDの情報も、同一かどうかの評価要素になるのです。

だったら、個人パソコンで開発する意味ないぢゃん！と思っちゃいますね。でも個人パソコンで作ったxamlをフォルダを一切合切を社内パソコンに移行するのか！

###そういう時に、「NuGet Package Explorer」が役に立ちます！

□「NuGet Package Explorer」って？
　NuGet Package Explorerは、Microsoft社が提供している、開発者ツールです。NuGet Package Explorerを使うと、GUIベースでNuGetパッケージの内容を閲覧したり、NuGetパッケージの作成作業を行ったりできます。
　以下のサイトから入手するのですが、Microsoftへのログイン、Microsoft Storeへアクセスし、そこからインストールする必要があります。無論無料です。
https://www.microsoft.com/ja-jp/p/nuget-package-explorer/9wzdncrdmdm3?activetab=pivot:overviewtab

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/9899a179-fa1d-d92c-0eef-fc4f2dead77f.png)

　NuGet Package Explorerを起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/ecf2d8bb-229e-34d5-ff7f-e4d0400f33a5.png)

Open a local package(Ctrl+O)をクリックし、個人パソコンで作成した、「カスタムアクティビティ」のnupkgを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/628e2900-d757-464e-e3c6-5b962112b809.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/c5924b18-6c86-b873-439e-a51b1a73d844.png)

メニューバーのEditをクリックし、Edit Metadataをクリックすると、各値を編集する事ができるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/aabd8d86-3e47-0711-9827-2fe1fae06c11.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/95aa83d3-e900-6f6f-a4ab-39b7b04bd15c.png)

この画面でAuthors、Ownersを、以前にPublishしたnupkgファイルのそれと同じ値にし、画面上の緑色のチェックアイコンをクリックし、仮保存します。

最終的に、メニューバーのFileをクリックし、Saveをクリックし、nupkgへの変更内容を保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/d29ee166-dc76-7af2-0c7b-1fe124511662.png)

##おわりに
いかがでした？

▼カスタムアクティビティは、版数だけでなく、Authors、Ownersが重要！
▼やっちまった！時には「NuGet Package Explorer」で編集していきましょう！


是非UiPathでのロボ開発の一助になればと思っています。

ありがとうございました！
jscpd に無視してもらいたい

## はじめに

jscpd は素晴らしいツールです

あらゆる言語で書かれたコードから重複を見つけ出して指摘してくれます

https://github.com/kucherenko/jscpd

コピー＆ペーストであちこちに重複ができてしまうと、無駄にコード量が増えるだけでなく、メンテナンス時にもあちこちを修正せねばならず、変更漏れがあればバグが発生してしまいます

重複はバグの温床、取り除かねばならないのです

以前の記事でも書きましたが、私は GitHub Actions の Super-Linter でいつもお世話になっています

https://qiita.com/RyoWakabayashi/items/6fe0e22ce7f692d67d04

jscpd に指摘されて、処理を共通化することもよくあります（つまり未熟）

しかし、それでも、どうしても見逃して貰いたいときもあるのです

## どうしても無視してほしい

ある仕組みの中で、設定情報を json ファイルに持っています

個別のものに対して、同一項目同一値の行が続き、 jscpd に怒られました

例えばこんな感じだと

```json
{
  "aaa": {
    "vvv": "VVV",
    "www": "WWW",
    "xxx": "XXX",
    "yyy": "YYY",
    "zzz": "ZZZ"
  },
  "bbb": {
    "vvv": "VVV",
    "www": "WWW",
    "xxx": "XXX",
    "yyy": "YYY",
    "zzz": "ZZZ"
  }
}
```

jscpd がこんな風に指摘してきます

```text
Clone found (json):
 - /hello.json [9:7 - 15:3] (7 lines, 97 tokens)
   /hello.json [2:7 - 8:3]

  9 | 2 | ": {
 10 | 3 |     "vvv": "VVV",
 11 | 4 |     "www": "WWW",
 12 | 5 |     "xxx": "XXX",
 13 | 6 |     "yyy": "YYY",
 14 | 7 |     "zzz": "ZZZ"
 15 | 8 |   }
```

しかし、同じ値ではあるものの、別のものなので共通化せずに別のままにしておきたいのです

[コメントで無視する箇所を指定する方法][ignore]もあります

```js
/* jscpd:ignore-start */
import lodash from 'lodash';
import React from 'react';
import {User} from './models';
import {UserService} from './services';
/* jscpd:ignore-end */
```

しかし、 json にはコメントを入れることができません

## jscpd　の設定ファイル

というわけで、設定ファイルで無視対象を指定することにしました

GitHub Actions で動かしている場合、 `.github/linters/.jscpd.json` に以下のように記載します

```json
{
  "threshold": 0,
  "reporters": [
    "consoleFull"
  ],
  "ignore": [
    "**/*.json"
  ],
  "absolute": true
}
```

これで json については jscpd の対象外にすることができました

## 最後に

Super-Linter は様々な linter を動かしてくれます

基本的には設定ファイルを指定できるようになっているので、各 linter の公式を参照して設定方法を確認しましょう

[ignore]: https://github.com/kucherenko/jscpd/blob/master/packages/jscpd/README.md#ignored-blocks
Apache Spark セットアップ

Apache Spark のセットアップ手順について。

# インストール

```sh
brew install apache-spark
```

準備が整いました。

# REPL起動

REPLでいろいろ試せます。

```sh
spark-shell
```

# 遊んでみる

README を読み込んでみるなど。

```scala
scala> val textFile = spark.read.textFile("README.md")
textFile: org.apache.spark.sql.Dataset[String] = [value: string]

scala> textFile.first
res0: String = # 俺のアパッチスパーク

textFile.count
res1: Long = 167
```

適当な DataFrame を作成してみるなど。

```scala
scala> :paste
// Entering paste mode (ctrl-D to finish)

import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder.appName(this.getClass.getName).getOrCreate()
import spark.implicits._

val people = Seq(
  ("Spark太郎", "spark-taro@example.com", "2021-01-01", 11),
  ("Spark二郎", "spark-jiro@example.com", "2021-02-02", 12),
  ("Spark三郎", "spark-saburo@example.com", "2021-03-03", 13)
).toDF("name", "email", "birthday", "age")

// Exiting paste mode, now interpreting.
...

scala> people.printSchema()
root
 |-- name: string (nullable = true)
 |-- email: string (nullable = true)
 |-- birthday: string (nullable = true)
 |-- age: integer (nullable = false)

scala> people.show(3)
+---------+--------------------+----------+---+
|     name|               email|  birthday|age|
+---------+--------------------+----------+---+
|Spark太郎|spark-taro@exampl...|2021-01-01| 11|
|Spark二郎|spark-jiro@exampl...|2021-02-02| 12|
|Spark三郎|spark-saburo@exam...|2021-03-03| 13|
+---------+--------------------+----------+---+
```

いろいろやってみてください。
おわり。
App Runnerの特徴9つまとめてみた

# はじめに
* 今回は、今年5月くらいにローンチされたAWSの新サービス、APP Runnerについて**特徴的な部分をピックアップし**まとめていきます。(ハンズオン系の記事はいっぱいあるので、、)
* また、内容は[Hello AWS App Runner](https://youtu.be/8Tmf5J2hXT8)から抜粋し、まとめているので詳細を知りたい人はこちらがオススメです。

# App Runner
## 特徴その１
* ECRにコンテナイメージをpushするか、GitHubにソースコードをpushすると、それを検知し、App Runnerがいい感じにコンテナをデプロイしてくれる。(code buildなどがいらないし、VPCとかセキュリティグループの設定もいらない)

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/0b5ae0be-c34a-3360-d432-aff295929f2e.png)

## 特徴その2
* コンテナがデプロイされるとApp RunnerがマネージドのLBを設定してくれる。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/e2ee11f4-51d1-d508-ddbe-b5fc4e72d3af.png)

## 特徴その3
* 今までは、Webアプリを仕様に合わせデプロイするときに、コンテナ管理とかVPC周り、ALB,NLBとかscaling, 自動化するならcodebuildとかを組み合わせていたものが、App Runnerがこれらをまとめて(**隠蔽して**)提供してくれる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/3be8a840-0c8c-d909-5e24-2b5e6e1aa394.png" width="500px">

## 特徴その4
* App Runnerのデプロイだと、VPCは作っていなく、内部的にはAPP Runnerのサービス側にはVPCがあるが、実際にApp Runnerを使う側はVPCを気にしなくて良い。
=> VPCのQuota制限はなし。

## 特徴その5
* auto scaling設定は、最小のインスタンス数は１（0にはできない）
=> なぜかというと、コールドスタートを防ぎたいから
(どうやら、Lambdaで利用者がコールドスタートを防ぐことを望む声が多かったっぽい)
ただ、課金は1インスタンス分フルフルで請求はされない。

* auto scaling設定でのRequest数の意味は、レスポンス返し待ちの数(リクエストバッファ) - 処理待ちのrequest数を指している。


## 特徴その6
* ~~(2021/07/03時点)の**制約事情として、VPC内のリソースにアクセスできない。**~~  
~~なので、例えばVPC内にあるRDSに接続できなかったりする。~~  
https://github.com/aws/apprunner-roadmap/issues/1  
~~=> ただ、App Runnerのload mapにこの制約については載っているので近々この制約がなくなる可能性もある。~~  

(2022/2/8)にApp RunnerがVPC対応したので、これで上記の制約は解消されました。
https://docs.aws.amazon.com/apprunner/latest/relnotes/release-2022-02-08-vpc.html

対応内容についても記事にしておきました。
https://qiita.com/yoshii0110/items/936c910c33a1a3e78f63

## 特徴その7
* 現状、Waf対応がされていない。  
https://github.com/aws/apprunner-roadmap/issues/58  
=> こちらもApp Runnerの要望として上がっているので、もしかするといつの日かサポートしてくれるかもしれない。

## 特徴その8
* App Runnerは裏側でFargateを使用している。ので、リージョンに属するいずれかのAZ上にインスタンスが起動される、かつFargateの為、DefaultでAZ分散の形でコンテナをデプロイするので、App Runnerも複数台起動するとAZ分散がされる、、かも?(正式には、AZ分散されるかは公表していない。)

## 特徴その9
* ヘルスチェック
アプリケーションのポートとヘルスチェックのポートは一緒になる

## 特徴その10
* App Runnerの間は、半角スペース


# 参考

https://aws.amazon.com/jp/blogs/containers/introducing-aws-app-runner/

https://twitter.com/toricls/status/1410910287255932930?s=20*
UiPath：カスタムアクティビティとDEBUG

UiPath：カスタムアクティビティとDEBUG

## 皆もすなるQiitaといふものを、儂もしてみむとてするなり。

と言う訳で、こん＊＊は！はなっち！です。

★★★この記事はUiPathブログ発信チャレンジ2021サマー、7/3の記事です。★★★

今回は、作った「カスタムアクティビティ」をどうテストしていくかをまとめてみました！

無論ですが、ロボットはテストをして、必要な機能が実装されているかを確認します。
テストには、以下の二つのカテゴリがあります。
１）xaml単体としてのテスト
２）PUBLISHしたアクティビティパッケージとしてのテスト

１）xaml単体としてのテストは、XAMLファイルをUiPath Stusioで開いて、その状態でテストを行う方法です。
　カスタムアクティビティは、その多くは引数を受け取り、処理をして、引数に返すようなものでしょう。
　これをテストするには、カスタムアクティビティのxaml単体をINVOKEするためのXAMLファイルを用意します。例えば"T_カスタムアクティビティの名前"のようなシーケンスでいいでしょう。

　このシーケンスのxamlファイルは、「ドライバxaml」と言い、このファイルには、「ワークフロー ファイルを呼び出し」アクティビティを配します。
　カスタムアクティビティの引数に値を設定するのは、本来はパラメタから設定できるようになるのですが、ここの時点では「ワークフロー ファイルを呼び出し」アクティビティの「引数をインポート」から設定します。
　無論、呼び出す前に「代入アクティビティ」などを使って、必要な変数に値を設定します。変数を使わずに、直接値を記述してもいいですが、試験のバタエティのたびに値を見直さないといけませんので、おすすめしません。
　
　余談ですが、カスタムアクティビティなどの戻り値を、固定的に返すだけのxamlファイルを、「スタブxaml」と言います。カスタムアクティビティを使用した後の処理を試験する場合で、カスタムアクティビティから固定的な値を返すだけの機能を持ちます。
　カスタムアクティビティを作っている側が使用するのが「ドライバxaml」、カスタムアクティビィを使う側が使用するのが「スタブxaml」ですね。

２）PUBLISHしたアクティビティパッケージとしてのテストは、実際にカスタムアクティビティとしてPUBLISHし、できたnupkgファイルをインストールしてテストを行う方法です。
　単体テストの場合は、同一プロセスの中でテストができましたが、nupkgファイルでのテストの場合、同一プロセスでPUBLISHしたnupkgを読み込もうとすると内部矛盾を起こすので、テストできません。
　新たなプロセス、例えばT_カスタムアクティビティパッケージ名を作成し、改めて「パッケージを管理」でPUBLISHしたnupkgをインストールします。
　
　xaml単体でのテストは実施されているので、ここでのテストは以下の事に注目します。
　・カスタムアクティビティをドラッグ＆ドロップした時に名称が区別できるようになっているか
　・階層が正しく展開され、その配下にカスタムアクティビティが存在しているか
　・余計なxamlファイルが見えていないか（「パブリッシュから除外」として設定すると、存在はしますが、非表示となります）
　・必須、任意の引数が正しく設定されているか

　カスタムアクティビティをPUBLISHしてからテストをする場合に、Orchestratorではなく、ローカルフォルダ、例えばUiPathフォルダ配下に対して行います。
　PUBLISHして、試験をするために「パッケージを管理」画面からインストールし、テストします。

　そのテストのあとに、ちょっとした誤記などがあった場合、同じバージョンでPUBLISHしたくなりますが、これは絶対ダメです。
　同じバージョンでPUBLISHできますが、既にインストールされていると認識され、新しいカスタムアクティビティが展開されません。
　ですので、必ず現状のバージョンより上のバージョンにする、あるいは特に意識しなければ、UiPath Studioが採番してくれます。

※バージョンの話
　UiPathでのバージョンは、セマンティック バージョンで管理されます。
　セマンティック バージョン管理スキームは、Major.Minor.Patch[-Suffix] という形式です。ここで、
　・Major は、メジャーバージョンです。
　・Minor は、マイナーバージョンです。
　・Patch は、パッチバージョンです。
　・-Suffix (任意) は、パッチバージョンに続いて、ハイフンの後にドットで区切られた識別子が付加されます。これは、プレリリースバージョンを表します。

　以前は、レガシーバージョンで管理されていましたが、いつの日か、変わっています。
　プロジェクトにより生成されるレガシーのバージョン番号の形式は、「M.m.bbbb.rrrrr」です。それぞれの意味は次のとおりです。
　・M はメジャーバージョンです。
　・m はマイナーバージョンです。
　・bbbb はビルドバージョンです。
　・rrrrr はリビジョンバージョンです。
　メジャーバージョンとマイナーバージョンは project.json ファイルでも編集できますが、ビルドバージョンとリビジョンバージョンはアルゴリズムに従って生成されます。ビルドの値は、2000年1月1日からの経過日数です。リビジョンの値は、現時点からリリース時までに経過した秒数です。この結果を2で割って、リビジョン番号が65535を超えないようにします。

　詳しくは以下のサイトをご覧ください。
https://docs.uipath.com/studio/lang-ja/docs/about-automation-projects

##おわりに
いかがでした？

▼ロボットであろうが、カスタムアクティビティであろうが、テストは重要です。
▼テストの方法には、単体xamlで実施する方法の次に、PUBLISHしたnupkgをインストールして、本番利用さながらのテストで実施する方法があります。
▼テストを実施したあと、再度PUBLISHする場合には、現在のバージョンより上のバージョンにしなければなりません。


是非UiPathでのロボ開発の一助になればと思っています。

ありがとうございました！
【スプレッドシート】セルの参照を変数化する

対象の行・列をGASやら関数でゴニョゴニョ計算して求めた結果`A2`という文字列が得られたとして、そのセルを参照するやり方のメモ。

## INDIRECT関数を使う

INDIRECT関数の第一引数にセル参照の文字列を指定すると参照できます。

`=INDIRECT("A2")`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/45431259-28be-9e73-b5f6-d8dfc8999965.png)

## 行・列が数値の場合はADDRESS関数を併用する

ゴニョゴニョ計算して求めた結果が`21`（2行・1列）みたいな場合、ADDRESS関数を併用します。
ADDRESSでセル参照文字列を取得してINDIRECTに渡します。

`=INDIRECT(ADDRESS(2, 1))`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/b16ad292-4e9b-09d1-6bf9-046d2304f1d1.png)

## 参考

https://support.google.com/docs/answer/3093377?hl=ja

https://support.google.com/docs/answer/3093308?hl=ja
Postgresql＆Pgadmin4インストール手順とコマンド

#Homebrew インストール
`$ brew install postgresql`

#文字コードUTF-8でデータベースの初期化
`$ initdb /usr/local/var/postgres -E utf8`

#PostgresSQlサーバー起動
`$ postgres -D /usr/local/var/postgres`

###ここでPermission deniedのエラーが出た

`FATAL:  could not create lock file "/tmp/.s.PGSQL.5432.lock": Permission denied`

#権限を与える
`sudo chmod 1777 /private/tmp`
__エラー解消された__

#再びサーバー起動
`$ postgres -D /usr/local/var/postgres`
__動いた__

#Pgadmin4をインストール
`brew install pgadmin4 --cask`

__Launchpadにアプリが表示__
![スクリーンショット 2021-07-02 23.53.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492712/16d798e2-837f-fdb5-803f-24cd27f26a36.png)

>参考記事

https://qiita.com/_daisuke/items/13996621cf51f835494b

https://qiita.com/daishin0413/items/f525ed3e4c1d5ac34a51

https://qiita.com/takuyanin/items/18590600d077df707923

思いの外エラーで手間取ったので、備忘録として作成
ASP.NET学習のまとめ(2)Controllerの操作や処理その他

次はModelに続いて、Controllerでの大まかな操作など学んだことをまとめてみた。
なお、Entity Frameworkを使用している前提で書く。
※いろいろ触ってみて気づいたことや認識に違いがあれば、都度修正していく予定。


<h2>全体の特徴</h2>
○クラス名は「Controller」クラスを継承している。
○クラスを作成する際に「Entity Frameworkを使用した、ビューがあるMVCコントローラー」を指定して作ると、CRUD操作のメソッドがデフォルトでクラスの中に自動で作成される。
○クラス内のどこでも参照できるインスタンスとして、DBのデータを読み込むコンテキストクラスが定義してある。
○ログイン認証前でも操作できるようにするには、[AllowAnonymous]アノテーションをクラスの1行上に記載する。
○ログイン認証後のみ、操作できるようにするには、[Authorize]アノテーションをクラスの1行上に記載する。

<h2>CRUD操作を扱うメソッドの中身</h2>
<h3>CREATE</h3>
○Http経由でのみ処理させるために、[HttpPost]アノテーションがメソッドの1行上に書かれている。
○引数で、Bindメソッドが使われており、Includeプロパティに結びつける引数が指定されている
(これをすることで、POSTアクションでデータが渡ってきたときに、そのデータの中のプロパティとそのPOSTアクションの引数に自動的に結びつけてくれる)。
○「ModelState.IsValid」はPOSTの処理が走ったときに、渡ってきたデータが正常どうかを判定している。
これがtrueだと

~~~csharp
db.(テーブル名).Add(引数);　// 引数をDBに追加
db.SaveChange(); // 変更内容を反映させる
RedirectToAction(); // 処理が成功した後に遷移したいページに遷移
~~~

falseだと、

~~~csharp
View(引数)
~~~
の記載があるだけ。

<h3>READ</h3>

~~~csharp
View(db.(クラス名).ToList())
~~~
DBに登録されているテーブルをただ返すだけ。

<h3>UPDATE</h3>
(表示)
UPDATEのページを表示する際に、UPDATEするデータを取得する必要があるので、

~~~csharp
db.(クラス名).Find(id)
~~~
で該当データを検索する。

検索したデータがあれば、

~~~csharp
View(引数)
~~~
で該当データがViewメソッドでUPDATEするデータを表示するViewページに渡される。

なければ、HttpNotFound()でエラーが返される。

(処理)
処理の流れは基本的にCREATEと同じ。

<h3>DELETE</h3>
(表示)
DELETEのページを表示する際に、DELETEするデータを取得する必要があるので、

~~~csharp
db.(クラス名).Find(id)
~~~
で該当データを検索する。

検索したデータがあれば、

~~~csharp
View(引数)
~~~
で該当データがViewメソッドでUPDATEするデータを表示するViewページに渡される。

なければ、HttpNotFound()でエラーが返される。

(処理)

~~~csharp
db.(クラス名).Find(id)
~~~
で該当データを検索した後に、

~~~csharp
db.(クラス名).Remove(id); // 該当データをDBから削除
db.SaveChange();  // 変更内容を反映させる
RedirectToAction(); // 処理が成功した後に遷移したいページに遷移
~~~
という流れになっている。


<h2>参考記事</h2>
https://www.atmarkit.co.jp/fdotnet/aspnetmvc3/aspnetmvc3_04/aspnetmvc3_04_01.html

http://yan-note.blogspot.com/2016/12/aspnet-mvc-13.html
FreeBSDでルーターを作る その2：IPv4 over IPv6(OCNバーチャルコネクト)対応失敗編

# FreeBSDでIPv4 over IPv6(OCNバーチャルコネクト)対応できるか？

えー、結論から言うと失敗しました。
タイトルは失敗編と書いてありますが、成功編の執筆予定はありません...

失敗したのをそのまま書くのも何ですが、前回続きを書くと書いた手前、とりあえず試したことを書いておきます。どなたかが完成させてくれることを期待して...

前回の記事 (https://qiita.com/yshdsnd/items/51db16214dc8202baa71) で、IPv6ブリッジ+PPPoEのルーターは作れており、実際この設定でしばらく運用していました。
そして、FreeBSDは長らくMAP-Eのポートをうまいこと割り当てる方法がなかったのですが、先日12.2-STABLE,13.0-STABLEのpfにMAP-E対応が入ったことからこれを使ってOCNバーチャルコネクト対応できるのでは？と考えたのがそもそもの始まりです。

# とりあえず、やったこと

まずは、IPv4 over IPv6のトンネルを作ることから始めます。
現在の環境は、光電話なしの契約なので、IPv6に関してはIPoEでRAが流れてきてアドレスが割り当てられるので、IPv6での通信は簡単にできます。

そこで、トンネル先となるBE(Border Relay)のアドレスに向けて、トンネルを作成します。ここではgifデバイスを使います。

BEのアドレスは、以下のページで調べることができます。
割り当てられているIPv6アドレスのプレフィックスを入力すると、自分のアドレス情報が出てきます。本来は自分で計算するかAPIで入手するみたいですが、そのあたりの情報があんまりないので、誰が作ったか謎なのですが、以下のサイトを使わせてもらいます。

http://ipv4.web.fc2.com/map-e.html

![スクリーンショット 2021-07-02 230630_修正.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373056/94f8560a-e1ec-659e-27f6-febca091ae42.png)

peeraddrがBEのアドレス、CEがルーターのアドレスです。

<pre>
# ifconfig bridge0 inet6 2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx prefixlen 64 alias
# ifconfig gif0 create
# ifconfig gif0 inet6 tunnel &lt;CEアドレス&gt; &lt;BRアドレス&gt; prefixlen 64 mtu 1460
# ifconfig gif0 inet &lt;CEのv4アドレス&gt; &lt;CEのv4アドレス&gt; netmask 255.255.255.255 alias
# route add default -iface gif0
</pre>

トンネル自体はこれでOKのはずですが...そもそもgifって単純にアドレスaliasで付けられないので、inet &lt;CEのv4アドレス&gt; &lt;CEのv4アドレス&gt; という指定の仕方をしたのですが、そもそもこれ同じアドレスへのトンネルという状態になって正しくない気がします...

MAP-Eのポートの設定ですが、MAP-E対応は2021/5月頭に-STABLEに入ったので、今のところ、-RELEASE版では対応していません。使う場合は-STABLEにアップデートしましょう。

設定方法ですが、man pf.confによると、

     map-e-portset ⟨psid-offset⟩ / ⟨psid-len⟩ / ⟨psid⟩
     With nat rules, the map-e-portset option enables the source port
     translation of MAP-E (RFC 7597) Customer Edge.  In order to make
     the host act as a MAP-E Customer Edge, setting up a tunneling
     interface and pass rules for encapsulated packets are required in
     addition to the map-e-portset nat rule.

     For example:

     nat on $gif_mape_if from $int_if:network to any \
               -> $ipv4_mape_src map-e-portset 6/8/0x34

     sets PSID offset 6, PSID length 8, PSID 0x34.

という事らしいので、アドレス先ほどのアドレス計算ページの結果から以下の様にすれば良さそうです。

<pre>
ext_if="gif0"
...
nat on $ext_if from $int_if:network to any -> ($ext_if) map-e-portset 6/6/0x13
</pre>

というところまでやってみたのですが、そもそもトンネルがうまくいってないようで、外部のIPv4アドレスにはpingが通りませんでした。

色々調べたけど分からず、これ調べている間、IPv4アドレス使えなくて色々困ったので、もう諦めて市販ルーターを買ってしまいましたw

さようなら、FreeBSDの自作ルーター。20年位ずっと歴代FreeBSDでルーター作ってきたけど、ついに市販品の軍門に降りました。
これで私も一般人の仲間入りです。
\# 買ったのはYamahaのルーターなので、やっぱり逸般人じゃないかという話もある。

# おわりに

私は諦めてしまいましたが、こうすればうまくいったよという人がいれば教えて欲しいです。

 \#よく考えたらpfでgif0の通信塞がったままの気がする...
【スプレッドシート】日付から月だけ取得する関数


## TEXT関数でOK

`TEXT`関数を使います。
第一引数に対象の日付を指定し、第二引数に表示形式を指定します。

## ゼロパディング無しの月を取得したい

`=TEXT(A1,"m")`でゼロパディング無しの月を取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/f716c89e-8495-f66c-a06c-df7d34addb4a.png)

## ゼロパディング有りの月を取得したい

`=TEXT(A1,"mm")`でゼロパディング有りの月を取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/bdc84f23-188d-0dc5-1130-9af50ed4421c.png)

## 他にも様々な表示形式がある

以下の表示形式がサポートされています。

> https://support.google.com/docs/answer/3094139?hl=ja
>
> - TEXT 関数は、次の日付/時刻形式をサポートしています。
>   - d - 1 桁または 2 桁の日付。
>   - dd - 2 桁の日付。
>   - ddd - 曜日の省略名。
>   - dddd - 曜日の正式名。
>   - m - 1 桁か 2 桁の月名、または時刻の分。時刻の時と秒とともにこのコードを指定していない場合は、月名が使用されます。
>   - mm - 2 桁の月名、または時刻の分。時刻の時と秒とともにこのコードを指定していない場合は、月名が使用されます。
>   - mmm - 月の省略名。
>   - mmmm - 月の正式名。
>   - mmmmm - 月名の最初の文字。
>   - YY - 2 桁の年。
>   - yyyy - 4 桁の年。
>   - HH - 24 時間制の時。
>   - hh - 12 時間制の時。
>   - ss - 時刻の秒。
>   - ss.000 - 時刻のミリ秒。
>   - AM/PM - 12 時間制の時間表示で、時刻に基づいて AM（午前）、PM（午後）を表示します。

## 参考

https://support.google.com/docs/answer/3094139?hl=ja
残プロ　第-31回　~Bootstrapでセレクトフォームを使う~

# セレクト部品とは

![select_widget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1578927/2da27f59-28d5-176d-b34b-0359fa464921.png)


こんなやつ.
コードは下記のようになります．画像の例ではtableの中に配置しています．

~~~ html:
<div class="form-group">
  <select class="form-control">
    <option>-</option>
    <option>Edit</option>
    <option>Delete</option>
  </select>
</div>
~~~
【AWS CLI】オプションにURLを指定した際に、received non 200 status code of  XXXと表示される場合の対処法

## はじめに

掲題の通り、AWS CLI の v1 にて、オプションに URL を指定した際に、`received non 200 status code of XXX` というエラーが表示されることがあります。
その原因と対処法をまとめました。

```shell
# create-custom-verification-email-template 実行時に遭遇しました
$ aws ses create-custom-verification-email-template \
     --template-name "dummy" \
     --from-email-address "dummy@example.co.jp" \
     --template-subject "dummy" \
     --template-content "content" \
     --success-redirection-url "https://www.example.com/verifysuccess" \
     --failure-redirection-url "https://www.example.com/verifyfailure"

Error parsing parameter '--success-redirection-url': Unable to retrieve https://www.example.com/verifysuccess: received non 200 status code of 404
```

## 環境

```shell
$ aws --version
aws-cli/1.19.104 Python/3.7.10 Linux/4.14.225-168.357.amzn2.x86_64 exec-env/CloudShell botocore/1.20.104

```

## 原因

AWS CLI の v1 では、`http://`, `https://` でパラメータが始まる場合、その URL からロードしたファイルを、URL の代わりのパラメータとして扱います。

> AWS CLI は、http:// または https:// の URL でインターネット上にホストされているファイルからのパラメーターのロードもサポートしています。次の例では、Amazon S3 バケットに格納されているファイルを参照しています。これにより、任意のコンピュータからパラメータファイルにアクセスできますが、コンテナが公開されている必要があります。

参考: [ファイルから AWS CLI パラメーターをロードする - AWS コマンドラインインターフェイス](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-parameters-file.html#cli-usage-parameters-file-remote)

[その時、200 以外のステータスコード が返却された場合、取得に失敗したと判断してエラーを出力します。](https://github.com/aws/aws-cli/pull/3590/files#diff-5801e3f2a4a97b1adc4fea185502e4c11d3b914e0842c6ca2dd57eda5c277e68L235)
これが `received non 200 status code of XXX` というエラーになります。

## 対処法

### 1. `~/.aws/credentials` に `cli_follow_urlparam = false` を追記する

以下にあるように、`cli_follow_urlparam = false`を指定すると、`http://`, `https://` で始まる文字列を通常の文字列パラメーターとして処理するようになります。

> With this configured the CLI will follow any string parameters that start with https:// or http:// will be fetched, and the downloaded content will be used as the parameter instead.
> false - The CLI will not treat strings prefixed with https:// or http:// any differently than normal string parameters.

参考: [AWS CLI Configuration Variables — AWS CLI 1.19.104 Command Reference](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#general-options)

逆にいえば、こちらのパラメータがデフォルトで`true`となっているため、上記の処理が実行されます。

### 2. `--cli-input-json`オプションを使用し、json 形式でパラメータを渡す

json 形式で値を渡すことで、上記の処理をスルーすることができます。

```shell
$ echo '{
    "TemplateName": "dummy",
    "FromEmailAddress": "dummy@example.co.jp",
    "TemplateSubject": "dummy",
    "TemplateContent": "content",
    "SuccessRedirectionURL": "https://www.example.com/verifysuccess",
    "FailureRedirectionURL": "https://www.example.com/verifysuccess"
}' > dummy.json
$ aws ses create-custom-verification-email-template --cli-input-json file://dummy.json
# エラーなし
```

### 3. AWS CLI の v2 を使用する

以下のように、AWS CLI v2 では`cli_follow_urlparam`が固定で false になっています。
そのため、設定を変更せずとも`http://`, `https://` で始まる文字列を通常の文字列パラメーターとして処理できます。

> AWS CLI version 2: false. In the AWS CLI version 2 you can't change this setting.

参考: [cli_follow_urlparam - AWS SDKs and Tools](https://docs.aws.amazon.com/ja_jp/sdkref/latest/guide/setting-global-cli_follow_urlparam.html)
[Windows11] TPMの認証を解除したISOファイルをダウンロードして作る。

#はじめに
最近ISOファイルを公式が配布されるようになり、ある程度知識がある人は様々なPCで試せるようになりました。
しかし、ISOファイルのダウンロードの仕方がかなり特殊であったり、Windows11のサポートされた機種がかなり限定されていて、自分の使っている、ノートPCで使えなかったため、その対策を考えていきたいと考える。

<B>2021年7月6日現在のバージョンでは最低構成のCPUが少し下がり、Intelでは、第４世代のCPUならTPM対応していないマザーボードでも、おそらくそのままInsaider Previweでも動くようになったと思われるため、大体のパソコンではこのやり方は必要ではなくなったのかもしれない。</B>

#Windows11のISOファイルをダウンロードするためのbatファイルをダウンロードする
直接ISOファイルをダウンロードできないため、コンソールを使いダウンロードを行う。
そのために、batファイルダウンロードする。
ダウンロード先を乗せると、直URLになってしまうため、その紹介しているYoutubeのURLを貼ろうと思う。

https://www.youtube.com/watch?v=vTCAHBQ5v9s&t=0s

#batファイルを変更し、ダウンロードする言語をjpに変更する
ダウンロードしたファイルは言語設定がen-usになっているため、ja-jpに変更する。
24行目のen-usをja-jpに変えるだけで大丈夫。

```
============一部抜粋===========
01: @echo off
02: 
03: rem script:	   @rgadguard and abbodi406
04: 
05: setlocal EnableExtensions
06: setlocal EnableDelayedExpansion
07: set "params=%*"
08: cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 09: 1>nul 2>nul || (  echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && exit /B )
10: 
11: if not exist "%cd%\bin\wimlib-imagex.exe" goto :ConvertLite
12: 
13: :Info
14: for /f "tokens=3 delims=: " %%b in ('dism /english /online /Get-Intl ^| find /i "System locale"') do (
15: 	call bin\lang-uup.cmd -en
16: 	if /i %%b==ru-RU call bin\lang-uup.cmd -ru
17: )
18: set "file_main=%~n0"
29: set "aria2=bin\aria2c.exe"
20: set "rand=%random%"
21: set "down_temp=%rand%\down"
22: set "aria2Script=%rand%\aria2_script.txt"
23: set "BuildInfo=22000.51"
=================================================================
24: set "lang_def=ja-jp"
=================================================================
25: set "destDir=uup/!BuildInfo!/!lang_def!/amd64"
26: set "updateId=e012464d-2c1e-4c36-9051-caa99ff6f213"
27: mkdir %rand%
```

#ダウンロードをする
実際にダウンロードをするときは、batファイルを適当な、空っぽをのフォルダーに入れてその中で実行をすると良い。
その後、batファイルをダブルクリック等で実行してもらえればダウンロードが開始する。

ダウンロードには軽く一時間ほどかかるので気長に待つといいと思う。

#ダウンロードしたisoファイルにTPMを判断させないようにする
windows11のISOファイルをマウントして、デスクトップ等の新しいフォルダーにマウントした中身をコピーをする。

windows10のISOファイルをマウントして、デスクトップ等の新しいフォルダーにマウントした中身をコピーする。

windows11の方の「sources」の中にある「Install.wim」をわかりやすいところにコピーする。

「Install.wim」の拡張子を変えて、「Install.esd」に変更する。

windows10の方の「sources」の中にある「Install.esd」を消して、windows11からコピーをしてきた「Install.esd」を入れる。

「sources」の中身を変えた、windows10の方のファイルをそのままISO化する。アプリ等は何でもいいですので、参考にした記事を見ながらやってほしい。

http://office-qa.com/win/win49.htm

ISO化したら、rufasでもなんでもいいので、USBにISOを書き込む。

あとは通常のwindowsのインストール方法を行っていけばwindows11がインストールできる。

#参考文献

https://www.youtube.com/watch?v=FzjWn0TILxc&t=103s

https://www.youtube.com/watch?v=vTCAHBQ5v9s&t=0s

http://office-qa.com/win/win49.htm

https://japanese.engadget.com/windows11-not-support-intel-mac-051551370.html
pathを通すとは。

環境構築をする際に、pathを通すことでコマンドを使用できるようにするというような記述をよく見かけますが、ぼんやりとした理解しかできていなかったので備忘録としてまとめます。

##pathを通すとは
コマンド検索pathを追加すること。
つまり実行したいコマンドの実体が定義されているファイルの場所を追加してあげること。

```
例
ls

/bin/ls
```
例えば上記のコマンドの結果は同じです。つまり```ls```は```/bin配下のlsファイル```を実行しています。ではなぜ```ls```と打つだけで/bin/lsが実行されるのか。

<strong>それはlsと打った際に、指定されたディレクトリからlsと名付けられたファイルが検索され実行されたからです。</strong>


それでは検索対象となったディレクトリを見てみましょう。

```
echo $PATH 
/usr/local/opt/mysql@5.7/bin:/Users/kunihondai/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
　
```echo $PATH```によって```:```で区切られたpathが表示されました。
今回は見やすくするため下記のように、並べてみました。

```
/usr/local/opt/mysql@5.7/bin
/Users/kunihondai/.rbenv/shims
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
```
7個のディレクトリのパスが表示されていますね。
<strong>これが検索対象のディレクトリです。</strong>

つまり```ls```コマンドが打たれると、```echo &PATH```で表示されるパスから、指定されたディレクト内をそれぞれ探索する。そしてlsファイルが/bin配下にて、存在が確認され実行されます。

```
echo $PATH 
/usr/local/opt/mysql@5.7/bin:/Users/namae/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

ls #上記のpathから、左から順番にそれぞれのディレクトの中でlsファイルを探索する。存在すれば実行される。

```
<strong>つまりpathを通すとは```echo $PATH```で表示されるpathに、使用したいコマンドの実体(ファイル)が存在するディレクトリのpathを追加してあげることです。</strong>

今回私の環境での```echo $PATH```の結果を見てみると先頭に```/usr/local/opt/mysql@5.7/bin```があります。

これはpathから見て取れるように、私がmysql5.7をインストールした際にmysqlのコマンドを使用するためにpathを通したからです。

##終わりに
pathを通す意味がよく分かっていませんでしたが、理解できると簡単ですね!
次回は実際にpathの通し方を解説します!
LaravelでSassを反映させる方法と反映されない時の対処

# はじめに
LaravelでSassを導入する流れと、実際に私がbladeに反映されずつまづいて改善した流れを書いていきます！　　
※既に`npm install`が実行された後の内容です。
### この記事の内容
- Sass導入の流れ
- Sassが反映されない時の対処
 
### 環境
- Docker
- Laravel8.48.2

# Sass導入手順
- webpack.mix.jsの確認
 
最初は以下のようなコードになっているかと思います。  

```Javascript

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps();
```
`resources/sass/app.scss`が`public/css`のapp.cssに変換されるってわけですね。  

- bladeファイルのhead内でlinkタグを確認

```html
    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
```
CSSは`css/app.css`を読み込みにいくよってわけですね。

- sass/app.scssを確認

最初は以下のようなコードになっていると思います。

```scss
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';
```
ここにscssを書いていけばコンパイル後にcssとして反映されますが、画面ごとのコンポーネントに分けると気持ちいいですね。
たとえば、top画面のscssを作りたいのであれば、sassディレクトリに`_top.scss`というようにアンダーバーから始まるファイル名でscssファイルを作りましょう。
そして忘れてはいけないのは、app.scssにインポートしてあげてください。ちなみにアンダーバーはいりませぬ。
すると以下のようになるはずです。

```scss
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';
@import 'top';

// Bootstrap
@import '~bootstrap/scss/bootstrap';
```
さて、それではコンパイルしましょう〜！
- `npm run dev`の実行
webpack.mix.jsのあるディレクトリで`npm run dev`を実行すれば、public/app.cssにコンパイルされているはずです。
画面も見て、cssが反映されているか確認しましょう。
ちなみに`npm run watch`で自動コンパイルしてくれるので便利。ページ更新するだけで確認が済みます。

しかし私の場合、コンパイルはされているがcssが反映されてないという状態に・・・

# CSSが反映されない時の対処
- キャッシュをクリアする

`npm cache clean -f`でキャッシュをクリアしてみましょう。

- デベロッパーツールでlinkタグを見て、ソースを確認
app.cssの内容が確認できると思いますが、ちゃんとコンパイルされてるでしょうか？
私の場合、ここで反映されていないことが分かったので、リロードして解消しました。

- `version()`をwebpack.mix.jsに足してあげる
※私の場合、別件エラーが出てコンパイルしなかったので`.vue()`付け足して解消してます。

```javascript
mix.js('resources/js/app.js', 'public/js').vue()
    .sass('resources/sass/app.scss', 'public/css')
    .version()
    .sourceMaps();
```

これでキャッシュを自動更新するようになります。

# おわりに
sassが反映されない原因は色々ありそうですが、この記事で解消できれば幸いでございます。
以上
javascript 画像プレビュー機能の実装

Every Qiita #3
のんびり独学初学者の毎日投稿チャレンジ 3日目
今回は・・・
自作コミュニティアプリ開発で画像のプレビュー表示を実装した時の備忘録です。

##実装の概要
1. 画像が選択された際にイベントの発火
1. ファイルデータを取得しsrc属性urlを生成
1. imgタグを生成し任意の場所に表示
  
上記の流れで実装していこうと思います！

##ソースコード
```javascript:img.js
 const question_img = document.getElementById('file_upload');
 question_img.addEventListener("change", function(){
   // question_img要素のデータを取得
   const reader = this.files[0];
   const img_url = window.URL.createObjectURL(reader);
   console.log(img_url);
   // エレメントを生成しimg_view要素に挿入
   const img_view = document.getElementById('img_view');
   const img_element = document.createElement('img');
   img_element.src = img_url;
   img_view.appendChild(img_element);
   console.log(img_element);
   console.log(img_view);
 });

```

```html:index.html
<input type="file" accept="image/*" name="upload_img" class="ml-4" id="file_upload" multiple="multiple">
<!-- 画像をimg要素で出力 -->
<div class="img_view" id="img_view"></div>
```

##ソースコード解説
1. input要素に指定したid("upload_img")でchangeイベントで発火
1. `this.files[0]`でオブジェクトのインデックス番号0の画像データを取得
1. `window.URL.createObjectURL(reader)`でローカル環境でのみ有効な画像urlを生成
1. `createElement`関数でimgタグを生成
1. srcに生成したurlを付与
1. 任意のエレメントに生成したimgタグを追加

※srcに指定したurlは保存しても取得した際画像を表示できないので、あくまで画像プレビュー機能として有効です。
※画像を複数選択した場合はforでループ処理してあげればokです！
【Mac】ショートカットキー一発でお目当てのアプリをアクティブにする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/8cfb9ce1-2bc3-7da3-e8b5-f1942d65a4aa.png)

Hammerspoonを使って、ショートカットキー一発で任意のアプリをアクティブにする設定です。

例えば、`Cmd + Ctrl + C` で Chrome をアクティブにしたり、 `Cmd + Ctrl + V` で VSCode をアクティブにできます。
![Kapture 2021-07-02 at 22.22.55.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/92b38168-8cf1-8f7e-722d-18727129ae76.gif)

↑入力キーもHammerspoonで表示しています。普段は邪魔なので出してません。

## みなさん、アクティブウインドウの切り替えってどうしてますか？

**Cmd+Tabですか？**
<img width=768 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/beb37c14-a21f-b2c7-2cb9-45b2ce1773a3.jpeg" />

**3本指スワイプですか？**
<img width=768 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/835276ab-0c13-0ea6-d7b2-1e523d7959d3.png" />

**ランチャーアプリですか？**
<img width=768 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/8340d238-9347-230a-0676-45cae3235951.png" />

上記の方法でやっている人は、**今すぐやめましょう！**
タブやMission Controlから**目視で選んでさらにクリック**したり、アプリ名を**わざわざ入力**するのは、貴重な脳のリソースの無駄遣いです！

## Hammerspoonでお目当てのアプリをアクティブにする

本題です。

冒頭の動画のようにショートカットキー一発で任意のアプリをアクティブにする設定です。

### Hammerspoonインストール

まずはHammerspoonをインストールしましょう。
brewでインストールできます。

```sh
$ brew install hammerspoon --cask
```

### ショートカットキー設定

Hammerspoonの設定ファイルにキーとアプリの紐付けを定義します。
言語はLuaです。

```lua:~/.hammerspoon/init.lua
local app_map = {}

local mash = {"cmd", "ctrl"}

local function registerAppLauncer(modifier, app)
  table.insert(app_map, string.lower(modifier) .. " - " .. app)
  hs.hotkey.bind(mash, modifier, function ()
    -- hs.alert.show('Cmd + Ctrl + '..modifier, 2) -- 動作確認用
    hs.application.launchOrFocus(app)
  end)
end

-- ショートカットキーとアプリの紐付け設定
registerAppLauncer("C", "Google Chrome")
registerAppLauncer("I", "iterm")
registerAppLauncer("V", "Visual Studio Code")
```

## Hammerspoonの設定ファイル再読み込み


「Reload Config」をクリックして設定変更を反映します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290998/bb120b36-39d6-9525-f587-344f12f4c266.png)

## 設定完了！

これでショートカットキー一発でお目当てのアプリをアクティブにできます！

HammerspoonではMacOSが認識できるイベントを元に様々な設定ができます。
アイデア次第で色々なショートカットを設定できて面白いですよ。

## 参考

https://qiita.com/mishiwata1015/items/fb7527e5b45f84a90eec

http://www.hammerspoon.org/

https://www.hammerspoon.org/docs/hs.application.html#launchOrFocus
Next.jsで「navigator is not defined」が出たときの対処法とSSRの説明

``react-simplemde-editor``というパッケージを使用したところ

**navigator is not defined**

というエラーが吐かれました。

こういうエラーが一番厄介ですよね。

# 解決策

結論から言うと、``dynamic``を使って動的にコンポーネントを読み込むことで解決できます。

```js
import dynamic from "next/dynamic";
const SimpleMDE = dynamic(() => import("react-simplemde-editor"), { ssr: false });

// 省略
```

このように問題が起きているであろうコンポーネントを``dynamic``で読み込んであげるとエラーは解消されました。

# 理由

あくまで僕個人の解釈なので合っているかは分かりませんが、一応調べたことを載せておきます。

まず、SSR(サーバーサイドレンダリング)とはなにか。

1度は聞いたことがあるかと思いますが、簡単に説明すると、**ユーザーのリクエストなしでは Pre-rendering できないページ、最新状態を維持する必要があるページ**のときにSSRを行うそうです。

イメージしやすいのはECサイトやツイッターのツイートとかですね。

これらは、リクエストを行うごとに毎回ページの内容が変わりますよね。

また、SSRはReactのコンポーネントをHTMLへ変換する前に処理を行うものです。

基本的には``getServerSideProps``を使ってリクエストを行います。

なので、ページが生成される前に処理を行い、その中で読み込みが終えていないもの(今回でいうとnavigator)を読み込もうとするとエラーが出るわけです。

当然っちゃ当然ですよね。

Reactでも``useEffect``を使わずにマウントされる前に``window.addEventListener``とか行うとエラーが出ます。

これも同様で、ページを読み込む前に``window``を指定すると当然``window``なんて無いですよ！とエラーが出ます。

これを``dynamic``はページを読み込みつつ、コンポーネントを呼んでくれる魔法のメソッドなわけです。

動的に読み込んでくれるおかげで依存関係を解消してくれる。

現に``dynamic``を使ってインポートしたコンポーネントは、一足遅れてレンダリングされと思います。

また、``{ssr: false}``として指定しているのはSSRの説明を見た方なら当然わかりますよね。

こんな感じでまたNext.jsとReactへの理解が深まりました。

こうしてみるとNext.jsってかなり作りこまれているなと思いました。

便利な反面、最初のうちはエラーの理由が分からなかったりするのですが、そこはやりながら理解していくというのが良いと思います。

なにかご不明な点であったり間違っていることなどがあれば、ぜひコメントにて教えていただけると幸いです。

引き続き勉強に励みます。

皆さんもよきNext.jsライフを！

以上、「Next.jsで「navigator is not defined」が出たときの対処法とSSRの説明」でした！

Thnak you for reading

# 参考記事

・[SSG と SSR で理解する Next.js のページレンダリング](https://zenn.dev/luvmini511/articles/1523113e0dec58)
・[next.jsでSSRの問題が起きたときにはdynamicとssr:falseでお手頃SSR回避出来る](https://zenn.dev/terrierscript/articles/2020-09-06-next-js-ssr-dynamic-import)
Mac で/private/etc/hostsファイルを書き換える方法

#はじめに

環境

MacOS Big Sur バージョン11.4

#hostsファイルの書き換え方

hostsファイルは、IPアドレスとホスト名の対応を記述したテキストファイルです。

デフォルトのIPアドレスはlocalhostとして設定してあるようです。

これを、例えば同じIPアドレスに別のホスト名を設定して、二種類のホスト名を使えるようにしたければ、

前述のhostsファイルを書き換える必要があります。

例えば、以下のように記述します。

```
#デフォルトのlocalhostの設定
127.0.0.1	localhost

#追記して使えるようにしたいホスト名
127.0.0.1 another.local
```

このとき、hostsは普通に書き換えることができません。

次のような警告文が出ます。

```
ファイル“hosts”を含むフォルダに書き込むためのアクセス権がありません。
```

書き換えるためには次の手順を踏みます。


・Finderを開く
　　　↓
・cmd + shift + g でパス検索の入力画面を開く
　　　↓
・/private/etc/hostsで検索
　　　↓
・hostsファイルをデスクトップにドラッグ＆コピー
　　　↓
・デスクトップのコピーファイルを開き、編集
　　　↓
・コピーファイルを、private/etcフォルダにドラッグ&ドロップ
　　　↓
・パスワードを入れてhostsファイルを置き換え
　　　↓
この手順で、ロックされたhostsファイルを書き換えることができます。

#まとめ

apacheの学習中に、hostsファイルを変更することがあり、ロックがかかっていたので手間取りました。

そのため手順を備忘録として記事に残します。
Herokuでデプロイするまでの苦悩

# はじめに
こんにちは、初めてアプリをHerokuでデプロイしようとした時に、いろんな壁にぶち当たったので、この記事はHerokuをデプロイするまでの手順とぶち当たった壁たちをまとめた記事になります。
Heroku初心者なので、理解せずにやっていることをお許しください。
ちなみにアプリはまだできてないです。AmazonのAPIを取得したかったのですが、そのためにはwebサイトのURLが必要だったので、先にデプロイをしなければならなくなっただけです。
長ったらしくなったらすみません。

### 環境
 - Mac catalina
 - Laravel：6
 - PHP：7.3
 - AWS Cloud9 EC2

バージョンを一応書きましたが、たぶん関係があるのはAWS Cloud9 EC2だけです。
Cloud9独特の問題もあったので、それ以外の開発環境の人にはあまり参考にならないかもです。

### 前提条件
PHP、Laravel、composerはすでに入ってるものとします。

# 手順
基本、公式ドキュメントの通りにしました。
1.[Heroku CLI](https://devcenter.heroku.com/ja/articles/heroku-cli#verifying-your-installation)
2.[Heroku(Laravel)](https://devcenter.heroku.com/ja/articles/getting-started-with-laravel)

# ぶち当たった壁たち
### 1.Heroku CLIをダウンロードの仕方がわからない
ここで小さな壁にぶち当たりました。公式ドキュメントにAWS Cloud9 EC2のダウンロードのコマンドが書かれてません。
なので、以下のQiitaの記事の通りにやったらできました。
https://qiita.com/zuumi/items/bd983a2890e9a2fa371b

### 2.ログインできない
ここで大きな壁にぶち当たりました。
それは、`heroku login`がうまくいかないことです。
本来ならば、`heroku login`をすると、
> `$heroku login`
> heroku: Press any key to open up the browser to login or q to exit
>  ›   Warning: If browser does not open, visit
>  ›   https://cli-auth.heroku.com/auth/browser/***
> heroku: Waiting for login...
> Logging in... done
> Logged in as me@example.com

こんな感じでwebブラウザが立ち上がり、そこでログインをするっぽいのですが、Cloud9は自動でwebブラウザが立ち上がらず、Waiting for login...から先に進みません。
書かれているURLに飛んでも、ログイン画面は出てくるのですが、Loginボタンを押すと、IP address mismatchと表示され、ログインできません。

なので、

```
heroku login -i
```
もしくは

```
heroku login --interactive
```
にすることで解決するようです。
これをすることで、ターミナル上でHerokuに登録したEmailとPasswordを入力し、ログインすることができます。

しかし、あとでHerokuの方で二段階認証をするよう言われたので設定したのですが、Cloud9でログインすると、`heroku login -i`をすると二段階認証を求められ、ブラウザは立ち上がらないしIP address mismatchって出てできないしで詰みました。なので二段階認証をなくしました。
これはまだ解決してないので、分かる方がいましたら教えてください。

### 3.ずっと403がでる
ログイン以降順調だったのですが、最後の`heroku open`でブラウザに行っても403になり、画面が出てきませんでした。
![スクリーンショット 2021-07-02 21.41.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/946733/c565f416-0509-16db-684b-2b25a904f273.png)
この壁が大きすぎて、リアルに10時間くらい溶かしました。不毛ですね。

原因は、2つありました。

#### 1.envとHerokuでAPP_KEYが違う
1つ目の原因がこれです。
403が出て、いろいろ調べて試しているうちに、おそらく何度もAPP_KEYを作成してしまったため、APP_KEYを記録する.envファイルとHeroknoの方で違うAPP_KEYを記録してしまっていました。
なので、両方のAPP_KEYを同じにすることで解決しました。
まず、`vim .env`をし、

```
APP_KEY=値
```
のところの値をコピーします。
次に、Heroku>アプリをクリック>Settings>Reveal Config Vars>APP_KEYの右の欄に.envのAPP_KEYの値をペーストします。

これで解決しました。

#### 2.ブランチが違う
しかし、私のブラウザはまだ403です。
またいろいろ調べました。その結果、Herokuへプッシュする'git push heroku ブランチ名'のブランチ名のところをmain(master)ブランチではなく、mainブランチから切ったブランチ名(トピックブランチ)を入れていました。
悪い例

```
git push heroku branch1
```
herokuにプッシュするブランチはmasterもしくはmainでなければならないようです。
私のデフォルトブランチはmainなので、切ったブランチをマージして'git push heroku main'とすることで、ようやく解決し、デプロイすることができました！！

# エラーになりがちなこと

- Procfileに

```
web: vendor/bin/heroku-php-apache2 public/
```
を書き忘れる。

- 変更をした後add、commitしてない。


# 関係なかったこと

これは、壁にぶち当たった時に色々調べてやってみたけど、終わってみれば関係なかったのかなってことです。実際関係なかったかはわかりませんが、元に戻しても正常に動いたので、このデプロイでは関係なかったのかなと思います。
まあ、いつか役に立つ日が来るのかもしれないと信じ、メモしておきます。

## Requestファイルの中身を変える
https://hpscript.com/blog/laravel5-7-403%E3%82%A8%E3%83%A9%E3%83%BC-forbidden/
このサイトを見ましたが、解決しませんでした。Herokuでもなかったので、目的のサイトではなかったのだと思います。

# さいごに
初めてHerokuでデプロイするために、大量の時間を費やしましたが、勉強にはなったので完全に無駄ではなかったと思います。
from TkinterDnD2 import * でエラー

#環境
Windows 10 Pro
Python version: 3.8.8
PyCharm Community Edition 2021.3.4

#エラー
<b>Tkinter</b>で、<b>canvas</b>にドラッグアンドドロップ機能を実装したく、いろいろ調べると、以下の通りにモジュールをインポートする必要が出た。

```python:examin.py
import TkinterDnD2
```
しかしながら、

```
ModuleNotFoundError: No module named 'TkinterDnD2'
```
とエラーが出てしまった。おかしい、、確かに```TkinterDnD2```をインストールしたはず、、

#対処法
たまたま、他のコードをしていると、```tkinter```を使用する際に、予測変換に```tkinterdnd2```とでてきた。
これは！と思い、すべて小文字にしてインポートすると、

```python:examin2.py
import tkinterdnd2
```
行けた！！

#コメント
まさか、大文字と小文字で、、
どなたかの、お力になれれば幸いです！
なんか、インポートおじさんになって気がする、、(笑)
勤怠Aフォロー：（続）Active Recordについて

## ここまでの内容を踏まえて注意したいこと

```
# NG
def create
  @user = User.new
　　　　@user.save
  redirect_to xxx_url
end

# better
def create
  @user = User.new
　　　　@user.save!
  redirect_to xxx_url
end

# best
def create
  @user = User.new
　　　　if @user.save
    保存に成功した時の処理
    redirect_to xxx_url
  else
    保存に失敗した時の処理
    # なぜ保存に失敗したか、バリデーションメッセージでユーザーに教えてあげましょう！
    render :new
　　　　end
end
```

```
例)seedファイルで初期データを作成するとき

# NG
User.create(name: "aaa", email: "test_a@example.com")
User.create(name: "bbb", email: "test_b@example.com")
User.create(name: "ccc", email: "test_c@example.com")
User.create(name: "ddd", email: "test_d@example.com")
User.create(name: "eee", email: "test_e@example.com")

# OK
User.create！(name: "aaa", email: "test_a@example.com")
User.create！(name: "bbb", email: "test_b@example.com")
User.create！(name: "ccc", email: "test_c@example.com")
User.create！(name: "ddd", email: "test_d@example.com")
User.create！(name: "eee", email: "test_e@example.com")
```

## Active Recordのメソッドはとても便利ですが

Rails 開発ではその恩恵を十分に受けつつ、SQL のクエリも簡単なものぐらいは書けるようにしておきましょう！
GASでRSSから情報を持ってきてスプレッドシートにDBライクに溜めておくコード


目的
---
RSSで取得したデータをどこかに保管しておきたいと思った。GASで手軽にできるようにコード書いた。

コード全体
-----

この関数を時間でトリガーしておけばOK。

```js

function get_rss_first(){
  
  
    //rssを読み込む
  var my_rss="https://news.yahoo.co.jp/rss/topics/top-picks.xml";
  var rss_data=UrlFetchApp.fetch(my_rss);
  var rss_xml = XmlService.parse(rss_data.getContentText());
  
  //アクティブシートを取得
  var activeSheet = SpreadsheetApp.getActiveSheet();
  
  //最後の行を特定
  const FValues = activeSheet.getRange('A:A').getValues();　 //A列の値を全て取得
  const LastRow = FValues.filter(String).length;　　//空白の要素を除いた長さを取得
  
  //パースして配列にする
  var rss_entries = rss_xml.getRootElement().getChildren('channel')[0].getChildren('item');

  //配列の要素の順を逆にする＝これやらないと順番おかしくなる
  var rss_setValue = rss_entries.reverse();
  var array_length = rss_entries.length;
  console.log(array_length);
  
  //タイトルの検索用＝重複してたら取り込まない
  var search_range = activeSheet.getRange('A:A').getValues();
  var src_flat = search_range.flat();
  var n =0;
  
 
  if (LastRow > array_length) {
    
    //配列からデータ取ってくる
    for ( var i=1; i < array_length ; i++){
    var title = rss_entries[i].getChildText("title");
    var link = rss_entries[i].getChildText("link");
    var date = rss_entries[i].getChildText("pubDate");
    var description=rss_entries[i].getChildText("description");
    
　//書き込む行を取得
   var activerow = LastRow + i;

   activeSheet.getRange('A'+activerow).setValue(title);
   activeSheet.getRange('B'+activerow).setValue(link);
　 activeSheet.getRange('C'+activerow).setValue(date);
　 activeSheet.getRange('D'+activerow).setValue(description);

}
  }
  else{
    for ( var i=1; i < array_length ; i++){
    var title = rss_entries[i].getChildText("title");
    var link = rss_entries[i].getChildText("link");
    var date = rss_entries[i].getChildText("pubDate");
    var description=rss_entries[i].getChildText("description");
    
   var activerow = LastRow + i;

    if (src_flat.includes(title)===true){

    }else{

   n++;
   var number = LastRow + n;
   activeSheet.getRange('A'+number).setValue(title);
   activeSheet.getRange('B'+number).setValue(link);
　 activeSheet.getRange('C'+number).setValue(date);
　 activeSheet.getRange('D'+number).setValue(description);
}
}
  }
  

}

```

問題点
----

なぞにタイトル全部をルックアップしてしまっているので、必要最低限にした方が良いかも。６分制限超えたらマズイので。


```js
  var search_range = activeSheet.getRange('A:A').getValues();
```
検索機能とタグ検索の両立

今日は検索機能とタグ検索の両立についてまとめます！


この検索機能の記事を実装できていることを前提とします！
https://qiita.com/Kairi_Yasunnde/items/935dcdb8ec88b9ed9d91#3タグ検索機能の実装




```tweet.controller.rb

  def index
      if params[:search].blank? && params[:tag_id].blank?
      @tweets= Tweet.all
    elsif params[:search].present? && params[:tag_id].blank?
      @tweets= Tweet.where("body LIKE ?",'%' + params[:search] + '%')
    elsif params[:search].blank? && params[:tag_id].present?
      @tweets = Tag.find(params[:tag_id]).tweets
    else
      @tweets = Tag.find(params[:tag_id]).tweets.where("body LIKE ? ",'%' + params[:search] + '%') 
    end
```

```index.html.erb
 <<%= form_tag({controller:"tweets",action:"index"},method: :get)do %>
 <%= text_field_tag :search %>
 
  <%= select_tag :tag_id,
  options_from_collection_for_select(Tag.all, :id, :name, params[:tag_id]),
  {
   prompt: 'タグで絞り込み',
   
  }%>
  
 <%= submit_tag '検索する' %>
<% end %>
```

この二つのページを挿入すれば、タグ検索と通常の検索機能を両立可能です！
カラムの違う部分などは都度調整してくださいね！
Scratch3.0をDockerで簡単に構築しよう

## はじめに
最近、Scratchのことを聞かれる機会が多くなってきました。
みなさんも自分の子ども、あるいは甥っ子、姪っ子、友人経由でその子ども達から聞かれる機会が増えてくるかもしれません。

「おじちゃん、IT詳しいのにScratchもわからないの？」とか子どもに言われたらけっこうショックな気がします。

というわけで誰かになにか聞かれてもすぐ答えられるように動作環境を確保すべく、Scratch3.0を簡単に環境構築する手順をまとめました。

Scratch3.0は無料のオンラインサービスがあるようですがアカウント作成が面倒という人は自分のPCにさくっと作るほうが心理的に楽かもしれません。また、Extension(拡張機能)を試すには環境構築するしかないようです。

### 参考
webを探してみるとここに手順がすべて記載されています。完璧！
[Nodejs x Alpine x DockerでScratch3.0の独自Extensions(拡張機能)を作成する開発環境を整える](https://deep.tacoskingdom.com/blog/29)

### 改良版
ですが・・このままでは手動の作業が多いのでこの内容をそのまま丸々`install.sh`にまとめました。

```install.sh
#!/bin/bash
if [ -f /usr/src/app/completed ]; then
  echo "Installation skipped"
  tail -f  /dev/null 
fi
cp -p /home/node/package.json /usr/src/app/.
cd /usr/src/app
git clone --depth 1 https://github.com/llk/scratch-gui.git
git clone --depth 1 https://github.com/llk/scratch-vm.git
cd scratch-vm
yarn install
yarn link
cd ../scratch-gui
yarn link scratch-vm
yarn install
touch /usr/src/app/completed
tail -f  /dev/null 
```

以下が`Dockerfile`の内容です。初回コンテナ起動時のみ`install.sh`から各種コマンドを実行するようにしています。

```Dockerfile
FROM node:12.16-alpine3.11

ENV NODE_ENV=development
WORKDIR /usr/src/app

RUN apk update && apk upgrade && apk add --no-cache \
    bash git openssh python2 curl

RUN npm i -g webpack webpack-cli webpack-dev-server

COPY --chown=node:node package.json /home/node
COPY install.sh   /home/node
RUN  chmod +x     /home/node/install.sh
RUN  chmod +w     /home/node/package.json
CMD  ["/home/node/install.sh"]
```

こちらが、`docker-compose.yml`の内容です。
インストールした内容は`~/work/data/scratch`に格納されます。

```/docker-compose.yml
version: '3'

services:
  app:
    image: scratch3:12.16-alpine3.11
    build: 
      context: .
      dockerfile: ./Dockerfile
    user: "node:node"
    restart: always
    environment:
      NODE_ENV: development
    ports:
      - 8601:8601
    volumes:
        - ~/work/data/scratch:/usr/src/app
    tty: true
```

image作成→コンテナ起動→`install.sh`実行で準備完了。
その後に`docker-compose exec app yarn start`を実行してScratchのサービスを起動するという流れです。


## How To Install
具体的な構築点順は以下の通りです。

**1. Dockerfile等のclone**

```
git clone https://github.com/ken992/Scratch3.0/
```

**2. Dockerfileのbuildとコンテナ起動**

```
cd Scratch3.0
docker-compose up -d
```

**3. install.shの実行状況確認**

```
docker-compose logs -f
``` 

以下のメッセージがでたらScratch3.0のインストール完了

```
〜〜〜省略〜〜〜〜〜〜〜〜〜〜
[4/4] Building fresh packages...
success Saved lockfile.
Done in 78.74s.
```

**4. Scratch3.0のサービス起動**

```
docker-compose exec app yarn start
```

以下メッセージが出たら起動完了

```
｢wdm｣: Compiled successfully.
```

**5. ブラウザにアクセスする**

```
http://0.0.0.0:8601/
```


以下にコミットしています。

https://github.com/ken992/Scratch3.0
PowerQuery Excelファイルの置いたパスで実行させたい時

# 概要
Excelファイルを置いたPathを取得して、相対的にファイル一覧を取得してPower Queryを実行する為の方法。

# やり方
1. Excel 関数で、Power Queryに渡すためのデータを作っておく

    ```ruby::Excel関数
    =LEFT(CELL("filename",A1),FIND("[",CELL("filename",A1))-2)
    ```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/606e515d-51db-9fc0-04c3-8cb4d8e2f5ae.png)

1. Power Queryで、上述のデータを利用して、フォルダ一覧取得（ファイル取得とかでも）

    ```ruby:フォルダ一覧取得
    = Folder.Contents(
      (Excel.CurrentWorkbook(){[Name="Settings"]}[Content]){0}[対象Path]
    )
    ```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/7c01bb52-636d-4531-1d27-d72570dfe6e3.png)


# 理解促進の為の要素分解
問題解決の近道は分解ですね。

1. = (Excel.CurrentWorkbook())<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/9e83e773-90ca-e3e2-086b-c240469ffb54.png)
1. = (Excel.CurrentWorkbook(){0})<br> = (Excel.CurrentWorkbook(){[Name="Settings"]})<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/b9065414-e15d-446a-7460-56f1e18d0aef.png)
1. = (Excel.CurrentWorkbook(){[Name="Settings"]}[Content])<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/a8c0a75f-89c0-4d3d-e4b4-369f0abc6458.png)
1. = (Excel.CurrentWorkbook(){[Name="Settings"]}[Content]){0}<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/d0ac7bf2-0f5c-25c1-b0c6-a17b2ad290d1.png)
1. = (Excel.CurrentWorkbook(){[Name="Settings"]}[Content]){0}[対象Path]<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120072/a7db0b03-5ab8-3138-4bef-b40dcab6c266.png)
Discordで仕事する、ということ

私が代表をやっている七夕研究所では、チームメンバーの会話のやりとりをDiscordでやってます。
機能的にはほぼ「貧者のSlack」なのですが、SlackとDiscordの違いをPros/Consにまとめてみました。

# Pros

* 課金がマイルド
    * Slackのフル機能に近いところまでDiscordだと無課金でやれる印象
* 直接Discordでお客さんサポートとかをやらない限り仕事で使うのは可能
    * 英米法では「商用利用」は明確な意味があって、仕事で使う != 商用利用です
    * とはいえ最近商用利用不可の記述が消えたらしい……

# Cons

* 本来企業用じゃないので「お金を払って機能アップ」が難しい
    * コミュニティのメンバー「複数人が」課金することでレベルが上がる構図
    * 特に音声品質通話品質とかは単体だと厳しい
* 連携が弱すぎる。多くのDX系SaaSがSlackは対応していますが、Discord対応はかなりレア。
    * とはいえZapierはDiscord対応
    * 未対応なら自分で書けばいいよね？がDiscord文化圏
* 多分セキュリティの担保がない
    * 全くないわけじゃないんでしょうが、内部統制とかのチェックに耐えるものじゃないだろうなぁ
    * まあそこまで考えるとSlackが月3000円コースになるけど……

まあ、使えないわけじゃないですよね。
必要な連携は自分で作りましょう。
Rails 6.1 でview を色んな場所でrender する

app/users/index.csv.ruby をrender する場合は下記の様なコードを書く。
どこからでも呼べる

```
args = { users: User.all } # index.csv.ruby で @users でアクセスができる
ActionController::Base.renderer.render(template: "users/index", formats: [:csv], assigns: args)
```
書籍のタスク管理アプリを徹底解体

## 概要

世間一般の技術書籍は最後にサンプルアプリケーションの作成方法が紹介されています。この記事は次の書籍のサンプルアプリケーションを投稿者が理解できる範囲で徹底解体します。

- `Vue.js` 入門: 基礎から実践アプリケーション開発まで