OmniAICreator/Qiita-1.07M huggingface dataset:
AWS触ったことがなかった新入社員のワイがSAA-C02に合格するまでの道のり

# 概要
さんざんいろんな人が書いてるけど・・・

2020/10/11、SAA-C02版のAWS認定ソリューションアーキテクトアソシエイトに合格したので、どういう勉強をしたのか、どういう問題が出たのかお伝えしていきたいと思います。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379397/b5848dac-0f14-25a2-3ec5-360153d0a3cf.png)

結果は769点でした。
8割くらいはいってるかなと思いましたが、そう甘くなかったですね。
でも、一発で合格できたので良かったです。

### 受けるきっかけ
私は20年卒の新入社員で都内の某IT企業で働いております。
私の会社では、数年前から自社のシステムをAWSに移行するプロジェクトが開始しており、社内でもAWS勉強会や資格試験に挑戦する流れが活発になってます。

会社に入る前の学生のときは、勉強したいとは思っていたものの、知らぬ間に数千円も課金されていたらヤダな・・・と思いAWSに触れたことはありませんでした。

会社では、エンジニアだけの共通研修があるのですが、そこで上司の方に「挑戦してみよう」と言われたことがきっかけで勉強することになりました。

### 受ける前の知識

EC2・・・きいたことはある
VPC・・・知らん
AZ ・・・知らん
RDS・・・知らん

ザコですねw
# 勉強内容
### 勉強期間
6月下旬くらいから初めて、受かったのは10月初旬なので約3ヶ月程ということになります。

新人だからといって資格を勉強する時間は与えられなかったので、仕事が終わって、ほぼ毎日30分～1時間くらいは勉強していたと思います。

私が所属する部署が管轄しているシステムは、現状AWSに移行していないので、業務で触れる経験はまったくありませんでした><

なので、社内で定期的に開催されるAWSハンズオンに参加して基本的なS3やLambda、EC2なんかは社内のアカウントを使用して実際に触れることはできました。
（途中で仕事が忙しくなって全く参加できなくなりましたw）

### 教材
##### [１．黒本](https://www.amazon.co.jp/%E5%BE%B9%E5%BA%95%E6%94%BB%E7%95%A5-AWS%E8%AA%8D%E5%AE%9A-%E3%82%BD%E3%83%AA%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%88-%E2%80%93-%E3%82%A2%E3%82%BD%E3%82%B7%E3%82%A8%E3%82%A4%E3%83%88%E6%95%99%E7%A7%91%E6%9B%B8/dp/4295005495/ref=sr_1_1?adgrpid=69470117510&dchild=1&gclid=CjwKCAjwrcH3BRApEiwAxjdPTZ5ypuZI5fYN692_MJzXR-8v7pRRrjunTN2p9W1NSkrQWPvYyGqcBRoC2kgQAvD_BwE&hvadid=338522196450&hvdev=c&hvlocphy=1009314&hvnetw=g&hvqmt=e&hvrand=8556175864164230916&hvtargid=kwd-624187092400&hydadcr=18699_11156867&jp-ad-ap=0&keywords=aws+%E9%BB%92%E6%9C%AC&qid=1592863923&sr=8-1&tag=googhydr-22)

最初はこの本をじっくり読むことから始めました。
少し古い本なので、正直試験に大きく役に立つことはなかったですが、基本的なAWSサービスの解説やアーキテクチャのイメージが掲載されていたので基礎を固める上では非常に役に立ちました。
模擬テストが１回分ついてくるのですが、私の感覚では本番の難易度に結構近いと思いました。

##### [２．模擬試験（udemy）](https://www.udemy.com/course/aws-knan/)
6回分390問ついてます。
最初にやったときはどれも40%前後しか取れなかったので、高難易度とはいうもののこんなに難しいのかと思いました。
この模擬試験6回分をだいたい5～6周くらいはしたと思います。（問題をある程度覚えてしまって、あまり効果がなかったかも）
この模擬試験のいいところは問題料が多いところと解説が丁寧なところです。
解説をよく読んだ上で、よくわからなかったところを調べたりすれば合格できる知識が自然と身につくと思います。

# いざ本試験
テストセンターで受験しました。
始まる前は落ちたらヤダな～、怖いな～と思って少し緊張しましたが、最初の問題が自信を持って回答できたので緊張がほぐれました。

問題の内容としては、コスト最適化を考えたときのインスタンスの選択やAutoScaling、リードレプリカが答えになるような問題が10問前後くらい出たと思います。
Udemyの6回目の模擬試験はC02を考慮した問題なので、似たような問題がいくつかでました。（Transit GatewayやFSxなど）
FSxが答えになりそうな問題が4問出たのは少し驚きましたw

先程も書きましたが、黒本の模擬試験やUdemyの基本問題に近い難易度の問題が多かったように感じます。

しっかり知識をつけていれば消去法で解ける問題が多いので、Udemyの高難易度の問題よりは細かい知識はいらないかなと思いました。

# まとめ
コツコツ勉強したかいがあって、合格できました。嬉しいです。
実際にAWSを触らなくても合格できると思いますが、勉強したあとでAWSを触ってみると覚えやすかったですし、理解が深まったのでできればやっておいたほうがいいと思います。

難易度の感覚としては、基本情報 <= AWS SAA < 応用情報　といった感じでしょうか。

いろいろ攻略方法はあると思いますが、Udemyの模擬試験　これが無難かと思います。

現状、仕事ではAWSを使えていませんが、近いうちに使う予定なので貢献できるように頑張りたいと思います。

実務経験が何よりも大事だと思うので・・・

以上、ありがとうございました。
Excelのワークシートをまとめる関数

# ワークシートをまとめる
同じワークブックにある複数のシートをまとめる作業が多発するようになった。手作業でコピー&ペーストを繰り返していたが、作業ミスが発生したので自動化させることにした。

## 仕様
+ コピー先ワークシートの最後行の次行に追加される
+ ワークシートのA列で最終行を判断する
+ コピー開始行は1行目か2行目のみ指定可能
+ ペーストは値のみコピー
+ 何らかのエラーが発生した場合はマクロの戻り値がFalse

## コード
```vb:sample.vba
'
'ワークシートを1つにまとめる
'DestSheet : まとめ先のシート
'SourceSheet : このシートをまとめ先シートの最後に追加する
'FirstLineInclude : 先頭行も含む　省略可　初期値はFalse=含まない
'
'ex) call VMakeOneSheet(WorkSheet1,WorkSheet2,True) 'WorkSheet2の先頭行を含めて、Worksheet1にコピーする
'
Public Function VMakeOneSheet(ByVal DestinationSheet As Worksheet, ByVal SourceSheet As Worksheet, Optional FirstLineInclude As Boolean = False) As Boolean
    On Error GoTo ErrorLabel

    Dim TargetLastRow As Long   'まとめ先シートの最終行＝ここがコピー先の行番号
    Dim SourceStartRow As Long  'まとめ元シートの先頭行
    Dim SourceLastRow As Long   'まとめ元シートの最終行

    '初期化
    If FirstLineInclude = False Then
        SourceStartRow = 2
    Else
        SourceStartRow = 1
    End If

    '主処理
    'まとめ先のA列最終行を求める
    'まとめ元のA列最終行を求める
    'まとめ元のSourceRowNumberからA列最終行までをクリップボードにコピー
    'まとめ先のA列最終行+1にペースト

    'まとめ先のA列最終行を求める
    TargetLastRow = DestinationSheet.Cells(Rows.Count, "A").End(xlUp).Row
    'まとめ元のA列最終行を求める
    SourceLastRow = SourceSheet.Cells(Rows.Count, "A").End(xlUp).Row
    'まとめ元のSourceRowNumberからA列最終行までをクリップボードにコピー
    SourceSheet.Range(SourceStartRow & ":" & SourceLastRow).Copy
    'まとめ先のA列最終行+1にペースト
    '最終行が1行目なら(ワークシートが空白を想定)1行目にペースト
    If DestinationSheet.Cells(1, 1).Value = "" Then
        TargetLastRow = 1
    Else
        TargetLastRow = TargetLastRow + 1
    End If
    DestinationSheet.Cells(TargetLastRow, "A").PasteSpecial (xlPasteValues)

    '後処理
    Application.CutCopyMode = False 'コピーモード解除
    VMakeOneSheet = True

    Exit Function

ErrorLabel:
'    Debug.Print Err.Number
'    Debug.Print Err.Description

    VMakeOneSheet = False

End Function


```
Koinを使ってActivity/Fragment間で共有するViewModelをDIする

- build.gradle

```kotlin
buildscript {
    ext.kotlin_version = '1.4.10'
    ext.koin_version = '2.1.6'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.koin:koin-gradle-plugin:$koin_version"
    }
}
```

- app/build.gradle

```kotlin
dependencies {
    implementation "org.koin:koin-android:$koin_version"
    implementation "org.koin:koin-androidx-scope:$koin_version"
    implementation "org.koin:koin-androidx-viewmodel:$koin_version"
}
```

- MainActivity

```kotlin
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager.beginTransaction()
            .replace(R.id.content_layout, MainFragment())
            .commit()
    }
}
```
- MainFragment

```kotlin
class MainFragment : Fragment() {

    private val viewModel by sharedViewModel<MainViewModel>()

    ...
}
```
Python3のExecutor.submitを大量に実行するとメモリを多く消費する場合がある


参考にしました: https://www.bettercodebytes.com/theadpoolexecutor-with-a-bounded-queue-in-python/

概要
----------

Executorでmax_workersを指定すると並列度が調整できますが、並列数を上回るペースでsubmitした場合にどうなるかというとブロックは起きません。その代わりメモリにため込むようです。この動きのため、大量に実行するとメモリを多く消費してしまうことがあります。

``` py
with ThreadPoolExecutor(max_workers=10) as executor:
    for i in range(0, 1024*1024):  # たくさん
        executor.submit(fn, i)     # つくる
    # forループはすぐ終わるが、消費メモリがすごいことになっている
```

実際、100万ループするようなコードを書くとメモリを2GBぐらい消費します。なので、対応を考えることにしました。


内部実装と原因
----------

内部実装を確認したところ、[ThreadPoolExecutorは内部でキューを持って](https://github.com/python/cpython/blob/v3.8.6/Lib/concurrent/futures/thread.py#L147)おり、[submitするとWorkItemというオブジェクトを作りキューに入れます](https://github.com/python/cpython/blob/v3.8.6/Lib/concurrent/futures/thread.py#L187)。この内部のキューには上限が無くブロックすることもないため、際限なくsubmitできてしまいます。

ちなみに、Workerスレッドはキューに詰めたタイミングで作っていて、[Workerスレッドはキューからデータを取り出し、実行、というのを無限ループでやっています](https://github.com/python/cpython/blob/v3.8.6/Lib/concurrent/futures/thread.py#L66)。


### 確認コード

実際にその動きを観察してみます。例えば0.01秒かかる関数を5000回実行します。これをmax_workers=10で回してみます。

for文内での進捗としてタイムスタンプとメモリ(今回はmaxrss)を見ます。

https://wandbox.org/permlink/n2P2CQssjhj1eOFw

タイムスタンプからsubmitでブロッキングは起きていないことがわかります（forループのsubmitする処理はすぐに終えてほとんどshutdown待ちになっている）
しかし、処理が進むにつれメモリ消費が増えていることがわかります。


対応
----------

### 案1. キューをサイズ付きにする

最初に考えたのはこの方法です。
ThreadPoolExecutorの内部で使うキューをサイズ付きのキューにします。継承してインスタンス変数をすり替えます。

https://wandbox.org/permlink/HJN0lRBR0VBYU0Pv

タイムスタンプからループ中にブロッキングが起きていることがわかります。しかしトータルの時間はさほど変わらずメモリの消費は非常に緩やかです。

コードは単純ですが、内部実装に手を出すのは若干いまいちな感じがしますし、ProcessPoolExecutorではこういったキューは持っていないので、この方法は使えません。

### 案2. Semaphoreで同時実行数を制御する

案1がいまいちだったのでどうにかできないか探していたところ参考元の記事を見つけました。

https://www.bettercodebytes.com/theadpoolexecutor-with-a-bounded-queue-in-python/

参考元を参考にPoolExecutorをラップしたクラスBoundedExecutorを作成します。API互換(map以外)なので差し替えて使えます。

内部実装はsubmit時にsemaphoreのカウントダウンを行い、ワーカーの処理が完了したときはsemaphoreのカウントアップをすること同時実行を制御します。「ワーカーの処理が完了したとき」というのは「futureのadd_done_callbackで登録した関数が完了時に呼ばれるとき」とします。（ワーカーの処理が完了した時もraise Exceptionした時もコールバックは呼ばれるので辻褄は合うはず）

https://wandbox.org/permlink/jn4nN8leJonLi2ty

こちらも、案1のときと同様な結果になりました。


ちなみに、max_workersよりも多くなるようにキューのサイズを決めたほうが良いです（コードで言えばbounded_ratio=1の箇所がbounded_ratio=2になるように引数を与えるor変える）
「並列数==キューのサイズ」にしてしまうと、キューに空きができてしまうタイミングが発生してしまい、ワーカーが遊んでしまい全体の完了が若干遅くなります。なので、少し多くなるようにするのがよいです。

https://wandbox.org/permlink/HPrJXNGxLeXzB1x2
【AWS初学者用】IAMとは？

# 本記事について
本記事はAWS初学者の私が学習していく中でわからない単語や概要をなるべくわかりやすい様にまとめたものです。
もし誤りなどありましたらコメントにてお知らせいただけるとありがたいです。

## IAMサービスとは
IAM(Identity and Access Management)サービスとはAWSを利用するユーザーのアクセスを安全に制御するサービス。正しいユーザーがアクセスし、ユーザーが持ってるアクセス権のサービスのみ操作できる仕組みです。

初めてAWSを利用する際にユーザーはメールアドレスやクレジットカード情報などを登録することで**AWSアカウント**を取得できます。そして取得したAWSアカウントの情報で**AWSマネジメントコンソール**にログインできます。このAWSマネジメントコンソールにログインしたユーザーを**ルートユーザー**と呼び、AWSアカウントの全ての管理権限を持っています。
ルートユーザーは全ての管理権限を持っているので「アカウント情報の漏洩」や「誤操作」などが起きるとアカウントを乗っ取られてしまったりして大変危険です。なので通常AWSのサービスを操作する際は、操作する用のユーザー(グループ)を作成して操作します。

```
この操作する用のユーザー(グループ)　＝　IAMユーザー(グループ)といいます
```

## IAMユーザー(グループ)
IAMユーザーはAWSサービスを利用するためのアカウントです。ログインIDを決めてIAMユーザーを作成します。　※作成した際にcsvでIDとパスワードがダウンロードできるかと思うので、ダウンロードしてください。今後ログインする上で必要になりますので。

IAMグループは会社などが複数のIAMユーザーをまとめて管理するときに使います。

## IAMポリシー
IAMユーザーを作成した段階では何も権限を持っていません。なので**IAMポリシー**によって権限を付与してあげることでAWSの各サービスなどのアクセスの制御ができます。
**IAMポリシーはIAMユーザーにアタッチすることができて、操作権限を付与(適用)する機能です。**
例えばIAMユーザーにIAMポリシーでS3へのアクセス権限を付与します。そうすることでS3へのみの操作が許可されたIAMユーザーが作成されたことになり、もしこのアカウントを乗っ取られたりしたとしても被害を最小限に抑えることができます。

## IAMロール
先ほどIAMポリシーはIAMユーザーに権限を適用するといいましたが、IAMロールにも適用することができます。
IAMロールとはAWSのサービスや他のアカウントに対して権限を付与するためのものです。IAMポリシーによってアクセス権限を付与されたIAMロールをEC2などのawsサービスにアタッチすることで権限を付与できます。


##まとめ
IAMサービスはAWSを安全に利用する上でとても重要なサービスです。
二段階認証アプリなどと併用することでより強固なセキュリティで保護できるかと思います。
削除されずに宙ぶらりんになったファイルがないか確認する


Active Storageを使ってアップロードファイルの管理をしていると、管理するテーブル (`active_storage_blobs` , `active_storage_attachments`) とストレージに実際に保存されているファイル (実体ファイル) との間に、何らかの理由で不整合が起きることがある。

中でも `active_storage_attachments` だけが削除され、それに紐づく `active_storage_blobs` と実体ファイルが残ってしまっている場合には、 `ActiveStorage::Blob.unattached` というスコープが使える。

```zsh
❯ rails c
Loading development environment (Rails 5.2.4.4)
[1] pry(main)> ActiveStorage::Blob.unattached
  ActiveStorage::Blob Load (1.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" LEFT OUTER JOIN "active_storage_attachments" ON "active_storage_attachments"."blob_id" = "active_storage_blobs"."id" WHERE "active_storage_attachments"."blob_id" IS NULL
```

これを使えば以下のように宙ぶらりんになった `active_storage_blobs` のレコードと紐づく実体ファイルを削除することができる。

```zsh
[1] pry(main)> ActiveStorage::Blob.unattached.each(&:purge)
  ActiveStorage::Blob Load (1.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" LEFT OUTER JOIN "active_storage_attachments" ON "active_storage_attachments"."blob_id" = "active_storage_blobs"."id" WHERE "active_storage_attachments"."blob_id" IS NULL
   (0.3ms)  BEGIN
  ActiveStorage::Attachment Exists (0.2ms)  SELECT  1 AS one FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = $1 LIMIT $2  [["blob_id", 1], ["LIMIT", 1]]
  ActiveStorage::Blob Destroy (0.5ms)  DELETE FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1  [["id", 1]]
   (0.8ms)  COMMIT
  S3 Storage (105.5ms) Deleted file from key: ...
=> nil
```

`ActiveStorage::Blob` クラスに `unattached` というスコープとして以下のように定義されている。(Rails v5.2時点)

```activestorage/app/models/active_storage/blob.rb
class ActiveStorage::Blob < ActiveRecord::Base
  ... 

  has_many :attachments

  scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) }

  ...
end
```

メモ：現時点でのmasterブランチには [missing](https://github.com/rails/rails/commit/9bfe89e68ee61d9c8ed71ac98c783a7e47bdb608) メソッドを使うように変更するコミットが入っているよう。

```diff:activestorage/app/models/active_storage/blob.rb
@@ -46,7 +46,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
 
   has_many :attachments
 
-  scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) }
+  scope :unattached, -> { where.missing(:attachments) }
```

### 参考
- https://tech.smarthr.jp/entry/2018/09/14/130139
  - 「浮いてしまったデータを定期的に削除する」の章
- https://github.com/rails/rails/commit/b9ed1fa4442633e1328dcde5a37a472b22003a6f
  - Rails v5.2向けのコミットは [こちら](https://github.com/rails/rails/commit/f763b2f3b8cc6a6e6d59df312344c9ab4688101d)
- https://github.com/rails/rails/commit/375722295619c69f0df84ecf4cc409e4f95fc4db
[React] importする際に絶対パスで書きたい

Reactでコンポーネントをimportする時などに、いちいち相対パスで書くのは面倒。そんな時は、絶対パスで書けるように設定しておきましょう。

```jsx
//こうじゃなくて。。。
import Button from '../../components/atom/Button'

//こう書きたい
import Button from 'components/atom/Button'
```

## 設定

簡単です。
Create React Appのドキュメントに書いてあります。

プロジェクトのルートに、 `jsconfig.json` 又は、 `tsconfig.json`を作成して以下を追記するのみです。

```json:jsconfig.json又はtsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

```

これでいちいち相対パスで書かなくて済みます。

[Create React App [Absolute Imports]](https://create-react-app.dev/docs/importing-a-component/#absolute-imports)
PythonでTwitterのツイートをしてみたいけどハマった話

自動でTwitterのツイートをしてみたいという話を聞いて、ちょっと作ってみたがハマった話

#CONSUMER_KEYがない

色々なサイトを見て検索するとpythonからツイートをするにあたって必要な項目が4項目あります。

CONSUMER_KEY,
CONSUMER_SECRET,
ACCESS_TOKEN,
ACCESS_SECRET

が必要と出てくるのですがどう見てもCONSUMER_KEYとCONSUMER_SECRETがない！

![スクリーンショット 2020-10-10 16.10.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372649/051a08fd-c851-3828-8fbf-629261c67089.png)


ただ、どうやっても見つからないので検索すると

CONSUMER_KEYはAPI KEYで
CONSUMER_SECRETはAPI_key_secretでいいそうです。

検索すればよく見るコードですが下記のコードに変数を変更しました。
このほうが理解しやすいと思います。

```python:twitter.py
import json
from requests_oauthlib import OAuth1Session

#ここにKeyとToken
API_KEY = 'XXXXXXX'
API_KEY_SECRET = 'XXXXXXX'
ACCESS_TOKEN = 'XXXXXXX'
ACCESS_TOKEN_SECRET = 'XXXXXXX'

twitter = OAuth1Session(API_KEY, API_KEY_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

url = "https://api.twitter.com/1.1/statuses/update.json"
print(twitter)

tweet = "Pythonからのテストツイートです\r改行する\r\n改行\n改行"  # ツイート内容

params = {"status" : tweet}

req = twitter.post(url, params = params) #ここでツイート

if req.status_code == 200: #成功
    print("Succeed!")
else: #エラー
    print("ERROR : %d"% req.status_code) 
```

#401エラーでツイートができない

API_KEYを取ったあと上記コードで実行を行っても

ERROR : 401

で全然ツイートができない。このケースは設定漏れでした。401の通り、権限なしでした。

まず、最初の設定は読み込みのみなので書き込みもできるように設定を変更しました。

![スクリーンショット 2020-10-12 20.20.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372649/c5d2d4c0-fe82-752c-e98a-52203d0d417c.png)

ただ、401エラーは継続です。

なんで出来なかったか。

多分ですがあとは読み込み & 書き込み設定に変更したあとに
再度ACCESS_KEYを取り直すこと(regenerate)。

![スクリーンショット 2020-10-12 20.21.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372649/9d02c362-8671-3915-2b56-e9f6b262fb6b.png)

再度取得しないと読み込みしか出来ないACCESS_KEYを使用している状態で書き込みができないのだと思われます。

それでも401エラーダメな場合は何度か取得して変数に代入してみてください。

#参考資料

https://prog-masaki.com/tweet-python/
ターミナルって何？

#ターミナル説明
**ターミナルは、PCに命令をすることができるツールですね。
環境構築を実行するには コマンドラインというツールが必要となります。
Macにデフォルトでインストールされているコマンドラインがターミナルです。**

#コマンドライン説明
**コマンドライン(または コマンドラインインタフェース：CLI )とは、コンピュータに対してキーボードからコマンドという文字を打ち込んで操作を行うツールになります。**

#GUI(グラフィカルユーザインターフェース)
**コマンドラインとは対照的に、グラフィックを用いて操作を行う仕組みを グラフィカルユーザインタフェース(GUI) と言います。**

例えば、「マウスでファイルをダブルクリックして開く」という操作など、普段PCで行う操作のほとんどはGUIで行なっていることが多いです。


![基本.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762918/f4063404-13d5-5ce7-6c67-b1a933fa7ac9.png)


#ターミナルを使う理由を学ぼう

先程、マウスなどを使った直感的に操作をすることができるGUIと、ターミナルのようにコマンドを使用して操作するCLIがあることを説明させて頂きました。

直感的に操作ができて普段から使い慣れたGUIではなく、なぜわざわざターミナルの操作に慣れ、それを使用しなければならないのでしょう？

**その理由は、GUIとCLIでは CLIの方が行える操作の数が圧倒的に多いためです。**

GUIのように全ての操作をグラフィカルに表現していては画面がメニューやボタンで溢りかえってしまって、分かりづらくなってしまうためです。
普段行わない操作についてはボタンが無かったり、そもそも簡単に操作できては危険なものであれば操作できないものである場合が多いです。

![ボタン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762918/a46bad11-0f8e-e3ee-09ed-99255b4cbf71.png)


CLIでは、PCに対しての操作のほとんどが行えると言って良いほどに、
様々な操作を行うことができます。

「Rubyプログラムの実行」もCLIが行える操作の1つです。
そのため、 ターミナルの操作は必須スキルだと思います。

#ターミナルの見方説明

まずは、ターミナルを開いて表示画面を見てみましょう！！

![ターミナル見方.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/762918/809a89dc-d18f-901f-c400-48a205e58a11.png)


**コンピュータ名は自分のコンピュータの名前ですね。
ここでは「A-137」となっています。ユーザ名は自分の名前です。
ここでは「div-M.T」となっています。このあとに続く「$」または「%」は プロンプト といい、 コンピュータが命令を受け付けられる状態であることを示します 。つまり今は、命令待ちの状態です。**

#まとめ

**・ターミナルとは、PCに命令をすることができるツールのこと**
**・「〜」には、カレントディレクトリが入ります。
　「％」は、プロンプトを言います。**


**プログラミングは、膨大な量の情報があるので、
基本から徐々に焦らずに理解していきましょう！！
できるようになるまで時間は、かかります。
でも、毎日継続して行えばだんだんとできるようになるので
一緒に頑張りましょう！！**

####以上。
Google Colabで学習済みモデルを保存する

# やりたいこと
- Google Colabで学習させたモデルを、Googleドライブに保存する

# 手順
- Google Driveを利用できるようにする
    - 以下を実行するとログイン画面に移動。ログインすると表示されるトークンを、入力するボックス（出てくる）に入力する

```python
from google.colab import drive
drive.mount('/content/drive')
```

- モデルを保存する
    - 右クリックで開かれるメニューからパスを取得できて便利

```python
model.save("drive/My Drive/Colab Notebooks/sample/sample.h5")
```

### 保存したモデルを使用する
```python
from tensorflow.keras.models import load_model
saved_model = load_model('/content/drive/My Drive/Colab Notebooks/sample/sample.h5')
```
mattermostからのユーザ削除

https://qiita.com/fake-deli-ca/items/7fb2446a94e3c2b2477a
gitlabに付属しているmattermostで、以前は上記の記事通りにユーザ削除をやってたんだけど、1年ぶりくらいにやってみたら、こういうエラーが出た。

```
$ /opt/gitlab/embedded/bin/mattermost -config='/var/opt/gitlab/mattermost/config.json' --version
---------------------------------------------------------------------------------------------
DEPRECATED! All previous commands are now deprecated. Run: platform help to see the new ones.
---------------------------------------------------------------------------------------------
panic: runtime error: invalid memory address or nil pointer dereference
```

https://docs.mattermost.com/administration/command-line-tools.html#mattermost-3-6-and-later
どうも、この3.6以降のCLIじゃないと動かなくなったみたい。

## Version
```
mattermost@dev:/opt/gitlab/embedded/bin$  ./mattermost  --config='/var/opt/gitlab/mattermost/config.json' version
Version: 3.7.0
Build Number: 3.7.5
(中略)
Build Enterprise Ready: false
DB Version: 3.7.0
```

## 動作方法
途中までは変わらず。dockerの中に入るところも一緒。

```
[root]# docker exec -it dev_gitlab_1 bash
root@dev:/# su - mattermost
$ bash
mattermost@dev:~$
```

ここからの手順が違う。
あ、`config.json`を設定するところは変わらないのと、mattermostアカウントじゃないとエラーになるところは今までと変わらず。

```
$ ./mattermost  --config='/var/opt/gitlab/mattermost/config.json' user delete hogehoge@example.com
Have you performed a database backup? (YES/NO): 
YES
Are you sure you want to delete the teams specified?  All data will be permanently deleted? (YES/NO): 
YES
[2020/10/12 10:38:39 UTC] [WARN] api.user.permanent_delete_user.attempting.warn%!(EXTRA string=hogehoge@example.com, string=j89mfnxdw7djifzy8m5ibg1m7c)
```

WARNが出るので若干不安になったが、うまく削除できれば、もう一度やり直すと登録なし、になる。

```
$  ./mattermost  --config='/var/opt/gitlab/mattermost/config.json' user delete hogehoge@example.com
Have you performed a database backup? (YES/NO): 
YES
Are you sure you want to delete the teams specified?  All data will be permanently deleted? (YES/NO): 
YES
Error: Unable to find user 'hogehoge@example.com'
Usage:
  platform user delete [users] [flags]

Examples:
  user delete user@example.com

Flags:
      --confirm   Confirm you really want to delete the user and a DB backup has been performed.

Global Flags:
  -c, --config string   Configuration file to use. (default "config.json")
```
重複チェックロジックを書くにはどのようにすれば可読性が高くなるのか

重複しているか調べたいデータをList型で所持する。DBのレコードならSQLで取得してくる。
そのうえで例えば文字列の重複チェックなら、

```Java:sample.java
String searchStr = '調べたい文字列';
List<String> dataList =  '調べる対象のデータ';

if(dataList.contains(searchStr)){
   // 重複したときの処理

} else {
   // 重複しなかったときの処理

}
```
このように書くとあとで見たときにわかりやすいのではないだろうか。
WSLでUbuntuインストール再び

# 背景
久しぶりに入れました。

# まずWindowsの準備とUbuntu入れる
PowerShellを管理者モードで起動して下記を入力。
`Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux`

終わると再起動を要求される。

「ストア」を開き、「Ubuntu」で検索して入手。

# 初回起動

スタートメニュー->から「Ubuntu」選択。

初回起動時はデフォルトユーザーのIDとパスワードを何にするか聞かれますので、指定します。

# 都合により、
私はProxyに邪魔されますので、

`vi .bashrc`
で、下記を追加。

> export HTTP_PROXY_USER=支給されたID
> export HTTP_PROXY_PASS=支給されたパスワード
> export HTTP_PROXY=http://\$\{HTTP_PROXY_USER\}:\$\{HTTP_PROXY_PASS}@サーバのアドレス:ポート/
> export HTTPS_PROXY=${HTTP_PROXY}

私は、「# Alias definitions.」の直前に入れた(if [ -f ~/.bash_aliases ]; then)の前に入れたけど、おそらく気分の問題。

…こっちもか。
/etc/apt/apt.conf を新規作成して、以下の内容を記載します。

> Acquire::http::proxy "http://アカ:パス@串アドレス:ポート/";
> Acquire::https::proxy "https://アカ:パス@串アドレス:ポート/";

忘れてましたよ。これやらないと、後でapt-getでこける。
`sudo apt-get update`
`sudo apt-get upgrade`

# 日本語化

読む分には英語でも良いけど、表示はして欲しい。
`sudo apt install language-pack-ja`
`sudo update-locale LANG=ja_JP.UTF-8`
`sudo dpkg-reconfigure tzdata`
`sudo apt -y install manpages-ja manpages-ja-dev`

何故か文字化けしている時は、コンソールを疑ってみるのも有り。
プロパティ
から
フォント
を選んで、日本語のフォントを選択。

# いくつか追加してみる
正直、python(Python2)とpython3はどうなってるのかよくわからん…

`sudo apt install vim-nox`
`sudo apt install git`
`sudo apt install build-essential`
`sudo apt install zsh`
`sudo apt-get install libssl-dev`
`sudo apt-get install libreadline-dev`
`sudo apt-get install zlib1g-dev`
`sudo apt-get install x11-apps`
`sudo apt-get install x11-utils`
`sudo apt-get install x11-xserver-utils`
`sudo apt-get install libsqlite3-dev`
`sudo apt-get install nodejs`
`sudo apt-get install fonts-ipafont`
`sudo apt-get install libxml2-dev`
`sudo apt-get install libxslt1-dev`
`sudo apt-get install ssh`
`sudo apt-get install sqlite3`
`sudo apt-get install python-pip`
`sudo apt-get install libhdf5-dev`
`sudo apt-get install python-opencv`
`sudo apt-get install python-matplotlib`
`sudo apt-get install nkf`
`sudo apt-get install python-scipy`

#Xの設定

Xmingを入れてみる。

Xサーバを下記からダウンロードしてインストール。
https://sourceforge.net/projects/xming/

Windows上のUbuntuから、GUIなUbuntuアプリを実行させたい。
`echo “export DISPLAY=:0.0” >> ~/.bashrc`
もしかすると、「"」が邪魔になるかもしれない。

お試しに入れてみる。
`sudo apt-get install gedit`

実験
`gedit`

デスクトップを入れる。

`sudo apt install xfce4`
`sudo apt install gnome-themes-standard`
`sudo apt install conky`

いつでも使える準備

`nano ~/.xinitrc`

中身はこちら

>    #!/usr/bin/env bash
>    export LANG="en_US.UTF-8”
>    export LC_ALL="en_US.UTF-8”
>    exec startxfce4

実行権限付けてリンク

`chmod +x ~/.xinitrc`
`ln -s ~/.xinitrc ~/.xsession`

# おまけ。
`sudo apt install python3-pip`

`mkdir ~/.pip`
`vi ~/.pip/pip.conf`

中身はこちら

> [global]
> proxy = アカウント:パスワード@プロキシサーバアドレス:ポート番号

下記は釈然としないが、まぁいいことにする。
`sudo -E pip3 install --upgrade pip`

`sudo apt install python3-venv`
`pip3 install jupyter`

`pip3 install chainer`
`pip3 install cupy`
Rustのok_orの便利の使い方

There are many methods can be used to check if an `Option` has value. For examples:

Use `is_some()` or `is_none()`:

```rust
    if x.is_some() {
        println!("test_with_if i is {:?}", x.unwrap());
    }
```

Use `match`:

```rust
    match x {
        None => {}
        Some(i) => {
            println!("test_with_match i is {:?}", i);
        }
    }

```

Use `if let`:

```rust
    if let Some(i) = x {
        println!("test_with_if_let i is {:?}", i);
    };
```

Or if `ok_or` to convert it to `Result` type and then process as `Result`:

```rust

    let _ = x.ok_or(NotFoundError).and_then(|i| {
        println!("test_with_ok_or i is {:?}", i);
        Ok(i)
    });
```


すべてのコードは　[ここ](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c864e8273fba337822f79864e1eb9a7f)
ESP32 を Arduino IDE から操作する

### ESP32 用の環境構築

ESP32用のプログラムを書けるようにする

まずは[ここ](https://www.arduino.cc/en/Main/software)からArduino IDEをダウンロード
Arduino IDE の初期設定は省略

上のバーの `ファイル > 環境設定` から `追加のボードマネージャーのURL` に `https://dl.espressif.com/dl/package_esp32_index.json` を追加する。すでに入力がある場合`;`で区切ってつなげる。
![新しいライブラリマネージャー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560003/82c953d8-50ea-c9b9-8492-3486ca2bac8a.png)

`ツール > ボード > ボードマネージャー` から右上のテキストボックスに`esp32`と入力して検索
`esp32 by Espressif Systems` を選択してインストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560003/00bc8b70-5bdc-c915-0a4c-815532b640a0.png)

以降、Arduino IDE を開いて `ツール > ボード` を見てみると確かに `ESP32 Arduino` の枠が追加されている。


### 使い方・文法など

なんと使い方は普段と同様である。特に設定を変更する必要はない。arduino のプログラムを書くときと全く同じように書き、コンパイルし、書き込めばいい。

### 謎のエラー

最後にボードへの書き込みの際に自分がハマったポイントを書き記しておく
ボードへ書き込もうとすると `exec: "python": executable file not found in $PATH` といったエラーが出て書き込めなくなることがある。普通に読むとpythonがインストールされていない、あるいはpythonのPATHが通っていない、ということだが自分の環境は確実に整っている。
しかしよく読むと"python"が無いと言っているのであって"python3"あるいは"python2"が無いとは言っていないのである。もしやと思い`sudo apt install python-is-python3`をしてみた。これで"python"が存在することになった(中身は"python3"だが)。これで書き込んでみると無事完了 　しっかり動作した。よくわからない設定ですね。
guard文について理解しよう！

今回は、guard文について学習したので、アウトプットしていきます
※以下の内容は、学習内容のアウトプット用のため、誤りがある場合があります。予めご了承ください。

#guard文とは？
***guard文を一言でいうと、条件不成立のときに早期退出を行なうための条件分岐文。***
基本的な書き方は以下の通りです。

```swift:qiita.rbvar 
guard 条件式　else{

条件式がfalseの場合に実行される文
guard文が記述されているスコープ外に退出する必要がある
(つまり、returnを記述する必要がある)
}
```
※{}←スコープ
では、基本的な例を見ていきましょう！

```swift:qiita.rbvar 


では、基本的な例を見ていきましょう！

func someFunction(){
let value = 99

guard value >= 100 else{
print("100未満の値です")//値が100未満だったので実行される
return
}

someFunction()
実行結果:100未満の値です
```
#guard文で宣言された変数や定数へのアクセス

guard文は、if文と同様にguard-let文が利用できます。

***if文を復習したい人は下記のURLをチェック！！***
if文とは？（"https://qiita.com/syunta061689/items/65d54a58936a5849a67a"）

if-let文との違いは、guard-let文で宣言された変数や定数は***guard-let文以降でも利用可能ということです。***

次の例ではguard-let文で宣言した定数intにアクセスしています。


```swift:qiita.rbvar 
func someFunction(){
let a: Any = 1 //Any型

guard let int= a as? Int //aをInt型にダウンキャストできますか？

else{//そうでないなら以下を実行してください

print("aはInt型ではありません")
return
}

print("値はInt型の\(int)です")//intはguard文以降でも使用可能！
}
someFunction()

実行結果:値はInt型の1です。
```
#if文との使い分け
では、今度は具体的な例を用いて、if文との使い分けを深堀りしていきましょう.
次の例では、if文とguard文で、2つのInt型を受け取り、両方の値を持っていればその和を返し、どちらかが値を持っていなければnilを返すという処理をしていきます。

***if文の例***

```swift:qiita.rbvar 
func add(_ optionalA: Int?,_ optionalB: Int?)-> Int?{

let a: Int      
if let unwrappedA = optionalA{ 

a = unwrappedA

}else{
print("第一引数に値が入っていません")
return nil

}
let b: Int
if let unwrappedB = optionalB{

 b = unwrappedB

}else{
print("第引数に値が入っていません")
return nil

}

return a+b 

}

add(optional(3)optional(2))//5
```

***guard文の例***

```swift:qiita.rbvar 
func add(_ optionalA: Int?, _ optionalB: Int?)-> Int?{

       

    guard let a = optionalA else{
    print("第1引数に値が入っていません")
    return nil

    }
        
        
        
    guard let b = optionalB else{
    print("第2引数に値が入っていません")
    return nil

    }

    return a+b
        
        
    }

    add(Optional(3),Optional(2))//5
```

このように、条件に応じて早期退出するコードは、 guard文を使用して実装した方が***シンプルとなります***
また、guard文では退出処理を書き忘れた場合にエラーになるため、単純ミスを未然に防げます！
Stackdriver LoggingのBigQuery関連ログをBigQueryへエクスポートする方法

BigQueryのクエリ履歴を保存しておき、後々調査できるようにするために、Stackdriver LoggingのBigQuery関連ログをBigQueryにエクスポートする方法をまとめました。

##BigQueryのデータセットを作成
Stackdriver Loggingからエクスポートしたログテーブルを格納するためのデータセットを作成しておきます。

##シンクの作成
以下コマンドで、エクスポートするログエントリのフィルタとエクスポート先を設定する**シンク**を作成します。
下記コマンドではログのresource-typeを**bigquery_resource**で絞っています。

```
gcloud logging sinks create <sink名> bigquery.googleapis.com/projects/<project-id>/datasets/<dataset> --log-filter='resource.type="bigquery_resource"'
```

##シンクの確認
コンソール画面のオペレーションロギング → ログルーターからシンクが作成されたことを確認します。
また、シンクの詳細を表示し、**writerIdentify**からサービスアカウントを表示し、コピーしておきます。

![スクリーンショット 2020-10-12 19.11.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397664/7f197a44-7b5d-5dfb-bf55-9ad2cde33d88.png)

![スクリーンショット 2020-10-12 19.18.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397664/13dd35f8-3599-149f-211f-cae9bc4449e3.png)

以下コマンドでも同様の確認ができます。

```
gcloud logging sinks list
gcloud logging sinks describe <sink名>
```


##BigQueryのデータセットの権限設定
先ほど作成したデータセットへサービスアカウントが書込みをできるように権限設定を行います。
BigQueryのデータセットの権限設定画面で、先ほどコピーしたサービスアカウントを貼り付け、BigQueryデータ編集者とし、追加を行います。

![スクリーンショット 2020-10-12 19.25.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397664/e0d71b92-c9f4-7f4f-baed-5e87519f2c4c.png)


##テーブルにデータが書き込まれていることを確認
テーブルにデータが書き込まれるまで少し時間がかかります。
データが書き込まれたら、例として以下のSQLで、どのアカウントがどのクエリを発行したかを確認できます。

```
SELECT 
  protopayload_auditlog.authenticationInfo.principalEmail AS email,
  protopayload_auditlog.servicedata_v1_bigquery.jobCompletedEvent.job.jobConfiguration.query AS query
FROM `<project-id>.<dataset>.cloudaudit_googleapis_com_data_access_20201012` LIMIT 1000

```

###参考
[BigQuery audit logs overview](https://cloud.google.com/bigquery/docs/reference/auditlogs)
[ログビューアによるエクスポート](https://cloud.google.com/logging/docs/export/configure_export_v2?hl=ja)
[BigQueryでクソクエリを検知する](http://iyuichi.hatenablog.jp/entry/2016/01/26/142133)
[Stackdriver Loggingのログを失う前にエクスポートしておく方法](https://apps-gcp.com/stackdriver-logging-export-bigquery-pubsub/)
マイグレーションファイルから Model を直接触るのはやめて欲しい

## 何が起こったのか
開発環境で新たにDBを作成。

お約束通り、

```
$ rails db:create
$ rails db:migrate
```

大量のマイグレーションファイルを一気に migrate しようとしたその時、事件は起こった。

```
PG undefined column hogehoge ...
```
的な感じで、スッと通らない🧐
嫌な予感がする。

なになに。。。。

マイグレーションファイルから直接 Model にアクセスしていたんです。

```ruby:migartion_file
...

Hoge.delete_all

...
```

みたいな感じでモデルに対して直接操作を行う処理が書かれていた😓

そのマイグレーションファイルは過去のものであり、その当時はそのモデルがあったのだろうが、今現在のソースコードには存在しない。
当然、 `そんなモデルは無いよ` と怒られたりする。


こんな感じで直接 Model に対して処理を行っているマイグレーションファイルが散見された。
結果 migrate を通すのにものすごく苦労し、過去のマイグレーションファイルにも手を入れなくてはいけなくなった。

マイグレーションファイルってそういうものなのでしょうか？
いいえ、違うはずです。

## どうすれば良いのか
マイグレーションファイルは、後々どんな人が開発に入ってきてもしっかり `rails db:migrate` が通るように積み上げていきましょう。

ファイル数が増えるのは仕方ありません。大事なのはしっかり通るかどうかです。
おそらく当時の開発者もモデルに対して処理を書いているのは分かっていたはずで、データメンテナンスがしたかったのだと思います。

既存データのメンテナンスはマイグレーションファイルでやらないで、rake task を作成するとか他にもやり方があったはずです。

自戒も込めて、マイグレーションファイルに関して気をつけるべきことを書いておきます

- マイグレーションファイルを追加した時には、 `rails db:migrate:redo` を習慣づけて redo が通ることを確認する
- 古いマイグレーションファイルを後から書き換えない（見栄えは悪いが単純に積み上げていくのがベストプラクティス）
- データメンテナンスはマイグレーションファイルの中で行わない(rake task を作ったり rails console から手動やったり他の方法がある)

Model は単なる ruby のクラスに過ぎません。
それは今後名前が変わるかもしれませんし、データの持ち方も変わるかもしれません。

マイグレーションファイルの中からモデルを直接触るのはやめましょう😭

---
どなたかの役に立てば幸いです。
【Ruby on Rails】中間テーブルを介してのデータ取得でハマりました

※画像が見づらくてすみません...アップして頂けると幸いです...
※updateアクションなどに保存失敗が考慮されていない部分ありますがまだ実装途中なのでご容赦ください...

## 開発環境
Ruby: v2.5.7
Rails: v5.2.4.4
PostgreSQL: psql (PostgreSQL) 13.0 (Debian 13.0-1.pgdg100+1)

## 概要

「ユーザーが所属するチーム名を取得して表示する」ことを目的に以下のモデル関連付けを行いました。
そして、中間テーブルを介した際に欲しい値を取得出来ずにハマりました。

- Userモデル
- Teamモデル
- TeamMembersモデル (両モデル間を繋ぐ中間テーブル)

Userモデル側では
![Userモデル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613376/dba9e5b8-316e-ad22-b97d-a2f1eb2e6ab3.png)
through を使って、Teamモデルへアクセス出来るように関連付けしています。

## ハマったこと
中間テーブルを介してteamテーブルから取得した場合とTeamモデルから取得した場合で結果が異なりハマりました。
UserController でデバッグし、2種類のパターンから取得したデータを確認したところ

- Teamモデルから直接取得した場合
![Teamモデルから直接取得した場合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613376/302cd192-aa7e-2b24-52f6-aa80b26406ef.png)


- 中間テーブルを介した場合
![中間テーブルを経由して取得した場合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613376/ae395166-851f-badb-df27-c6bcd702db21.png)

コンソール画面の最後で、チーム名を取得したいと思いコマンドを実行していますがTeamモデルから取得した場合は、チームが選択出来ているのに対して中間テーブルを介した場合は、先頭に付いている`#<Team:0x00...`の`Team`が取得されてしまいチーム名が取得出来なく、その原因が分からずハマりました。

## 結論
今回、中間テーブルを作り多対多のアソシエーションを組んでいるのでユーザーは複数のチームに所属出来ることになっています。
そのため、中間テーブルを介して取得してきたデータは複数になるので配列になっており 
(よく見たら`[]`で囲まれてました...)
チーム情報を取得するにはインデックスで個別に指定してあげる必要がありました。
なので、`@user.join_teams`を`@user.join_teams[0]`と変更することでチーム情報を取得でき`@user.join_teams[0].name`とすることでチーム名を取得することが出来ました。

要因が分かってみると基礎中の基礎でした...
反省します！！

ただ、今回の件で

- コンソール上のSQL文からもヒントを得られるということ
- クラスを調べることが原因を調べる上で有効な手段であること

を、知ることが出来ました。
(色々と調べても分からず、現役エンジニアである友人からこの手法で答えを考えてみなと教えてもらいました。)

### SQL文からヒントを得る
今回のコンソール上のSQL文ですが、
`=>   Team Load (2.4ms)  SELECT "teams".* FROM "teams" INNER JOIN "team_members" ON "teams"."id" = "team_members"."team_id" WHERE "team_members"."user_id" = $1  `
と、表示されていました。
このメッセージを調べて解釈してみたのですが
`teamテーブルから全てのデータを取得しteam_membersテーブルにteamテーブルのidを対応させる形で内部結合したのちに、team_membersテーブルのuser_idカラムを条件にして取得する`
ということでした。(間違ってましたら是非、教えて頂きたいです...)
2つのテーブルを内部結合して、複数のデータがヒットする可能性があるというヒントがSQL文にありました。

内部結合や外部結合に関しては
https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59
こちらの方の記事が分かりやすく、参考にさせて頂きました。

### クラスから原因を調べる
コンソール上で、クラスを見てみたところ
`pry(#<UsersController>)> @user.join_teams.class  
=> Team::ActiveRecord_Associations_CollectionProxy`
と、表示されました。
このクラスなんですが、RailsのActiveRecordモジュールのクラスの１つで、has_manyで関連をもたせたときにその関連で取得したデータがインスタンスに入っているとのこと。
ここから、複数データがインスタンス内に入っていると知ることが出来たかもしれません。

## 最後に
「そもそも、多対多の関係なんだから複数のデータが取得されて当然だろう」と言われてしまう内容なのですが
中間テーブルの関連付けデータを１つしか作っていなかったことで、自分で自分を惑わしてしまいハマってしまいました。
基礎、基本が重要であることに加えて今回の件で言えばクラスとSQL文から原因を掴むヒントを得られると知れたので自分としてはまた成長出来たかなと思っています。
ActiveRecordのおかげでデータベース言語を打つ場面が日頃少ないのでSQLについてはあまり触れていませんでしたが、SQLの知識も活用することで今回のように更に広い視野で原因を掴めると気付けました。
Pythonの内包表記が好きなのでmapと比較検証してみた

##はじめに
　こんにちは、麻菜結です。Pythonの内包表記が数多あるプログラミング言語の記法の中でも多機能かつ可読性が高く素晴らしい文法なのは間違いありませんが（個人の意見であり多大に偏見を含んでいる可能性があります）、よく競技プログラミングでは以下の記法が見られます。

```Python
l = list(map(int, input().split()))
```

　内包表記の敵、`map`ですね（偏見を(略）。いちいち`list`かまさないといけないmapがなんぼのもんじゃい！ということで速度を比較してみました。

##環境
wsl1のUbuntuを使います。

```Bash
$ uname -a
Linux LAPTOP-H6KC5C7N 4.4.0-18362-Microsoft #1049-Microsoft Thu Aug 14 12:01:00 PST 2020 x86_64 x86_64 x86_64 GNU/Linux
$ py --version
Python 3.8.0
```

##概要
`1` 10000行5列の非負な整数値が並ぶテキストファイルを用意する。先頭に10000と記述しファイルの列の数とする。
`2` timeモジュールのtime()を用いて始まりの時間を記録する。
`3` 行ごとに内包表記かmapでデータを格納する。
`4` data.appendを繰り返して終わったらtime()で時間を取得して`2`の値と差分を出しそれを実行時間として表示する。
`5` 5回ほど試しそれの平均を取り比較する。

##スクリプト
以下のスクリプトを用います。

```Python:内包表記
import time

start = time.time()
n = int(input())
data = []
for _ in range(n):
    data.append([int(e) for e in input().split(" ")])
process_time = time.time() - start

print(process_time)
```

```Python:map
import time

start = time.time()
n = int(input())
data = []
for _ in range(n):
    data.append(list(map(int, input().split())))
process_time = time.time() - start

print(process_time)
```

##検証
五回ぐらい実行してみてその平均を考えてみます。
まずは内包表記です。まぁまずまずですね。

```Bash:内包表記
$ for((i=0;i<5;i++)); do cat InputText.txt | py time_comp.py; done
0.0833895206451416
0.07289266586303711
0.08693099021911621
0.12533855438232422
0.09039974212646484
```
平均すると0.09541くらいでしょうか。
次にmapです。

```Bash:map
$ for((i=0;i<5;i++)); do cat InputText.txt | py time_map.py; done
0.0980367660522461
0.08674144744873047
0.11994338035583496
0.08462047576904297
0.08770060539245605
```
こちらも平均すると0.09541と同じですね。
（いや有意差が無いぞ…内包表記遅いやないかい！で記事書こうと思ったのに）

##追加検証
ラズパイ4でやりました。

```Bash
$ uname -a
Linux asana 5.4.51-v8+ #1333 SMP PREEMPT Mon Aug 10 16:58:35 BST 2020 aarch64GNU/Linux

$ py --version
Python 3.7.3

$ for((i=0;i<5;i++)); do cat InputText.txt | py time_comp.py; done
0.12080025672912598
0.10874629020690918
0.1127462387084961
0.1103978157043457
0.15588116645812988

$ for((i=0;i<5;i++)); do cat InputText.txt | py time_map.py; done
0.11949372291564941
0.11281895637512207
0.11392450332641602
0.2708289623260498
0.276080846786499
```

平均を計算すると、内包表記は0.1217、mapは0.1786ぐらいでしょうか。ここでは内包表記が優位になりました。

Windowsでやりました。

```Powershell
> cmd /c ver

Microsoft Windows [Version 10.0.18363.1110]
> py --version
Python 3.8.2

> for($i = 0;$i -le 5; $i++){cat .\InputText.txt | py .\time_comp.py}
1.0488629341125488
0.7845804691314697
1.163966178894043
0.7295846939086914
0.7399096488952637
0.8466687202453613

> for($i = 0;$i -le 5; $i++){cat .\InputText.txt | py .\time_map.py}
0.5758388042449951
0.5823671817779541
0.6683478355407715
0.6919825077056885
0.6597652435302734
0.6140100955963135
```

~~Windowsおっそ~~明らかな有意差が出ましたね、結構OSによる差があるみたいです。

##結論
　結論としては、『環境や状況に左右されやすく、どっちが良いとは断言できない。』という感じでしょうか。自分はmapの方が明らかに早いからよくつかわれていているのだと思い込んでいましたが、そういうわけでは無いみたいですね。それともまた他のアーキテクチャやOSでは差が出るのか…inputをsys.stdinにしたら変わるのか、そもそもリストアペンドが遅くて差が隠れてしまっているのではないか、メモリの効率はいかがなのかなど他にも検証事項はありそうですが、この辺でいったん終わりにしたいと思います。
　ここまでお読みいただいてありがとうございました。皆さんはどちら派ですか？
(Jest) Cannot read property 'fn' of undefined となってしまう


```
 TypeError: Cannot read property 'fn' of undefined

      14 |             mockStore[key] = {};
      15 |             Object.keys(store[key]).map(actionKey=>{
    > 16 |                 mockStore[key][actionKey] = jest.fn();

```
直ったのですが、要因が下記のいずれかだったようです。

## 考えられる要因１：jestのimportの記述

https://jestjs.io/docs/ja/next/jest-object
>The Jest Object
jest オブジェクトは、すべてのテストファイル内で自動的にスコープされます。 jest オブジェクトのメソッドはモックの作成に役立ち、Jestの全体的な動作を制御できます。 import {jest} from '@jest/globals' を介して明示的にインポートすることもできます。

```js

import {jest} from '@jest/globals'
// import jest from 'jest' //ではない
```


## 考えられる要因２：jest.config.js にresetMocks: trueを追加

https://github.com/facebook/jest/issues/9131#issuecomment-668790615

```jest.config.js
module.exports = {
    resetMocks: true,
}
```
以上で jest.fn() が呼べるようになりました。
以上です。
USB復元なソフトウェアで失われたUSBメモリデータを回復する

USBメモリは、主に作業調査データや重要な情報を保存するために使用されます。これは非常に重要で頻繁に使用されます。ただし、残念ながら、USBメモリの使用中は、ポイズニング、誤って削除したり、誤ったフォーマットをしたり、プロンプトをフォーマットしたりするなど、あらゆる種類のデータ損失が常に発生します。これに関して、USBメモリのブランドサプライヤーは私たちに合理的で効果的な解決策を与えていません、それは本当に頭痛の種です。したがって、<a href="https://jp.bitwar.net/course/usbdisk/2784.html">失われたUSBメモリデータを復元する</a>には?


まず、USBメモリが誤ってデータを削除した後、失われたデータを復元できることを理解する必要があります。


## USBメモリのデータ保存原理

多くの人々は、誤って削除されたUSBメモリ、SDカード、およびその他のストレージデバイスを使用すると、失われたデータは完全に削除されたデータと同等であると考えています。元に戻すことはできません。削除されたUSBストレージのデータはコンピューターのごみ箱に一時的に保存されず、削除されたファイルを一時的に保存するためのUSBの隠しフォルダーがないためです。それでは、USBストレージから削除されたファイルは復元できなくなりますか？


実際、ハードディスク上の完全に削除されたデータと同様に、データがUSBストレージから削除されても、その内容はすぐには破棄されません。Windowsは、データが配置されているストレージスペースを利用可能としてマークするだけなので、データはコンピューターに表示されません。新しいデータが追加されると、新しいデータは、ハードドライブで使用可能とマークされた古いデータによって占められていたスペースを上書きする機会があります。この場合、削除されたデータは破損または上書きされ、正常に復元することができません。


したがって、削除されたデータによって占有されているハードディスク領域が新しいコンテンツによって上書きされる前に、高速で効果的なデータ復元を確保する必要があります。メタデータを早く復元しようとすると、成功する可能性が高くなります。


データの復元を決定したら、すぐにこのUSBメモリの使用を中止してください！なぜなら、新しく保存されたデータは、復元したいデータとデータを上書きして、リカバリが失敗する可能性があるからです！


## USBメモリの失われたデータを復元する方法？

削除された重要なデータを復元するには、強力なUSBメモリ復元ソフトウェアであるBitwar Data Recoveryを使用して回復してください。これは、WindowsとMacの両方のオペレーティングシステムでインストールできるプログラムです。これら2つのオペレーティングシステムに接続できるUSBメモリで、写真、ドキュメント、ビデオなど、あらゆるタイプのデータを復元するためのサポート。さらに、回復が成功する前にこのデータをプレビューすることもできます。非常にシンプルで使いやすいです。


   詳しくは、「<a href="https://jp.bitwar.net/course/usbdisk/2784.html">USB復元なソフトウェアで失われたUSBメモリデータを復旧する</a>」をご参照ください。
SharePointにおいたCSVファイルを、LogicAppsを使って、AzureBlobStorageにコピーする

#なぜ残すか
CSVファイル（中身はShiftJISの日本語）が、LogicAppsによるBlobStorageコピー後、中身が文字化けしたので。

#HowTo
LogicAppsのトリガー（ファイルが作成された）において、「Infer Content Type(内容の推測?)」を「No（推測しない）」にする。
![スクリーンショット 2020-10-12 18.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43829/7359091c-0048-bb8b-fa4a-7731dee3038a.png)
これをやっておかないと、CSVをテキストファイルと推測して処理してしまい文字化けが起こる。
推測無し＝そのまま（バイナリーファイルとして？）処理してくれれば、文字化けしない。

一昔前のFTP転送のASCとBINみたいなものか。（蛇足）
【Shell】Gitでリモートのデフォルトブランチを取得する方法

- 普段Gitを用いた開発の際に、shellやCIでデフォルトブランチ名を取得した処理を記述する場合がある。
- ただ、最近諸事情でデフォルト名が**master**から**main**へ変更されたため、汎用的に取得する必要がある。
- そのため、Gitソースのリモートデフォルトブランチ名を取得する2つの方法を記録する。

## 結果
- `git remote`コマンドを利用した主に2つのやり方は下記。
    - 環境に合わせて選択

```bash
# awkの場合
# 出力 : master(or main)
git remote show origin | awk '/HEAD/ {print $NF}'

# grep / cutの場合
# 出力 : master(or main)
git remote show origin | grep 'HEAD branch' | cut -d' ' -f5
```

## 参考
- https://git-scm.com/docs/git-remote
- https://davidwalsh.name/get-default-branch-name
Azure Portal の UI を英語にする

オンラインヘルプが英語で、UIが日本語だと、機械翻訳がどう翻訳したのかを推測しなくてはいけない。そんなのは確証が得られない。なので、オンラインヘルプに合わせてUIも英語にしてしまう。

以下のメニューから、「言語」を「English」にして仕舞えば良い。

![スクリーンショット 2020-10-12 18.43.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43829/47bca474-4db7-02fe-1435-4e831082fdf1.png)
【Web】Oculus Questなどのプロジェクトをパソコンでデバッグする際に便利な方法【PlayCanvas】

基本的には、Oculus Questのデバイスをデバッグする際にはVR端末などの実機でデバッグをすることが多いと思いますが、Oculus Questの実機で確認ができない場合にはこの、拡張機能として使えるエミュレーターを使うと便利です。

WebXR emulator extensionを使ってPlayCanvasのチュートリアル、WebXR UI Interactionパソコンのブラウザから操作をしてみます。
https://developer.playcanvas.com/en/tutorials/webxr-ray-input/

### 1. 拡張機能をインストール

拡張機能
WebXR emulator extension
https://github.com/MozillaReality/WebXR-emulator-extension

FirefoxとGoogle Chromeで使用できますのでこちらから拡張機能をインストールします。
#### Firefox
https://addons.mozilla.org/firefox/addon/webxr-api-emulator

#### Google Chrome
https://chrome.google.com/webstore/detail/webxr-api-emulator/mjddjgeghkdijejnciaefnkjmkafnnje


### 2. XRのプロジェクトへアクセスし開発者ツールを開く

![](https://i.imgur.com/1cUoz66.png)

1. 対象のWebXRのプロジェクトのURLにアクセスをしブラウザのF12をクリックし開発者ツールを開きます。

https://developer.playcanvas.com/en/tutorials/webxr-ray-input/

2. 拡張機能をインストールするとWebXRの項目が増えているので、WebXRを選択
3. 使用したいVR機器を選択




### 3. WebXRのプロジェクトに入る

![](https://i.imgur.com/DiAcKWA.png)


今回のプロジェクトでは、このVRボタンをクリックすることでVRのプロジェクトに入ることができます。

![](https://i.imgur.com/8qmYITY.png)

<img src="https://i.imgur.com/tUFmh1V.gif" width="800px" />


このような形でVRのプロジェクトを操作することができます。


使用したリンク
拡張機能
- WebXR emulator extension
https://github.com/MozillaReality/WebXR-emulator-extension

デモプロジェクト
- WebXR UI Interaction
https://developer.playcanvas.com/en/tutorials/webxr-ray-input/


<font color="OrangeRed">
PlayCanvas開発で参考になりそうな記事の一覧です。
</font>
<font color="OrangeRed">

- [PlayCanvas入門- モデルの作成～ゲームに入れ込むまで](https://qiita.com/yushimatenjin/items/2a2c35fe7b1d08f9e361)
- [JavaScriptでスロットを実装する。【PlayCanvas】](https://qiita.com/yushimatenjin/items/e84c42da74df1eb07386)
- [3Dモデルのビューワーを3分で作る【初めてのPlayCanvas】](https://qiita.com/yushimatenjin/items/4b09c9e2076a0b2a6933)
</font>
- [PlayCanvasのコードエディターでes6に対応する](https://qiita.com/yushimatenjin/items/a61a21c64c1c1a550dd4)
- [Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった](https://qiita.com/yushimatenjin/items/5f0f178e8a4ba4a5ee57)
- [PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む](https://qiita.com/yushimatenjin/items/7a64220cceac66843d7d)
- [React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す](https://qiita.com/yushimatenjin/items/7c7ad5d35473c11f32f2)
- [PlayCanvasのエディター上でHTML, CSSを組み込む方法](https://qiita.com/yushimatenjin/items/814b4a32db53397219df)
- [日本PlayCanvasユーザー会 - Slack](https://join.slack.com/t/playcanvasjphq/shared_invite/zt-9aihkaep-TNA04tqgvYDFhBJABLLckw)


</font>

<font color="OrangeRed">
その他関連

- [PlayCanvasタグの付いた記事一覧](https://qiita.com/tags/playcanvas)
</font>

### PlayCanvasのユーザー会のSlackを作りました！

 少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください！

- [日本PlayCanvasユーザー会 - Slack](https://join.slack.com/t/playcanvasjphq/shared_invite/zt-9aihkaep-TNA04tqgvYDFhBJABLLckw)
Symbol from NEMやmijin Catapultで最新のアカウント情報を取得する


ページにアクセスしたタイミング、入金したタイミングで最新のアカウント情報を取得するためのTIPSです。

今回はAliceアカウントを作成し、そこに記録されたモザイク一覧情報を、ページ表示時など能動的に取りに行く方法とWebSocketを利用して入金時に受動的に取得する方法を同じロジックを使用して処理する流れで説明します。


#### 事前準備
```js
(script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-0.21.0.js';
document.getElementsByTagName('head')[0].appendChild(script);
```
chromeブラウザを開いてF12キーを押し、コンソール画面で上記スクリプトをコピー＆ペーストしてください。

#### ライブラリインポート、変数初期化
```js
NODE = 'https://sym-test.opening-line.jp:3001';
GENERATION_HASH = '6C1B92391CCB41C96478471C2634C111D9E989DECD66130C0430B5B8D20117CD';

nem = require("/node_modules/symbol-sdk");
op   = require("/node_modules/rxjs/operators");
rxjs = require("/node_modules/rxjs");
accountHttp = new nem.AccountHttp(NODE);
nsHttp = new nem.NamespaceHttp(NODE);
wsEndpoint = NODE.replace('http', 'ws') + "/ws";
listener = new nem.Listener(wsEndpoint,nsHttp,WebSocket);
```

今回使用するライブラリのインポートとノードへの接続など、必要情報を定義します。

#### アカウント準備
```js
alice = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET);

"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + alice.address.plain() +"&amount=2"
```
Aliceアカウントを作成してそこに2XYM入金しておきます。


#### アカウント情報取得時のアクションを記述
```js
accountSubscribe = function(observer){

	observer.subscribe(_=>{
		//ここにアクションを記述
        console.log(_);
	},err => console.log(err));
}
```
アカウント情報を取得した後、どのようなアクションを起こしたいかを定義しておきます。
ページに表示された項目の更新などになるかと思います。今回はコンソールに出力させるだけです。

#### アカウント情報から所有モザイク一覧情報だけを抜き出すオペレーション
```js
opMergeMosaics = function(){
	return rxjs.pipe(
		op.mergeMap(_=>_.mosaics),
		op.map(_ =>  _.id),
		op.toArray(),
	)
}
```
アカウント情報から所有Mosaicを取り出すためには、取得情報を少し操作する必要があります。あらかじめどのように操作すればMosaic情報を取り出せるかを定義しておきます。


#### アカウント情報取得
```js
getMosaics = function(){
	assetHttp = accountHttp.getAccountInfo(alice.address)
	.pipe(
		opMergeMosaics()
	);
	accountSubscribe(assetHttp);
};
getMosaics();
```
能動的にアカウント情報を取得します。


#### アカウント情報取得リスナー
```js
listener.open().then(() => {
	assetListener = listener.confirmed(alice.address)
	.pipe(

		op.mergeMap(x=>accountHttp.getAccountInfo(alice.address)),
		op.first(),
		opMergeMosaics(),
		op.repeat()
	);
	accountSubscribe(assetListener);
});

```
WebSocketを利用して、入金時にアカウント情報を取得します。
confirmedリスナーでsubscribeされる内容には所有モザイク情報は含まれていません。ですので再度getAccountInfoで情報を取得する必要があります。listenerを使用した場合はHOTストリームなので、firstで取得できた1件だけを処理に回し、あとはrepeatで再び待ち受け状態にします。

#### 入金テスト
```js
"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + alice.address.plain() +"&amount=2"

```

入金してみましょう。
AWSアカウントを作成したらやっておくことメモ

## 概要

AWSアカウントを作成したら必須でやっておかないといけないことをまとめます
他にもあったら教えて下さい:bow:

## rootアカウントでやること

- MFAを有効化
    - GoogleのAuthenticatorアプリを利用する方法にしました
    - QRコードで読み込んだが、どこかに情報を保存しておいたほうがいいかも
- IAMからadminユーザーを作成
    - PolicyはAdministratorAccess等

## adminユーザーでやること

- CloudTrailで証跡を作成
- CloudWatchで請求アラームの作成
【AWS】マネコンの新しいEC2管理画面ではCloudWatchアラーム設定出来ない件についての対応方法

# ことわり
これは2020年10月時点でのAWSマネジメントコンソールの操作画面に関する話であり、
将来的に改変されている可能性があります。


# EC2 > ターゲットグループ > モニタリング

ALBからのヘルスチェックでインスタンスの死活監視をCloudWatchアラームで行うのは定番方法であるが、
マネジメントコンソールのEC2のページで、ターゲットグループからモニタリングタグを選択すると、
旧管理画面ではCloudWatch アラームの作成・設定が行えるが、
新しい管理画面では行えなくなっている。


◼︎旧管理画面
![スクリーンショット 2020-10-12 17.51.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/8c7f7285-25d6-5eb6-2822-3a6fb5b4c566.png)



◼︎新管理画面（CloudWatchアラームに関するリンクが存在しない）
![スクリーンショット 2020-10-12 17.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/928c075d-5f69-e6e8-61c7-6eada26b5a16.png)


# 旧管理画面に変更する方法
ページ左上にて新・旧 画面の切り替えスイッチがあるので、そこから旧画面へ切り替える。
![スクリーンショット 2020-10-12 18.04.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/2d31edff-3988-7280-591c-935b992af70b.png)


# おまけ
実はマネコンのCloudWatchのページからCloudWatchアラームを作成可能であるのだが、
そこから作成すると（個人的に）凄い煩わしかったりする。
（ターゲットグループから設定出来た方が直感的に楽である）
Android Navigation Componentのツールバーのタイトル挿入機能の内部実装について調べてみた

# ToolBarにsetTitle時に変数を挿入できる。。じゃあ変数以外の部分の多言語化は？

実際に挿入している箇所は下記のコードブロックに抜き出してある通り、
labelというCharSequence型の変数に
`Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");`の
正規表現にマッチする文字が無くなるまでループを回して、
`StringBuffer title = new StringBuffer();`に連結していき
最終的な文字列を`setTitle(title);`しています。

```Java
CharSequence label = destination.getLabel();
if (!TextUtils.isEmpty(label)) {
    // Fill in the data pattern with the args to build a valid URI
    StringBuffer title = new StringBuffer();
    Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
    Matcher matcher = fillInPattern.matcher(label);
    while (matcher.find()) {
        String argName = matcher.group(1);
        if (arguments != null && arguments.containsKey(argName)) {
            matcher.appendReplacement(title, "");
            //noinspection ConstantConditions
            title.append(arguments.get(argName).toString());
        } else {
            throw new IllegalArgumentException("Could not find " + argName + " in "
                    + arguments + " to fill label " + label);
        }
    }
    matcher.appendTail(title);
    setTitle(title);
}
```

[AbstractAppBarOnDestinationChangedListener](https://androidx.tech/artifacts/navigation/navigation-ui/2.2.2-source/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.java.html)



ではlabelをどこで作っているか調べてみると、
下記のコードブロックにある通り
`setLabel(a.getText(R.styleable.Navigator_android_label));`という関数でxmlから呼び出しています。

```Java
@CallSuper
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
    final TypedArray a = context.getResources().obtainAttributes(attrs,
            R.styleable.Navigator);
    setId(a.getResourceId(R.styleable.Navigator_android_id, 0));
    mIdName = getDisplayName(context, mId);
    setLabel(a.getText(R.styleable.Navigator_android_label));
    a.recycle();
}
```
[NavDestination](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.java?source=post_page---------------------------%2F%2F%2F&autodive=0%2F%2F%2F%2F%2F%2F%2F%2F)

では`R.styleable.Navigator_android_label`を探してみると、
こちらはコンパイル時にスネークケースに置き換えられる名前で定義されている場所は、

```XML
<declare-styleable name="Navigator">
    <attr name="android:id"/>
    <attr name="android:label" />
</declare-styleable>
```
[attrs](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/navigation-common/src/main/res/values/attrs.xml?source=post_page---------------------------%2F%2F%2F&autodive=0%2F%2F%2F%2F%2F%2F%2F%2F%2F)

の箇所になります。

以上からnavGraphのxml上のandroid:labelにR.stringのリソースを連結できる仕組みになっていなかったため、
変数を埋め込んだタイトルを多言語対応させるには、
1. 遷移前の画面であらかじめ`getString(int resId, Object... formatArgs)`でタイトルを作りlabelを丸ごと変数にする
2. 変数を埋め込む画面のnavGraphだけNestedNavGraphで別ファイルにして固定文字列部分を各ファイルで実装する

になるかと思います。