OmniAICreator/Qiita-1.07M huggingface dataset:
クラウドネイティブPCを試してみよう②　～計画編その１　キックオフ～

# キックオフ
PoC を成功させるには、適切な関係者間で、PoC の目的、スコープ、アウトプットについて合意することが欠かせません。これらを最初に話し合うことで関係者間の認識のずれを抑えたり、PoC 実施期間中に注力すべきこと (議論が発散した時に立ち戻る柱) が明確になります。
関係者を集めてキックオフ ミーティングを開催し、PoC を成功させるために必要な合意をしましょう。また、キックオフを行ったメンバーで Weekly Meeting で課題や成果を共有しましょう。

1. PoC 参加者の選定
2. PoC の目的
3. PoC のスコープ (メインのシナリオ)
4. PoC の達成目標

## 1. PoC 参加者の選定
PoC を計画している限られた立場の人たちだけで PoC を進めるのは得策ではありません。組織のセキュリティ戦略の方針変更にあたっては、技術的な課題だけではなく組織的な課題がハードルになることが多々あります。例えば、オフィス アプリケーション課はゼロトラスト戦略に舵を切ろうとしても、セキュリティ課はネットワーク セキュリティの強化を目指す中期計画の真っただ中かもしれません。関係者の合意形成をするために全員の理解レベルを底上げすることは重要ですので、せっかくの PoC を合意形成の良い機会として活用しましょう。
少なくとも下記のメンバーを PoC の参加メンバーに加えることをお勧めします。

* サイバー セキュリティ (SOC/CSIRT)
* リスク管理
* デバイス管理
* オフィス アプリケーション
* ネットワーク インフラ
* 認証
* 事業部門
* 法務、総務

また、プロジェクトの実行に際して経営層のエンドースメントを得ることをお勧めします。
参加者の選定が終わったら、キックオフ ミーティングを調整しましょう。

## 2. PoC の目的
キックオフ ミーティングで PoC の目的について合意しましょう。
下記に代表的な例を記載します。

* **フィジビリティ スタディ**
多くの場合、こちらが目的になると思います。既存のセキュリティ ソリューションを置き換えや、期待している働き方の変化を実現できそうかを確認したい場合など
 
* **プロモーション**
実際に費用支出をする事業部やグループ企業、あるいは経営層などに Microsoft 365 を訴求していくためのデモ環境やシナリオを構築したい場合など

* **情報収集**
Microsoft 365 展開の要件定義に向けて情報収集をする場合や、他ソリューションとの比較検討の材料にする場合など

## 3. PoC のスコープ (メインのシナリオ)
検討しているライセンスや PoC にかけられる時間などによって機能スコープを限定する必要がある場合があります。以下のポイントについて関係者で合意しましょう。

* **どのユーザーを対象とするか**

* **どの機能を対象とするか**

* **既存のシステムの動作検証をどこまで行うか**



## 4. PoC の達成目標
PoC 終了時に行うクロージング ミーティングでは Next Action が想定される明確なアウトプットを得られるべきです。そのアウトプットを目指して普段の Weekly Meeting で課題や進捗を共有しながら PoC を進めているとも言えます。最終的にどのようなアウトプットを得られれば PoC が成功したと言えるかという達成目標について合意しましょう。
こちらは「**2. PoC の目的**」がなにか、という点によって変わってくるかと思います。

**フィジビリティ スタディ・情報収集の場合**
この場合、クラウドネイティブPCに移行した場合に**既存の業務が円滑に実行できる**か、また、**サービスやセキュリティの改善につながる**か、という点がポイントになるかと思います。具体的には以下のような点となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626626/8216e152-bd44-9848-e8ab-4c286ed34270.png)

**プロモーションの場合**
* Microsoft Threat Protection によって置き換えできるセキュリティ ソリューションを特定する
* Microsoft 365 の導入を進めるために必要なロジックとして、コスト削減できることのリストを作成する
* 現在許可されていない制限 (PC 持ち出し制限、BYOD 禁止、VPN 必須など) の中で撤廃出来ることのリストを作成する
Railsのmysql接続でユーザー権限がなくconnection errorになるときの対処法

## ユーザー作成
```
CREATE USER 'test-user'@'localhost';
```

## ユーザー確認
```
SELECT user, host FROM mysql.user;
```


## 権限確認
```
SHOW GRANTS;
```

## 権限付与
```
grant all on *.* to 'test-user'@'localhost';
```
NoCodeについて調べた

# はじめに
`NoCode` と言う言葉は結構昔からある気がしますが、最近以前にも増して聞くようになった気がします。
自身もエンジニアをしていますので、どんなものなのか知っておきたく、記事を書かせて頂きました。
調べつつ書いてるので、備忘っぽくなってますがご了承ください。

# NoCodeとは
その名の通りプログラミングコード不要で、Webサイトやらアプリが作成出来るという事を指します。
HTMLすら不要らしいです。非常に興味深い。

# ローコードとは
調べてて `ローコード(LowCode)` というワードが出たので、こちらも軽く調べてみました。
`Nocode` と似ていますが、 `ローコード` はコード量が少なくなるツールを指す言葉のようです。
メジャーなところだと、 [Monaca](https://ja.monaca.io/) は有名ではないでしょうか。
プラットフォームへの最適化(iOS/Androidへの最適化)や、開発環境構築など、
若干面倒な部分を担ってくれるツールの印象を受けました。
フレームワーク的な考えでしょうか。

# NoCodeの代表的なツール

## Bubble
https://bubble.io/
2012年にリリースされたもので、結構王道なツールのようです。
Webサイトやスマホネイティブアプリを開発することができます。
情報量が多いのかも？

## Adalo
https://www.adalo.com/
2018年にリリースされたツールで、Webアプリとスマホネイティブアプリを開発することができます。

## Glide
https://www.glideapps.com/
GoogleスプレッドシートをDBとして、PWAアプリを作成することができるツールです。
PWAアプリなので、スマホに最適化した形のアプリを作ることができます。

## Webflow
https://webflow.com/
HTML・CSSの知識が不要で、ツール上で設定してアプリを作っていく形式のツールです。
カートや決済など、ECで必要な機能も搭載しているらしいです。
いろんなアニメーションが組み込めそう。
https://webflow.com/interactions-animations

# 起動してみた
ノーコードの概要は何となく分かったので、実際に起動してみました。
今回はサイトがおしゃれだったと言う理由だけで、 `Webflow` を触ってみたいと思います。
<img width="800" alt="スクリーンショット 2020-07-27 13.47.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66330/bf49fc25-d932-0f1a-5c4f-cd7512c1301d.png">

実際にアプリを作っていこうと思いましが、予想以上にボタンがいっぱいあり、
記事が長くなりそう&理解するのに時間がかかりそうなので、別の記事で実際に作っていきます・・・(すみません)

# 調べてみた所感
思った以上に多種多様なツールがあり、できることの幅も広そうです。
実際にNoCodeで開発されたプラットフォームサービスが買収されていたり、一定の浸透はしているように思えます。
ただ、パッとみた感じメニュー・ボタンが多すぎて、とっつき辛さはありました。。

また、大量アクセスを捌けるイメージは正直あまりないのですが、その辺りはどうなのでしょうか・・・。
ただ、プロトタイプレベルであれば非常に有益そうなジャンルだと感じました。
次回移行で実際に触って、理解を深めていければと思います。
ionicの始め方＆S3簡単デプロイ

##はじめに
ionicを使ってどうやってプロジェクト作るのー？
作ったは良いけどどうやってS3にデプロイするのー？
って人向けの記事です。

##ionicの始め方
まずはじめに**terminal**でionicCLI、native-run、cordova-resをインストールします。

```
$ npm install -g @ionic/cli native-run cordova-res
```

あとはionic startを使ってプロジェクトを作成していきます。
ですがここで結構決めておかないと後々困る事があるので説明していきます。
まず下記がionic startコマンドです。

```
$ ionic start <name> <template> [options]
```

**name**の部分ですがこれはプロジェクト名です。
わかりやすい名前をつけましょう。

**template**に関しては今回作成するプロジェクトに対してionicが用意してくれているテンプレートをつけるか否かです。
--listでどういったテンプレートがあるか見れます。
例:blank, tabs等

**options**ですがこれは名前の通り色々なオプションがつけれます。
ここでangularをベースにするのかreactをベースにするのか等選べます。
--listでどういったオプションがあるか見れます。
例:--type=angular, --type=react等

**name**をhogeにし**template**をつないのでblankにし**options**でangularをベースにする場合は下記のようになります。

```
$ ionic start hoge blank --type=angular
```

##作成されたファイルの確認
では作成されたを確認していきましょう。
今回はhogeというプロジェクト名にしたのでhogeをサンプルとして書いていきます。
まず作成した対象のファルダに移動して下記のコマンドを叩いてみてください。

```
$ ionic serve
```
これでhttp://localhost:8100/home の画面が勝手に表示されたと思います。
まずhomeが作成されているのでhoge/src/app/homeに確認しにいきましょう。
home配下にhtml,ts,scss等が作成されているのが確認出来ると思います。
基本となるhome.page.htmlを見ていきましょう。

```home.page.html
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>
```

###ion-header
まずion-headerですがこれがheaderです
ion-titleの中にタイトルを書き込むと反映されます。

###ion-content
ion-contentはbodyの部分にあたります。
デフォルトだとion-contentの中にion-headerが書いてありますが,
これは画面上には反映されていません。
よく見るとion-headerの中に**collapse="condense"**というのがありますね。
**collapse="condense"**はiosに対して表示されるheaderのオプションになります。
気になる人は後ほどデプロイするのでデプロイ先をiphoneで確認してみてください。


##build
terminalで下記コマンドを実行します。

```
$ ionic build
```
そしたらhoge配下に**www**というフォルダが作成されたかと思います。
この中にbuildされたファイルが入っています。

##S3へデプロイ
自身のAWSアカウントへログインしAWSマネジメントコンソールにてS3を検索しS3へ移動します。
新しいバゲットを作成するをクリックし任意の名前やリージョンを指定しバゲットを作成してください。
作成したらバゲット一覧の中に新しく作ったバゲットがあるのでクリックします。
次にアップロードボタンがあるのでクリックするとアップロード画面が表示されますので**www**配下のファイルを全てアップロードします。
あとはバゲットポリシーを設定し完了です。
とりあえず下記のものがパブリックですが設定すると見れます。
セキュアにしたい場合はipアドレスの設定をしてください。

```
{
    "Version": "2008-10-17",
    "Id": "PolicyAccessCtrl",
    "Statement": [
        {
            "Sid": "StmtAccessCtrl",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::作成したバケット名/*"
        }
    ]
}
```

##最後に
ionicの基本的な始め方を書きましたがこれがモバイルアプリ作成の第一歩です！
ionicを使うとiosアプリやandroidアプリをデプロイ出来ます。
[DeployGate](https://deploygate.com/?locale=ja)を使い簡単にデバックも出来ますので今度はDeployGateを使った記事を書いてみようと思います。

##参考
[Ionic公式ドキュメント](https://ionicframework.com/jp/docs/)
中華の闇　携帯電話

# 4月1日から使い始めた中華製のスマホUMIDIGI X ついに壊れました。(７月２６日現在)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/e8c7950c-df6a-341f-1ca1-6a219b76a1dd.png)

#115日間で壊れたことになります。
## 壊れた場所は、type cのソケット毎日、PCに接続して充電したり、Visual studioのadb に接続していた。
原因は、ケーブル側のソケットの形状が悪いために、本体側のソケットが破損される。
#アマゾンで購入すると1年間の保証付きである。
当然修理依頼のチャットを出した。らちが明かないので、電話した。
#なんだ修理してくんない!,金返すから送れになった。
中国人女性は、電話の向こうで賢そうな応対してくれた。(ハッカーに対して高圧的なしゃべり)
日本語勉強しても、日本的な女性的なものの勉強がたりない。
要するに、通常の使い方していたかいなかを論破しようとした。
この電話を使い始めた理由は、ジャーナリストから中華スマホで個人情報抜かれているエビデンス作ることが目的である。
#中華スマホは、個人情報を盗むか?
## コロナアプリで　日本政府に個人情報を盗まれました。
## googleに個人情報を盗まれました。
## UmidigiにUpdateする際に位置情報と電話番号盗まれました。
## Rakuten unlimitに位置情報　盗まれました。
## 家内に顔認証を破られました。
# 個人的感想は、そんなチップ入ってないし、OSもGoogleにおんぶにだっこみたいな携帯だった。
AmazonのUmudigi担当に勧められるままにS5Proを注文した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/e7e111a4-40e6-c981-e12b-22dc5c1e126b.png)
Mediatek Helio G90T 2GHz 8 Core Arm めちゃくちゃはやい。
富岳と同じ系統のCPU RISC(単純命令セット)パケットモニターするのにUserLandを使っているんだけど相性抜群によい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/a8d73ca1-744a-47a4-1653-6a751cbc673c.png)
携帯でLinux/Ubuntuとかpython,C,Fortranまで使える。
結局、1万円高いデバイス買わされた。
#まあいっか～! Rasberry pi4が買えなくなった!こうなったらUSBにGPIOつけたら、無停電付きのRaspiにしてやるぞ。
RaspberryPiのカメラでリアルタイムでゴニョゴニョするためのコード

使いたいライブラリに合わせて適宜利用すればOK。
物体検知とかにどうぞ。

# PILバージョン
```python:PILバージョン
import os
from datetime import datetime
from PIL import Image
from picamera import PiCamera

camera = PiCamera()

while True:
    nowtime = datetime.now()
    filename = nowtime.strftime('%H%M%S') + '.png'
    camera.capture(filename)
    with Image.open(filename) as frame:
        pass # ここでゴニョゴニョする
    os.remove(filename)
```

# cv2バージョン
```python:cv2バージョン
import os
from datetime import datetime
import cv2

cam = cv2.VideoCapture(0)

while True:
    nowtime = datetime.now()
    filename = nowtime.strftime('%H%M%S') + '.png'

    is_success, frame = cap.read()
    pass # ここでゴニョゴニョする。is_successは上手く撮れているかどうかのフラグなので利用しなくてOK
    os.remove(filename)

cam.release()
cv2.destroyAllWindows()
```

[カメラも付くケース](https://www.amazon.co.jp/dp/B082Y8BKDQ/ref=sspa_dk_detail_1?psc=1&pd_rd_i=B082Y8BKDQ&pd_rd_w=nXEaq&pf_rd_p=6413bd85-d494-49e7-9f81-0e63e79171a9&pd_rd_wg=H4rZc&pf_rd_r=DB24ZPQ6TA7RTV78HWM6&pd_rd_r=c5ea11b5-1144-4186-bd3e-f46a394c02fb&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEyMjA1UUdXMDMxUjZKJmVuY3J5cHRlZElkPUEwNDA4NzczM05ERzdUTk1KSUJOTCZlbmNyeXB0ZWRBZElkPUEzTENKRUJENTYwNkNYJndpZGdldE5hbWU9c3BfZGV0YWlsJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ==)
「Unknown archive format」Drupal7をDrushでCoreアップデート時にerror

#実現したいこと
DrushにてDrupal7のCoreをバージョンアップしたい
##発生している問題
Drushコマンド `drush up drupal -y`時にerrorでUpdateできず。
errorは以下の通り。
`Unable to extract /tmp/drush_tmp_1595810392_5f1e225836bab/drupal-7.72.tar.gz. Unknown archive format. ` 
##前提条件
- さくらレンタルサーバ PHPバージョン 7.4
- Drupal 7
- Drush ver8
- Drupal Core ver 7.71 → 7.72にしたい

##試したこと
- composerをバージョンアップ　→　改善しない
- PHPバージョン確認　最新版の7.4(2020/07/27現在)
- tar.gzファイルはサーバ上で展開可能の状態

##解決策
**PHPのバージョンを7.4から7.3に下げる**
![2020-07-27_10h11_52.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272611/2d6a4959-8742-a920-bace-7f57a858f8eb.jpeg)

原因は、さくらサーバのPHPバージョンが7.4だった事でした。
まさかPHPのバージョンアップが原因だとは予想できませんでした。
PHPのバージョンを7.3に下げることでアップデートが可能になりました。
アップデート完了後は、PHPのバージョンを7.4に戻すことを忘れないようにしてください。

さくらレンタルサーバは2020/07現在、自動でPHPをアップデートしているのでご注意ください。

##参考にしたサイト


[drush up fails with Unknown archive format](https://drupal.stackexchange.com/questions/291489/drush-up-fails-with-unknown-archive-format)
【備忘】5G時代に備えるべきIoTを狙ったサイバー攻撃と対策を復習


# 初めに
5Gの登場により、医療・福祉・交通インフラ・農業など様々な分野でのIoT活用が期待され、急速に成長しています。
IDC は、2025 年までに世界で 416 億のデバイスが接続され、合計 79.4 ゼタバイトのデータを生成すると予測しています。
そして、Statista は、2025 年にオンラインで接続されるエンドポイントは 750 億を超えると予測しています。

そんな中、この注目市場を狙った攻撃手段が次々と出てきており、それらへの対策はビジネスしていく中だけでなく、生きていく上で必須と言えるでしょう。

今ある主な攻撃と対策を以下にまとめました。

# IoTデバイスを狙うマルウェア「Mirai」
2016年頃、世界的に感染が広がったマルウェアが「Mirai」です。
ハッカーズネットにソースコードを投稿した者が「Anna-senpai」（杏奈先輩＠＠？）という、かわいらしいネーミングですが、その影響はかわいらしくないものでした。
インターネットにつながる様々なIoT機器を標的に、これら機器の脆弱性を利用してMiraiが感染すると、攻撃者が遠隔操作可能な状態（踏み台）になり、
一度踏み台になると、攻撃者は任意のコマンドを多数の踏み台に対して同時に実行できるようになります。
こうした踏み台を使って、大規模なDDoS攻撃（分散（distributed）DoS攻撃）が発生したことが世界的にも話題になりました。

# Miraiによる被害事例 米国DNSを狙ったDDoS攻撃
発端といえる事例です。
2016年後半、米国でDNS（Domain Name System）がDDoS攻撃を受けて、
Twitter、Spotify、Netflix、ウォールストリートジャーナル等々の著名なインターネットサービスが被害を受けました。
これは「Mirai」に感染したIoT機器から発せられたと言われています。
※他にも沢山ありますが割愛

# 続々と登場する「Mirai」の亜種
亜種の多くは特定のIoT機器固有の脆弱性を突いて感染、初期パスワードから変更しても防御不能
• ポート番号81で管理画面にアクセス可能なOEM生産のIPカメラに感染するPERSIRAI
• 様々なカメラやルータ等が持つ、機種固有の脆弱性を突いて感染するReaper (IoTroop, IoT reaper)
• ポート番号52869経由でネットワークコントローラチップ用のRealtek SDKの脆弱性を突いて感染するSatori／Okiru

# IoT機器を破壊する「BrickerBot」
第三者へのDDoS攻撃に悪用せず、IoT機器の利用者に直接被害を与えるウイルス
「Mirai」と同様に、初期ユーザ名＆パスワードでtelnetで不正ログインして感染
感染後、ストレージ機能とカーネルパラメータの設定変更、インターネット接続妨害、動作速度低下、
機器上の全ファイル消去等の致命的な改変を行い、最終的に使用不能に

# 原因と対策
原因の多くは、ID・パスワードがデフォルト設定のままになっていたり、
最新ファームウェアバージョンが適応されていなかった等がほとんど。

まずは、
・ID・パスワードはデフォルト設定にせず、複雑なものを設定。
・ファームウェアは最新を適応する
は必ず行い。さらに重要なものは
・不必要にインターネットに露出させない。
・ネットワーク・セキュリティ監視を行う。
・セキュリティサービスの導入による強化を検討。
等を行うとよい
【JavaScript】【初心者】コールバック関数について

##コールバック関数とは

コールバック関数について備忘録を残します。
コールバック関数とは**関数の引数として渡される関数**のこと。
直訳の【呼び返す関数】という意味で好きなタイミングで呼び返すことができる関数です。






##例

下のコードでは、hogeがbaseのコールバック関数として渡されています。

```javascript:sample.js

const hoge = () => {
    console.log('hogeが呼び出されました。');
};

const base = (callback) => {
    console.log('baseが呼び出されました。');
    callback();
};

base(hoge); //base(hoge());ではないことに注意!

```

```tarminal:実行結果
baseが呼び出されました。
hogeが呼び出されました。

```


##注意
上の例で、base(hoge());としてしまうと。base(undefined);となり、エラーが発生します。
hogeはreturnのない関数なので返り値はundefinedです。よって実行前の関数自体を引数として渡すように注意して下さい。
登録されている単語に従って文字列を切り出す(Python)

登録した単語で分割するクラスを作りました

#概要

文字列を登録済みの単語で分割します

"かも","かもめ","めだか" とう言う単語が登録されている時、
"かもめだか"という単語を"かも/めだか"という単語に分割します。

"かもめ/だか"では、"だか"という単語がないので分割されません。

なお、"かも/めだか"というふうにデリミタ(区切り文字)があっても認識されます
厳密には「登録した単語の頭文字以外の文字」をすべてデリミタとしてみなします

もし、'だか'という単語が登録されていて、
"かもめ/だか","かも/めだか"の2通り二分割される場合、
前の文字数が長い、"かもめ/だか"として認識されます。
(基本的には、二通りの分割方法があるパターンは考えていません)

#使い方

```python
s = StringChopper()

s.Register('かもめ')
s.Register('かも')
s.Register('めだか')

chop = s.Chopper('かもめだか')
if chop is not None:
    print('/'.join(chop))
else:
    print('分割失敗')
```

#ソース

```Python

class StringChopper:
    def __init__(self):
        self.initialdic :Dict[str, List[str]] = {}
    
    def Register(self, name):
        length = len(name)
        if length == 0: return

        page = self.initialdic.get(name[0])
        if page is None:
            self.initialdic[name[0]] = [name]
            return

        pagelength = len(page)
        for i in range(0, pagelength):
            if name == page[i]: return
            if name < page[i]:
                page.insert(i, name)
                return
        page.append(name)
    
    def Print(self):
        for value in self.initialdic.values():
            for name in value:
                print(name)
    
    def Serialize(self) -> List[str]:
        result = []
        for value in self.initialdic.values():
            for name in value:
                result.append(name)

        return result
    
    def Deserialize(self, datalist):
        for data in datalist:
            self.Register(data)

    def Chopper(self, namelist : str) -> Optional[List[str]]:
        if len(namelist) == 0: return []
        tmpname = namelist

        page = None
        while 0 < len(tmpname):
            page = self.initialdic.get(tmpname[0])
            if page is not None:
                break
            tmpname = tmpname[1:]
        if len(tmpname) == 0: return None

        for s in page:
            if tmpname.startswith(s):
                result = [s]
                a = self.Chopper(tmpname[len(s):])
                if a is None: continue
                result.extend(a)
                return result

        return None

```
Ionic(Angular)アプリのフロントエラーを検知する

AndroidやAppleの開発者コンソールではクラッシュ報告をレポートされますが、発生した具体的なエラー内容は見ることができません。
アプリで密かに発生しているエラーを拾い上げるため、自前のエラーハンドラークラスを作成してエラー情報を送信することが目的です。

フロントエラーの検知方法の考えは、下記記事”失敗談1 : クライアントサイドのエラー調査”を参考にしました。
[Qiita - SPAリリースの失敗から学んだこと @norih](https://qiita.com/norih/items/42182500630417559a25)

##環境
- Ionic4
- Angular8

Ionic4のプロジェクトで動作検証しましたが、Angularの機能しか使っていないのでAngularでも動作するかと思います。

##自前のエラーハンドラークラスを用意する
アプリ内全体でなんらかのエラーが発生した際に動き出すクラスを用意します。
`consolole.error`を削除するとコンソールにエラー情報を表示しなくなるので、削除しません。

```app-global-error-handler.ts
@Injectable({ providedIn: 'root' })
export class AppGlobalErrorHandler implements ErrorHandler {

  constructor() { }
  handleError(error) {

    console.error(error); // デフォルトのコンソールエラーのため、削除しない。

  }
}
```

##ErrorHandlerの代わりに自前のエラーハンドラークラスを使用する

ErrorHandlerのトークンが呼び出された際に、`AppGlobalErrorHandler `のインスタンスを返すように、ルートモジュールの`providers`を修正します。

これでJavaScriptでエラーが発生した際に、`AppGlobalErrorHandler `の`handleError `メソッドが動くようになります。

```app.modules.ts
@NgModule({
  ...
  providers: [
    ...
    { provide: ErrorHandler, useClass: AppGlobalErrorHandler },
  ],
})
```

##自前のエラーハンドラークラスを実装する
あとは`error`オブジェクトの情報や端末情報などをhttp通信で送信するなど、エラー発生時にやりたいことを実装します。


####送信データ例

- エラースタック
- エラー発生ページ
- ユーザ属性
- 端末のOSとそのバージョン
- 端末の製造情報

```app-global-error.handler.ts
@Injectable({ providedIn: 'root' })
export class AppGlobalErrorHandler implements ErrorHandler {

  constructor(
    private errorLogService: ErrorLogService,
  ) { }
  async handleError(error) {

    console.error(error); // デフォルトのコンソールエラーのため、削除しない。

    const errorLogRequest = new ErrorLogRequest();
    errorLogRequest.message = error.message;

    // ... 他にいろいろな処理を行う
    
    // エラーログ送信用サービスでエラー情報を送信する
    this.errorLogService.postErrorLog(errorLogRequest)
      .then(request => {
        console.log('success send error log');
      })
      .catch(request => {
        console.log('failed send error log');
      });
  }
}
```
##おわりに
フロントのエラー検知を備えておくと障害時に調査に役立ちますし、開発者が想定しないことが起こっている際に早く気づくことができます。重要な機能だと感じています。

特に利用者のフィードバックが少ないアプリだと積極的にアプリで何が起きているか把握する仕組みが必要です。
VRソフトウェア開発ツール

SketchUp Studio
SketchUp Studioは建設業界と建築に焦点を当てた強力な3Dモデリングツールであり、仮想現実アプリの開発に使用できます。 建築、商業用インテリアデザイン、景観建築、住宅建築、3Dプリント、都市計画などのユースケースに役立ちます。

使いやすいUIを備えた強力なデスクトップツールを入手でき、建物を3Dで設計するのが簡単になります。 このツールを使用すると、魅力的なグラフィックを使用できます。さらに、SketchUp Studioは、建設業界固有の機能を提供します。 このツールを使用して、VRアプリと拡張現実（AR）アプリの両方を構築できます。

SketchUp Studioの学習に役立つ学習リソースとチュートリアルがあり、それらのWebサイトのSketchUpヘルプページからアクセスできます。 価格プランは、ウェブサイトのプランと価格のページで確認できます。

VR開発の詳細については、https://bap-software.net/knowledge/vr-development/

Maya
MayaはまだオートデスクのVRソフトウェア開発ツールです。 Mayaでは、3Dアニメーション、モーショングラフィックス、VFXソフトウェアを作成できます。

Mayaは、ダイナミクス、エフェクト、3Dアニメーション、3Dレンダリング、3Dシェーディング、3Dモデリング、パイプライン統合、モーショングラフィックスなどのツールを提供する強力なソフトウェアです。Mayaが提供する機能の詳細については、Webサイトの機能ページにアクセスしてください。

Webサイトのサポートと学習ページには、この製品スイートを学習するための優れたリソースが用意されています。 このWebページでソフトウェアをダウンロードし、トラブルシューティングガイドにアクセスすることもできます。
Oculus Medium
Oculus Rift S、Oculus Quest、Oculus GoなどのVRプラットフォームの有名なプロバイダーであるOculusも、Mediumという強力なVR開発ソフトウェアを提供しています。 3Dアセットを作成できる包括的なツールです。

作成したVRアセットをスカルプト、モデリング、ペイントできます。 初心者でも、Oculus Mediumを使用すると、没入型の環境をすばやく作成できます。

Oculus Medium YouTubeチャンネルには多くの役立つビデオがあり、それらを表示して、このツールの詳細情報を取得できます。 Oculus Mediumについて学ぶには、包括的な「ユーザーガイド」を読むこともできます。
ローカル環境からGCPシークレットマネージャーの値を取得する


## 前提

担当プロジェクトのシークレット情報をGCPシークレットマネージャーで管理することになりました。

最終的にはCI/CDを任してるCircle CIからシークレットマネージャーにアクセスして値を取得するようにしますが、その前段として自分のローカル環境から値を取得できるか確認します。

## 1. ローカルにGoogle Cloud SDKをインストールする

こちらの記事（[メモ Google Cloud SDKをHomebrewを使ってMacにインストールした時のメモ - Qiita](https://qiita.com/acevif/items/abb155d1e0517b30242a)）の通りでうまくいきましたので、適宜引用しながら作業を追ってきます。

> 1.  brew cask install google-cloud-sdk する.

この通りにコマンドを実行する。

```zsh

brew cask install google-cloud-sdk
```

gcloudとgsutilがインストールされる。

次はパスを通す。自分の場合は`.zshprofile`。

> brew cask infoで出る下記のような注意書きの通りに .profile や .bashrc や .zshrc などの適切なところに指定のスクリプトを実行する2行をいれる. これをしないとパスが通らない.


```zsh

# 末尾に追加
source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc
source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.zsh.inc
```

ターミナルを再起動して、以下のコマンドを実行する。

```zsh

gcloud -v
# バージョン返ってきたらok
gsutil -v
# バージョン返ってきたらok
```

これでインストール完了。

## 2. gcloud初期化

こちらの記事（[macOS 用のクイックスタート  |  Cloud SDK のドキュメント  |  Google Cloud](https://cloud.google.com/sdk/docs/quickstart-macos?hl=ja)）の通りで間違いないです。



## 3. シークレットの取得

自分のアカウントにシークレットマネージャーへのアクセス権限が付与されてれば、以下のコマンドでシークレットの値を取得できる。

```zsh

# 設定してるシークレットの一覧を取得
gcloud secrets list
# シークレットの値を取得
gcloud secrets versions access latest --secret=“シークレット名”
# 任意のバージョンの値を取得する（上記コマンドのlatestを任意のバージョンに書き換える）
gcloud secrets versions access 1 --secret=“シークレット名”
```

## おわりに

本当はローカルじゃなくてコンテナ作るのがいいんだろうな。
Railsでファイルのアップロード機能を簡単に追加する方法

#プログラミングの勉強日記
2020年7月27日　Progate Lv.226

#概要
　[こちらの記事](https://qiita.com/mzmz__02/items/61423e7259c4624f3010)で扱ったgemのCarrierWave用いる。アップロードしたファイルの保存先はデフォルトでpublic/uploadsになる。
　前提として、`scaffold`のコマンドで作られている。（以下のは例）

```:ターミナル
rails generate scaffold user name:string email:string image:string
```

#使い方
　CarrierWaveを使うためにGemfileを編集する

```:Gemfile
gem 'carrierwave'
```

 Gemfileを編集したら、CarrierWaveをインストールする。

```:ターミナル
bundle install
```

アップローダークラスとカラムの紐づけをする。

```ruby
class モデル名 < ActiveRecord::Base
  mount_uploader [:カラム名], [アップローダークラス]
end
```
　AvatarアップローダとUserモデルに作成したフィールドavatarを関連付ける。（avatarにアップロードした画像をAvatarアップローダが処理するイメージ）

```ruby:app/models/user.rb
class User < ActiveRecord::Base
  mount_uploader :avatar, AvatarUploader
end
```

　これに合わせてビューファイルを変更する。

```ruby:app/views/users/_form.html.erb
<div class="field">
  <%= form.label :avatar %>
  <%= form.file_field :avatar, id: :user_avatar %>
</div>
```

```ruby:app/views/users/show.html.erb
<p>
  <strong>Avatar path:</strong>
  <%= @user.avatar_path %>
</p>
<p>
  <%= image_tag @user.avatar_path %>
</p>
```

モデルとビューを修正したので、これでCarrierWaveの基本機能が使えるようになった。

参考文献
https://pikawaka.com/rails/carrierwave
https://www.sejuku.net/blog/65175
さくっとVue3をWebブラウザで試したい時に使えるTips

Vue3がRCになっていよいよリリース間近ですね。
環境構築不要でさくっとVue3の動き確認したいなーって時に使えるTipsを紹介します。

# script要素を追加し、CDNからVue3のスクリプトを読み込む

適当なサイトを開き、以下コードをWebブラウザのdevtoolsのconsole上で実行してください。
srcにvue-nextのCDNのアドレスを設定した`script`要素を作成し、body直下に追加しています。

```js
document.body.appendChild(
  (() => {
    const s = document.createElement("script");
    s.src = "https://unpkg.com/vue@next";
    return s;
  })()
);
```
<img width="622" alt="スクリーンショット 2020-07-25 5.34.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/60496e1e-10a1-a861-f7ae-bd5f2bf0b5a5.png">

これでvue-nextのCDNが読み込めたので、console上でVueが使えます。
あとはやりたい放題使えばOKです。

<img width="800" alt="スクリーンショット 2020-07-25 8.32.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/77916bd9-be66-c03d-ee2f-b44ca39fc717.png">


# 動作確認コードの一例

参考までに、自分で動かしてみたコードを紹介します。

## Vueコンポーネントの動作確認

DOMを追加して、そこに`Vue.createApp()`でVueをマウントしコンポーネントを表示しています。`template`と`setup()`内を調整すればほとんどの動作を確認できます。

```js
// マウント用のDOMの追加
document.body.insertBefore((() => {
  const d = document.createElement("div");
  d.id = 'vue-app';
  return d;
})(), document.body.firstChild);

// DOM要素へのVueコンポーネントのマウント
const app = Vue.createApp({
  template: `<my-component />`
})
app.component('my-component', {
  template: `
    <h1>Hello Vue3</h1>
    <input type="text" v-model="message"/>
    <div>{{ upperCase }}</div>
  `,
  setup() {
    const message = Vue.ref('')
    const upperCase = Vue.computed(() => message.value.toUpperCase())
    return {
      message,
      upperCase
    }
  }
})
app.mount('#vue-app')
```

[yahoo! JAPAN](https://www.yahoo.co.jp/)で実行してみた例
![Jul-25-2020 08-27-10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/94888ac5-8dd2-1547-635b-0af96983bbe1.gif)



## リアクティブの動作確認

Vue3のComposition APIのリアクティブAPIの動作確認です。`ref`でも`reactive`でも同様にリアクティブに処理を再実行してますね。

```js
const reactiveObj = Vue.reactive({ count: 1 });
const refObj = Vue.ref(1);
const sumVal = Vue.computed(() => reactiveObj.count + refObj.value);

console.log(sumVal.value); // 2

refObj.value = 3;
console.log(sumVal.value); // 4

reactiveObj.count = 10;
console.log(sumVal.value); // 13
````

<img width="756" alt="スクリーンショット 2020-07-25 5.43.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/45571c67-8cb9-3d0f-5005-b8aa52b88fa1.png">


## リアクティブの消失の動作確認

Vue Composition APIを使う上で注意が必要なリアクティブの消失について動作確認です。リアクティブな値から分割代入でプリミティブな値を取得すると、リアクティブを消失するのが良くわかると思います。

```js
const reactiveObj = Vue.reactive({
  num: 1,
  str: "hoge",
  arr: [1, 2, 3],
  obj: { foo: "foo", bar: "bar" },
});

const { num, str, arr, obj } = reactiveObj;

console.log(Vue.isReactive(num)); // false
console.log(Vue.isReactive(str)); // false
console.log(Vue.isReactive(arr)); // true
console.log(Vue.isReactive(obj)); // true
```

<img width="724" alt="スクリーンショット 2020-07-25 6.33.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/a040dec9-8b93-8a03-eb8b-603df4a0ba47.png">

# 終わりに

とても簡単ですが「さくっとVue3をWebブラウザで試したいときに使えるTips」でした。
ちょっと動きを確認したいときとかに地味に便利なので使ってみてください〜。
【iOS】 ReactorKitでStoryboardを使う方法

## 経緯
[ReactorKit](https://github.com/ReactorKit/ReactorKit)を使っているプロジェクトでStoryboardでレイアウトした画面を表示させたら画面に何も表示されなかったので原因を調べてみた

## 対応方法
最初はbind内でloadView（）を呼んでViewの生成を行っていたがStoryboardを使っているのにloadView()を呼びたくないとおもい公式ドキュメントを調べてみたらそれ用のプロトコルが用意されていた

```swift
func bind(reactor: Reactor) {
  loadView()
}
```

以下のようにStoryboardViewプロトコルを使用するとViewの生成が行われ画面が表示された

```swift
class MyViewController: UIViewController, StoryboardView {
  func bind(reactor: MyViewReactor) {
    // this is called after the view is loaded (viewDidLoad)
  }
}
```
Rails環境下でjqueryにて画面のバリデーションを設定する

###  <font color='lightblue'>背景</font>
現在、自分が作成しているポートフォリオについて、データの更新、新規登録時にバリデーションをかけたい項目がある。個人的には、Vue.jsを学習中のため、こちらで実施したかったが、いろいろ試した結果について次のとおりにまとめる。

###  <font color='lightblue'>環境</font>
| 項目              |               内容 | 
|:-----------------|:------------------:|
| OS.Catalina      |           v10.15.4 |      
| Ruby             |            v2.5.1 |  
| Ruby On Rails    |           v5.2.4.3 |   
| MySQL            |           v5.6 |   

###  <font color='lightblue'>対応手順</font>

【手順1】
1)公式サイトからバリデーションを実行してくれるJSファイルをダウンロードする
jquery-validation-1.19.1/dist/jquery.validate.js
jquery-validation-1.19.1/src/localization/messages_ja.js

2)以下に格納する
app/assets/javascripts/jquery.validate.js
app/assets/javascripts/messages_ja.js

3)JSファイルの設定
①対象となるフォームオブジェクトのIDを設定する(２行目)
②rulesオブジェクトに対象となるテキストボックスのオブジェクトIDを設定する（４行目）
→ここで、正確なオブジェクトidはデベロッパーツールを使用すること
③対象オブジェクトにバリデーションに引っかかった場合の出力メッセージを設定する(9行目)

```javascript:app/assets/javascripts/genre_edit_vaidate.js
1 $(function(){
2     $("#new_genre").validate({
3        rules : {
4             "genre[in_genre]": {
5                 required: true
6             }
7         },
8         messages: {
9             "genre[in_genre]": {
10                 required: "必須入力です",
11             }
12         }
13     });
14 });
```

4)htmlファイルの設定
今回例では、ジャンル名の登録テキストボックスに必須バリデーションを設定した。

```erb:new.html.erb
<title>メモのカテゴリを登録</title>
<p>ジャンルの追加<br></p>
<%= form_for (@genre), url:  genres_path(:id), method: :post do |f| %>
      <%= f.label :id,"ユーザ名" ,class: "com-box" %>
      <%= f.text_field :user_id ,class: "com-box" ,value: current_user.name, readonly:true %>
      <%= f.hidden_field :user_id  ,value: current_user.id%>
      <%= f.label :id,"ジャンル名を登録してください" ,class: "com-box" %>
      <%= f.text_field :in_genre ,class: "com-box" %>
      <%= f.submit "登録する" %>
<% end %>
```

<img width="150" alt="test.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647299/4b552b40-0aae-39c0-9d1c-5490d92e5436.png">
→「必須入力です」と出力されています。あとはスタイルシートで加工すればOKです。簡単にできました！

以上です。

###  <font color='lightblue'>参考文献</font>

[【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装 〜導入編〜](https://qiita.com/tiphp452/items/2ac12f7d95a230ef9d02)

[【Rails】簡単にバリデーションチェックできるjQueryValidationプラグインの実装手順を解説](https://techtechmedia.com/jquery-validation-plugin-rails/#jQueryValidation-2)

[jQuery Validation 簡単にフォームをチェック バリデーション](https://sys-guard.com/post-14579/)
圧力センサーFSR402で水位計の製作に挑戦した話(失敗談)

#1．農業農村工学における水位センサーの重要性
農業農村工学分野，特に水文分野においては河川やため池，ダム，井戸などの水位を知るというのは治水・利水の両方にとってとても重要です．

治水面では，豪雨時に河川堤防から水が溢れないように水位を監視するために用いられたりします．
利水面からみれば，かんがい用水をどれだけ確保できるかを知るためにも河川や井戸，ため池などの水位を監視している必要があります．

水位の観測に用いられる水位計は一般的に高価(数万～数十万円)です．
人命に関わるダムや大規模河川の流量観測には信頼性の高い高価な水位を用いるのが妥当だと思います．

しかし，近年は農業分野におけるIoTの活用や農業のICT化などを推進するためにも，田んぼ1枚1枚など比較的小規模で数が多い水域に水位計を設置して水位をモニタリングすることが求められています．
ですが，1本数万円もするような水位計をわざわざ全ての田んぼにつけるようなことは現実的とは言えません．

そこで，最近では安価な水位計の開発が求められています．


#2．水圧センサーFSRシリーズ
![FSRシリーズ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666904/f08c8864-2892-5cd4-a22c-952109625277.png)
FSR402とはInterlink Electronics社が販売する圧力センサーFSRシリーズの1つです．感圧面に力を加えると2枚の膜の接触面積が変化し，それが抵抗の変化として出力されるというものであり，ひずみゲージと同じ挙動を示すというのが売りです．

水位を測る場合には水圧を測るのが一般的です．
>
JFE アドバンテック株式会社　水位計　各種測定方式
https://www.jfe-advantech.co.jp/products/water-technical-method.html

FSR402の値段は数百円程度とお得だったので，不安はありましたが，このセンサーを使って水位計の製作に挑戦してみました．
ですが，やっぱり安かろう悪かろうということで使い物になりませんでした．

#3．FSR402のテスト
さっそく，FSR402のテストに取り掛かりました．
データシートと同じ回路を製作し，圧力の変化に対する抵抗および電圧の変化をテスターで確認しました．
結果としては確かに下記のデータシートのような挙動は確かに示しました．

しかし，この時は指で感圧面を押していたので，当然圧力は一定とはなりません．
なのである異変を見落としていました．

その異変とは，このサイト↓でも紹介されているのですが，実はこのセンサーに一定の圧力を加えてていても抵抗値がどんどん低下していくというものです．

>
ラズパイでコーヒーポットIoT
https://shirouzu.jp/tech/coffeepot-iot

当たり前ですが，そんなセンサー使い物になりません．
水圧センサーとして使うために防水加工をする以前の問題ですね．

原因を完全に突き止めることはできませんでしたが，おそらくはこのセンサーを構成する2枚の膜がコンデンサーのような働きをしてしまっているのではないかと思います．
現に印可する圧力が一定になった直後も抵抗値は下がり続けますが，時間が経つにつれて抵抗値の下がり具合が小さくなっていました．

#4．おわりに
FSR402は水位計用の圧力センサーとしては使えませんでした．
となると，やはり水圧を測ろうとしたらひずみゲージを使う必要があるのかなぁと思いました．
(ひずみゲージ式だと，材料の1つであるダイアフラムの入手性が悪いので正直困るのですが．．．)

めげずに今後も安価な水位計の製作に取り組みたいと思います．

最後までお付き合いいただきありがとうございました．
また次回の記事でお会いしましょう．
遷移先でdismissした後に画面更新したい場合

iOS13からModalで画面遷移をする際にフルスクリーンではなくシート型に変更になりました。この場合、UIとしては格好いい（スワイプで閉じられる）のですが、遷移元の画面更新をviewWillAppearでやっていた場合、iOS13では呼ばれなくなりました。

# 対応策

遷移先のViewControllerのなかで下記のメソッドを呼んでやる必要があります。iOS12で呼ぶと二重で実行されてしまうので、実行をiOS13以上にしてやる必要があります。


```swift:
override func viewWillAppear(_ animated: Bool) {

    if #available(iOS 13.0, *) {
        presentingViewController?.beginAppearanceTransition(false, animated: animated)
    }
    
    super.viewWillAppear(animated)
        
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if #available(iOS 13.0, *) {

        presentingViewController?.beginAppearanceTransition(true, animated: animated)
        presentingViewController?.endAppearanceTransition()
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if #available(iOS 13.0, *) {
        presentingViewController?.endAppearanceTransition()
    }
}
```
Git でマージ済みのブランチ名のリストを引数にブランチを削除する

# 方法

`git branch --merged`
によって、マージ済みのブランチを一覧表示します。

`git branch -d ${branch_name}`
によって、指定した名前のブランチを削除します。

これを合体させた

`git branch -d $(git branch --merged)`

これなら、簡単に全てのマージ済みブランチをローカルから削除することができます。

## 参考

[Gitでマージ済みブランチを一括削除](https://qiita.com/hajimeni/items/73d2155fc59e152630c4)

> ※develop と master は消さないようにしています。

とあるように、こちらでは、今紹介したコマンドでは考慮していないことを考慮されています。

なので、今紹介したコマンドは、 develop で実施しないと悲しいことになる可能性があります。
（リモートには残ってるからどうとにもなる）
Swift でショートムービ(動画)を再生する

# はじめに

AVPlayer を使って動画を再生できるように実装していきます。
より詳細な情報は下記で載せました。

- https://yamato8010.hatenablog.com/entry/2020/07/26/214009

# 動画を表示するためのクラス作成

まずは動画を再生するために、`AVPlayerLayer` を `layerClass` とする UIView のサブクラスを
作成していきます。

```swift
class VideoPlayerView: UIView {
    private var playerLayer: AVPlayerLayer? {
        return layer as? AVPlayerLayer
    }

    // Override UIView property
    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}
```

# `VideoPlayerView` に Property を追加

基本的には動画の制御に必要な API は `AVPlayer` が全て持ってます。
`isPlaying` に関しては `AVPlayer` に特にそれっぽい API が無かったのでプロパティを追加しま
した。

```swift
class VideoPlayerView: UIView {
    private(set) var player: AVPlayer? {
        get {
            return playerLayer?.player
        }
        set {
            playerLayer?.player = newValue
        }
    }

    var isPlaying: Bool {
        guard let player = player else {
            return false
        }
        return player.rate != 0 && player.error == nil
    }

    private var playerLayer: AVPlayerLayer? {
        return layer as? AVPlayerLayer
    }

    // Override UIView property
    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }

    func setPlayer(player: AVPlayer) {
        self.player = player
    }

}
```

# 再生してみる

```.swift
  // 再生
  videoPlayerView.player?.play()
  // 一時停止
  videoPlayerView.player?.pause()
```
プロジェクトの上流を円滑に進めるための要点

<font color="green">本ページの目的　プロジェクトを開始する時の要点を記述。
毎回聞くことを思い出す必要があるので、メモしておくことで思い出す手間を省く。</font>

<font color="red">■　超上流</font>
１．　何をしたいのか（目的、結論、Why、Issue）
２．　困ってることは何か（問題）／改善したい事は何か（課題）
３．　現状はどうなっているのか（現状把握、Now）
４．　現状の中で、対象となっている部分はどこか（改善したい箇所、スコープ）
５．　将来はどうなりたいのか（ビジョン、Vision）
６．　埋めるべくギャップは何か（ギャップ、Gap）
７．　いつまでに解決したいのか（いつ、When）
８．　規模感はどれくらいか（規模感、Size）
９．　対象のものはどこにあるのか（場所、Where）
１０．プロジェクト関係者は誰がいるか（顧客、お客様、Who）
１１．お客様のうち、要人は誰か（キーマン、決定権を持つ人、影響力を持つ人、Who）
１２．ユーザーは誰か（ユーザー、Whom）
１３．予算はあるか（いくらで、How much）
１４．プロジェクト関係者はどうやって自分たちの問題を解決しようとしているか（ユーザの意見、具体的な使いたいソフトウェア、手段、運用の変更）
１５．同一の顧客に複数の依頼を受けている時は、優先順位はどちらか（優先順位、priority）

<font color="blue">【やっていること】</font>
１回目の打合せでユーザー／お客様と話すことは上記の内容。ユーザーは何に困っていて、なぜそれを改善したいのかなど、聞き取りの段階。話しているユーザも自分の問題を間違って認識していることがあるので要注意。そもそも改善する必要がないこともある。

<font color="blue">【プロジェクトを円滑に進めるためにやること】</font>
打合せが終わったら雑談程度で顧客と１：１で話す。本音は会議では出てこない。（例：私はホントはこんな仕事やりたくない）

<font color="red">■　上流</font>
１．顧客の思う問題点は本当に問題か（issueの明確化、真の課題）
２．顧客の要望に対して何ができるのか（What、解決案）
３．Whatのうち、自分たちで出来ることは何か（Who）
４．Whatのうち、誰かの力を借りることで出来ることは何か（Who）
５．自分たちのWhoは現実的に可能か（他のタスクとの兼ね合い、並行プロジェクトの事情）
６．顧客のwhy達成のためにどんな技術を使うか（技術、How、skill）
７．技術を使ってどう実現できるかをイメージする（仕事してる姿のイメージ、自動化、半自動化）

<font color="blue">【やっていること】</font>
顧客の話を聞いて自社で考えるところ。顧客が思っている問題が本当に問題なのか？どうやって解決するかを具体的に考える。問題達成ためのフローをイメージする。

<font color="blue">【プロジェクトを円滑に進めるためにやること】</font>
顧客に解決案を提示する時は３案（松竹梅）を用意するとよい。２案では少ない。４案では多い。

　松：早い、高い、楽
　竹：（松と梅の中間くらい）　<font color="deepskyblue">←　ココに顧客との合意事項が来やすいようにする</font>
　梅：遅い、安い、手間かかる
<b>言った言わなかったを無くすために、必ず議事録を書いて顧客に送付すること。
要点だけ箇条書きでいい</b>

----------------------------------------------------------------------
ここまで実施することでプロジェクトの大方針が決まる。
・　如何に早い段階でビジョンを明確に、詳細に、実装の問題点が無いように顧客と握れるか
・　顧客、チームメンバー、上司などの関係者との認識が常に同じになるように意識すること
Laravelでオリジナルなページネーションを作る

Laravelには協力なページネーションの機能があるので、カスタマイズしなくても大体のことは出来るのですが、

- 見た目を完全にオリジナルにしたい
- ページ数が省略される位置のロジックを特殊なものにしたい、一番端まで行くボタンを追加したい

などの仕様に対応するためには少し工夫が必要です。以下でどうやってやるか解説していきます。

# 見た目を完全にオリジナルにしたい
```terminal
php artisan vendor:publish --tag=laravel-pagination
``` 
を実行して、Laravelのフレームワーク側が持っているレイアウトを`/resources/views/vendor/pagination/`以下に持ってきて、レイアウトファイルをカスタマイズします。

カスタマイズしたページネーターを`/resources/views/vendor/pagination/custom/pagenator.blade.php`とした場合は、

```php

{{ $paginator->links('pagination::service/offer/offer-list') }}
```

という形で呼び出すことができます。

# ページ数が省略される位置のロジックを特殊なものにしたい、一番端まで行くボタンを追加したい
ページの制御は`LengthAwarePaginator::elements()`にかかれており、その実装は`Illuminate\Pagination\UrlWindow`にかかれているので、これら２つをカスタマイズすればよいです。

`UrlWindow::getSliderTooCloseToBeginning()`、`UrlWindow::getSliderTooCloseToEnding()`、`UrlWindow::getUrlSlider()`辺りが、ページネーションの最初の方に何件ページを表示して残りを…にするか、ページネーションの最後の方に何件ページを表示して残りを…にするか、ページネーションの中間のところで何件ページを出して残りを…にするか、というのが設定できるので、そこをカスタマイズすればよいです。

# おまけ
ページネーションで移動するときはページのトップではなくて、一覧の最初の部分から始まって欲しいのでid指定をしたい、ということもあるでしょう。

そういうときは`fragment()`を使えばよいです。

```php

{{ $paginator->fragment('pagination-form')->links('pagination::custom/paginator') }}
```
[GAS] ファイルのアクセス権限を絞るスクリプト(ドメイン内の設定も)

会社ではG Suite を使っているのですが、GoogleDriveにあるファイル/フォルダのアクセス権限って大事じゃないですか。困るのは「アクセスしちゃダメなユーザーにアクセス権がついちゃってる場合」です。

もちろん運用ルールとして「こうやって使いましょう」というのはありますが、人もファイルも多くなってきて、「すぐ共有したい」という場面が増えてくると、うまく管理できていないケースがあります。見直しというか、棚卸しというか、何かのタイミングでやりたいですよね。

その際に「やたらといっぱい人が追加されてる」ことがありました。それを一人ひとり手動で「削除」していくのが苦痛だったので、とりあえず「オーナーだけアクセスできるように、アクセス可能者を絞る」ためのスクリプトを書きました。(自分がオーナーのファイルのみ有効)

その後、改めて「正しい」アクセス範囲を指定しよう、と。


```js:
function changeAccessControl(fileId) {
  const file = DriveApp.getFileById(fileId);

  const me  = Session.getActiveUser().getEmail();
  if ( me !== file.getOwner().getEmail() ) {
    throw new Error(`あなたは [${file.getName()}] [${file.getUrl()}] のファイルのオーナーでは無いため、処理を中断します`);
  }
  
  const sharingAccess = file.getSharingAccess(); 
  file.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.NONE);
  
  const editors = file.getEditors();
  for (const editor of editors) {
    file.removeEditor(editor);
  } 
  
  const viewers = file.getViewers();
  for (const viewer of viewers) {
    file.removeViewer(viewer);
  } 
}
```



# 今回のポイント

↓ これはSpreadsheetを開いて、右上の「共有」を押したあとの画面ですが、


![access.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8612/2ca1a32c-6a75-056b-9869-1ecf017cee32.png)

## (1) の部分について

```js:
  const editors = file.getEditors();
  for (const editor of editors) {
    file.removeEditor(editor);
  } 
  
  const viewers = file.getViewers();
  for (const viewer of viewers) {
    file.removeViewer(viewer);
  } 
```

で、そのファイルの編集者と閲覧者を取得して、削除することができます。

参照元

* [Class File  \|  Apps Script  \|  Google Developers](https://developers.google.com/apps-script/reference/drive/file) 

## (2) の部分について

Gsuiteを使っていて、そのドメイン(つまり会社・組織)にいる人全員に対しての設定ができるところですが、

```js:
  const sharingAccess = file.getSharingAccess(); 
  file.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.NONE);
```

で指定できることを今回知りました。

参照元

* [Enum Access  \|  Apps Script  \|  Google Developers](https://developers.google.com/apps-script/reference/drive/access)
* [Enum Permission  \|  Apps Script  \|  Google Developers](https://developers.google.com/apps-script/reference/drive/permission)


# その後のネタとして

上記スクリプトで「オーナーだけがアクセスできる状態」にしたあと、
「必要な人にだけ、必要な権限(閲覧or編集)をつける」という処理を追加してもいいですね！

↓ こんな感じで！

![access2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8612/68e94896-1603-76ac-7c52-9b056bbb2166.png)


誰かお願いします！
datetimeカラムのデフォルト値について

# datetimeカラムのデフォルトを現在時間にしたい

この前提のもと、migrationに

```ruby:
t.datetime :created_at, default: DateTime.now
```

と安直に書いてmigrationすると、migrationされた日がデフォルト値になってしまう。
datetimeみたいに動的な値は、migrationされた時にしか計算されないためだ、と。
[Railsガイド>Active Record マイグレーション>3.5 カラム修飾子](https://railsguides.jp/active_record_migrations.html#カラム修飾子)

# 設定方法

[こちらのPRで上がってました](https://github.com/rails/rails/pull/20005)

```ruby:
t.datetime :created_at, default: -> { 'NOW()' }
```

これでdatetimeのデフォルト値を設定できました！

# 参考

他の記述方法もありました！参考にさせていただきました。
[Ruby on Railsのmigrationでdatetimeのデフォルト値をCURRENT_TIMESTAMPにする](https://qiita.com/keizokeizo3/items/f2b278a4439bc921b14f)
NetCommons3でメール送信テストをするためにMacにMailCatcherをインストール

MailCatcherはローカルでメール送信テストをするのに便利なツールです。

まずは https://mi-rai.co.jp/blog/326 を参考に MailCatcherをインストール

```
$ sudo gem install mailcatcher -n /usr/local/bin
```

インストールできたら起動

```
$ mailcatcher
Starting MailCatcher
==> smtp://127.0.0.1:1025
==> http://127.0.0.1:1080/
```

起動するとSMTPの情報が表示されるので NetCommonsのメール設定をMailCatcherにあわせます。

管理者アカウントでログインして「管理」→「システム管理」→「メール設定」

<img width="768" alt="スクリーンショット 2020-07-27 11.07.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9845/38c2d612-10cb-5dc8-548e-cc1a0ca00273.png">

「メールを送れるかテスト送信する」にチェックをいれてメール送信のテスト。

http://127.0.0.1:1080/ にアクセスしてメールが届いてればOK

さぁ、メール関連の動作確認しましょう。

# 2020/07/27　14:53 追記

上記までやってメールが届いてるのを確認しても登録フォームからのメールが届きませんでした。

NetCommons3の各プラグインがメールを送信するときは、Mailsプラグインを利用して送信しています。
そのMailsプラグインは送信するメール情報を一旦mail_queuesテーブルに保存し、その後でメール送信コマンドを exec することでメールを送信するようになってます。
一旦mail_queuesテーブルに保存するのは、送信先が多かったときに分割送信したり、ある日時になったら送信するようなメールを各プラグインがあまり意識しなくても済むようにです。

で、MAMPでNetCommons3を動かすうえで問題になるのは即時メール送信をするために exec でメール送信コマンドを実行している点。

MAMPでうごいてるNetCommons3からexecでcakeコマンドを実行すると、MAMPのPHPでなく、macOSのPHPが実行されます。macOSのPHPでは、そのままだとDB接続できないので、mail_queuesテーブルから送信すべきメールがよみだせません。

というわけでこのあたりをカバーするために app/Console/cakeを書き換えました。

40行目phpを実行している箇所を書き換えます。

```
exec php -q "$CONSOLE"/cake.php -working "$APP" "$@"

```

MAMPのPHPへのフルパスに書き換えて、php.iniファイルも指定して下記の様にしました。

```
exec /Applications/MAMP/bin/php/php7.4.2/bin/php -c "/Library/Application Support/appsolute/MAMP PRO/conf/php7.4.2.ini" -q "$CONSOLE"/cake.php -working "$APP" "$@"
```

これでOKかと思ったらこれでも不足で app/Plugin/Mails/Utility/MailSend.php の書換も必要でした。

44行目 \MailSend::execInBackground() 内でコマンドを実行しているのですが、どいうわけか ```nohup``` を使うとcakeコマンドの呼び出しに失敗します。

そこで nohup 使わないように変更。

```
//			exec('nohup ' . $cmd . ' > /dev/null &');
			exec('' . $cmd . ' > /dev/null &');
```

これでやっと登録フォームから送信したらメールが送られるようになりました。
macOS にて Java のバージョンを alias で手軽に切り替える

## 設定

.zshrc などに環境変数 JAVA_HOME と PATH を設定するようなエイリアスを設定する。
インストール済みの Java Virtual Machine (JVM) 毎に設定する。

```
# AdoptOpenJDK 8
alias setjava8="export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home; export PATH=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin:$(echo ${PATH})"

# AdoptOpenJDK 11
alias setjava11="export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home; export PATH=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/bin:$(echo ${PATH})"

# AdoptOpenJDK 14
alias setjava14="export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home; export PATH=/Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home/bin:$(echo ${PATH})"

# Amazon Corretto 8
alias setcorretto8="export JAVA_HOME=/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home; export PATH=/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/bin:$(echo ${PATH})"

# Zulu OpenJDK 13
alias setzulu13="export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home; export PATH=/Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home/bin:$(echo ${PATH})"
```

## Java のバージョンを切り替える

設定したエイリアス名を入力することで Java のバージョンを切り替えることができる。

```
$ setjava14
```

切り替えられているか環境変数と java -version を確認。

```
$ echo; echo JAVA_HOME=${JAVA_HOME}; echo; echo PATH=${PATH}; echo; echo 'java -version'; java -version

JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home

PATH=/Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin

java -version
openjdk version "14.0.2" 2020-07-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.2+12)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.2+12, mixed mode, sharing)
```

## 欠点

- エイリアス設定時に環境変数 PATH が展開されるため、シェルを起動するたびにエイリアスで設定される PATH の長さが伸びることがある。
Python3: CouchDB のデータを削除 (Delete)

```py:couch_delete.py
#! /usr/bin/python3
# -*- coding: utf-8 -*-
#
#	couch_delete.py
#
#					Jul/27/2020
# ----------------------------------------------------------------
import sys
import json
import requests
#
# ----------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")
#
key_in = sys.argv[1]
print("%s" % key_in)
#
url_json = 'http://localhost:5984/nagano'
url_key = url_json + '/' + key_in
#
rr=requests.get(url_key)
#
unit_aa = json.loads(rr.text)
#
if ('_rev' in unit_aa):
	print(unit_aa['_rev'])
	url_del = url_key + '?rev=' + unit_aa['_rev']
	rr=requests.delete(url_del)
else:
	sys.stderr.write("*** not exist ***\n")
	print(unit_aa)
#
sys.stderr.write("*** 終了 ***\n")
# ----------------------------------------------------------------
```

実行

```bash
./couch_delete.py t2024
```
Python3: CouchDB のデータを更新 (Update)

```py:couch_update.py
#! /usr/bin/python3
# -*- coding: utf-8 -*-
#
#	couch_update.py
#
#					Jul/27/2020
#
# ----------------------------------------------------------------
import sys
import json
import datetime
import requests
#
# ----------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")
#
key_in = sys.argv[1]
population_in = int(sys.argv[2])
print("%s\t%d" % (key_in, population_in))
#
url_json = 'http://localhost:5984/nagano'
url_key = url_json + '/' + key_in
#
rr=requests.get(url_key)
#
unit_aa = json.loads(rr.text)
print(unit_aa['name'])
unit_aa['population'] = population_in
unit_aa['date_mod'] = '%s' % datetime.date.today()
#
out_str = json.dumps(unit_aa)
#
try:
	rr=requests.put(url_key,out_str)
except Exception as ee:
	sys.stderr.write(str(ee) + '\n')
#
sys.stderr.write("*** 終了 ***\n")
#
# ----------------------------------------------------------------
```

実行

```bash
./couch_update.py t2028 81954200
```
Eclipseでデバック中にJavaのコードを実行するのに使うViewはDebug Shell

つまんないことだけど・・・Viewの名前がわからなくなってすごく探したので。

1. [Window] > [Show View] > [Other...]でダイアログを表示する
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/7b05fe69-c72e-ed76-3134-23d75d5123c3.png)
1. [Debug] > [Debug Shell]でViewが表示される
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159761/49697fd0-952a-f562-2705-744d5ca3fd61.png)
Python3: CouchDB のデータを読む (Read)

```py:couch_read.py
#! /usr/bin/python3
# -*- coding: utf-8 -*-
#
#	couch_read.py
#
#					Jul/27/2020
#
# ----------------------------------------------------------------
import json
import sys
import requests
#
# ----------------------------------------------------------------
def dict_display_proc(dict_aa):
	for key in sorted(dict_aa.keys()):
		if ((key != '_id') and (key != '_rev')):
			unit = dict_aa[key]
			name = unit['name']
			str_out = str(key) +"\t"+ str(name)
			str_out += "\t" + str(unit['population'])
			str_out += "\t" + str(unit['date_mod'])
			print(str_out)
#
# ---------------------------------------------------------------
def couch_to_dict_proc (url_json):
	url_all_docs = url_json + "/" + "_all_docs?include_docs=true"
	rr=requests.get(url_all_docs)
	list_aa = json.loads(rr.text)

	dict_aa = {}
#
	if ('rows' in list_aa):
		for it in list_aa['rows']:
			key_aa = it['key']
			unit_aa = it['doc']
			name = unit_aa['name']
			population = unit_aa['population']
			date_mod = unit_aa['date_mod']
			dict_aa[key_aa] = {'name':name,'population':population,'date_mod':date_mod}
	else:
		sys.stderr.write("*** db not exist. ***\n")
#
	return dict_aa
#
# ----------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")
#
url_json = 'http://localhost:5984/nagano'
#
dict_aa = couch_to_dict_proc(url_json)
#
dict_display_proc(dict_aa)
#
sys.stderr.write("*** 終了 ***\n")
# ----------------------------------------------------------------
```

実行

```bash
./couch_read.py
```
Python3: CouchDB のデータを作成 (Create)

```py:couch_create.py
#! /usr/bin/python3
# -*- coding: utf-8 -*-
#
#	couch_create.py
#
#					Jul/27/2020
#
import sys
import requests
import json
#
#
# ----------------------------------------------------------------
def	dict_to_couch_proc (dict_aa,url_collection):
	rr=requests.delete(url_collection)
	try:
		rr=requests.put(url_collection,'{}')
	except Exception as ee:
		sys.stderr.write(str(ee) + '\n')
#
	for key in dict_aa:
		value = dict_aa[key]
		json_str = json.dumps (value)
		url_target = url_collection + '/' + key
		try:
			rr=requests.put(url_target,json_str)
		except Exception as ee:
			sys.stderr.write(str(ee) + '\n')
#
# ----------------------------------------------------------------
def dict_append_proc(dict_aa,key,name,population,date_mod):
	dict_aa[key] = {'name':name,'population':population,'date_mod':date_mod}
#
	return dict_aa
#
# ----------------------------------------------------------------
def	data_prepare_proc():
	dict_aa = {}
	dict_aa = dict_append_proc(dict_aa,'t2021','長野',47395,'2003-8-21')
	dict_aa = dict_append_proc(dict_aa,'t2022','松本',31687,'2003-5-15')
	dict_aa = dict_append_proc(dict_aa,'t2023','上田',59462,'2003-6-8')
	dict_aa = dict_append_proc(dict_aa,'t2024','小諸',57238,'2003-7-24')
	dict_aa = dict_append_proc(dict_aa,'t2025','岡谷',73896,'2003-4-19')
	dict_aa = dict_append_proc(dict_aa,'t2026','塩尻',84129,'2003-10-14')
	dict_aa = dict_append_proc(dict_aa,'t2027','茅野',96351,'2003-5-24')
	dict_aa = dict_append_proc(dict_aa,'t2028','飯田',53476,'2003-2-12')
	dict_aa = dict_append_proc(dict_aa,'t2029','中野',64987,'2003-11-14')
	dict_aa = dict_append_proc(dict_aa,'t2030','諏訪',82154,'2003-7-7')
	dict_aa = dict_append_proc(dict_aa,'t2031','駒ヶ根',72685,'2003-9-17')
	dict_aa = dict_append_proc(dict_aa,'t2032','佐久',28741,'2003-7-20')
	dict_aa = dict_append_proc(dict_aa,'t2033','伊那',12753,'2003-3-12')
	dict_aa = dict_append_proc(dict_aa,'t2034','千曲',62518,'2003-8-19')
#
	return	dict_aa
#
# 
----------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")
#
url_collection = 'http://localhost:5984/nagano'
#
dict_aa = data_prepare_proc()
#
dict_to_couch_proc(dict_aa,url_collection)
#
sys.stderr.write("*** 終了 ***\n")
#
# ----------------------------------------------------------------
```

実行

```bash
./couch_create.py
```