OmniAICreator/Qiita-1.07M huggingface dataset:
開発版の Ruby 3.0 の右代入で分割代入できる機能がはいった

[ブログ記事](https://secret-garden.hatenablog.com/entry/2020/10/26/194316) からの転載です。

入りました。
元々はパターンマッチの 1行 in を利用して分割代入を行えるような機能が Ruby 2.7 で実験的に入っていました。

```ruby
# Ruby 2.7 で動くコード
p RUBY_DESCRIPTION
# => "ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]"

data = { name: "homu", age: 14 }

# 1行 in を使うと分割代入を行うような事ができてた
data in { name:, age: }
p name  # => "homu"
p age   # => 14
```

しかし、Ruby 3.0 でパターンマッチが正式に導入されるタイミングで1行 in は一旦削除される予定になっていました。

* [[Feature #17260] Promote pattern matching to official feature](https://bugs.ruby-lang.org/issues/17260)

分割代入はかなり便利な機能なので欲しかったので削除されるのは残念だなあ、と思っていたんですが1行 in が削除される代わりに Ruby 3.0 の右代入で分割代入が実装されました。
これにより1行 in で行えたような分割代入が右代入でできるようになりました。

* PR: https://github.com/ruby/ruby/pull/3703

```ruby
p RUBY_DESCRIPTION
# => "ruby 3.0.0dev (2020-10-26T09:00:24Z master 52c630da00) [x86_64-linux]"

data = { name: "homu", age: 14 }
data => { name:, age: }
p name  # => "homu"
p age   # => 14
```

ちなみに分割代入と言っていますがあくまでもパターンマッチの変数キャプチャ機能になるので以下のように使えることもできます。

```ruby
data = { name: "homu", age: 14 }

# 一部だけ代入できる
data => { age: }
p age   # => 14

# Struct でもキャプチャできる
data = Struct.new(:name, :age).new("mami", 15)
data => { name: }
p name  # => "mami"

# マッチしなかった場合に raise NoMatchingPatternError が発生する
data => { name: Integer }
```

元々 1行 in は `if` の条件式として使いたい経緯があったんですが、最終的にはそういう用途で利用できなくなり逆に分割代入という微妙に意味が異なる機能になってしまいました。
それが右代入で実装されたことでより意味が違い機能になって個人的にはかなりよくなったと思います。
逆に 1行 in では条件式で使えるように `true / false` を使えるような機能になってほしいですねえ。
ひとまずこれで右代入の使いみちができましたね！！
ところで左代入でも分割代入するような方向にはなるんでしょうか。
なぜドメインは買わなければならないのか？


なぜ.comというものを買わなければならないのかについて考えてみたことがありますか？これに関する良い映像があり、共有しようと思います。

#domainとは？
ドメインを買えば、そのお金は誰のところに行き、誰がどのようにドメインを作るのか説明しようとします。
その前にドメインシステムを理解しなければなりません。
ウェブというのは結局インターネットに連結されたコンピューターです。
コンピューターが互いを探す方法はIPアドレスを通じてです。

IPアドレスはただ長い数字です。 この数字をそのまま言えばユーザーは非常に不便だと思います。 それでdomain name systemを作ります。普通これを巨大な電話帳を連想すれば良いと言います。
その電話帳には、電話番号の代わりに、ウェブサイトのアドレス=IPアドレスが書かれています。例えば、google.comとアドレスをタイプした時、サイトが電話帳に尋ねます。

``Google ドットコム = 8.8.8(IPアドレス) 
``

その後、そのIPアドレスにアクセスします。
もしこの電話帳がたった一つなら非効率的でしょう？ドメイン名は数百万個存在するからです。実際には1つの電話帳がありますが、ここには別の電話帳が書かれています。

つまり、google.comとタイピングすると、一度上位にある電話帳に行き、その電話帳はそれを知っている別の電話帳を教えてくれるのです。 すると下位電話帳に移動し、この下位電話帳をregistryと言います。 このようなregistryは私企業によって運営されています。代表的には、.comのregistryはVerisignという米国企業が運営しています。ドメインに関しては最大の企業です。
![verisign.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619479/849edb04-d683-a76c-bfa3-62c343b1e2cc.png)

関連運営することは易しいことではないが、かなり収益性が良い事業です。 多くのサーバーがyoutube.com、google.comを尋ねる時、迅速に処理できるインフラが必要で、セキュリティと安全の要素を構築することはもちろんです。
また、.comのような大きなregistryも存在しますが、.coのような小さいところも存在します。
registryはIPアドレスとドメインを連結した橋のような存在で、結局ドメインを作り出すドメイン工場だと言えます。

#RESELLER
しかし、大衆にドメイン販売は直接しないです。
例えば、ドメインを買うためにverisignに行くのではありません。ドメインリセラーにアクセスすることです。そして実際の.comの費用よりもっと支払って購入しなければなりません。リセラーは仲介者であり、ユーザ管理、決済プロセッサ、ドメイン管理などの作業を行うため、その分の支払いコストがさらに発生するのです。

#もしregistryを直接作りたいなら？
もし私だけのドメインを作って、registryになる方法はICANNに申し込めば可能です。
![ICANN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619479/2d8021ac-a610-2a7f-4d24-d5818accf82e.png)

ICANNはこうしたドメインを管理する非営利機構ですが、上記の上位電話帳を管理することもあります。
しかし申し込み費用はドルで$185,000です。また、なぜこのドメインがインターネット環境に必要なのかもまた、これを運営するためのインフラ、技術、顧客管理などが整っていることを様々な資料を通して証明しなければなりません。

Registryに登録すると、ご希望の何でもできる権利が生まれます。お金をもらってドメインを売ることもできます。
たとえば.nycというドメインはニューヨーク市に住む人だけ該当ドメインを買うことができるようになっています。このような条件をつけることができるということです。

📚参考講義：[なぜdomainを買うのか？](https://youtu.be/ao0VtQnBBXA)
ANTLRでLexer ruleと同じ名前を識別子として使う場合の注意

# 課題
[TSQL解析機をAntlr4で作っていると、](https://github.com/GCer-Hidenori/ANTSQLParser)role,requiredなど幾つかの単語を列名にすると解析でエラーが起きる事に気づいた。


例)
``select role from tab
``

例のroleはParser ruleでselectの後に登場するLexer ruleのIDにマッチする意図だったが、Lexer ruleのROLEにマッチしてしまっている。

```TSqlLexer.g4
lexer grammar TSqlLexer;
FROM:               'FROM' ;
ROLE:               'ROLE' ;
SELECT:             'SELECT' ;
SPACE:              [ \t\r\n]+    -> skip;
ID:                 [A-Z]+ ;
```

```TSqlParser.g4
parser grammar TSqlParser;

options { tokenVocab=TSqlLexer; }

sql_clause       : select_statement EOF ;
select_statement : SELECT column_name FROM table_name  ;
column_name      : ID ;
table_name       : ID ;
```


[Stackoverflowで聞いてみると、](https://stackoverflow.com/questions/64572919/why-lexer-rule-name-word-cause-parse-error)Lexer ruleはparserに優先して適用されるので、Parser ruleでの記載に関係なくLexer ruleが適用されるらしい。

# 対策
下のようにするべきらしい。
おそらく、selectのあとのroleがLexer ruleのROLEになったとしてもParser ruleのidになり、column_nameがidで定義されているので解析でエラーが起きない。ということだろう。

```
select_statement : SELECT column_name FROM table_name  ;
column_name      : id ;
table_name       : id ;
id               : ID | ROLE ; // add more keywords if needed

FROM             : 'FROM' ;
ROLE             : 'ROLE' ;
SELECT           : 'SELECT' ;
SPACE            : [ \t\r\n]+ -> skip;
ID               : [A-Z]+ ;
```


# 実験
- 実験1  
 
```TSqlLexer.g4
lexer grammar TSqlLexer;

FROM:               'FROM' ;
ROLE:               'ROLE' ;
SELECT:             'SELECT' ;
SPACE:              [ \t\r\n]+    -> skip;
ID:                 [A-Z]+ | ROLE ;   //ここを変更
```
```TSqlParser.g4
parser grammar TSqlParser;

options { tokenVocab=TSqlLexer; }

sql_clause       : select_statement EOF ;
select_statement : SELECT column_name FROM table_name  ;
column_name      : ID ;
table_name       : ID ;
```

`SELECT ROLE FROM TAB` はやはり解析エラー。なぜ？

- 実験2

```TSqlLexer.g4
lexer grammar TSqlLexer;

FROM:               'FROM' ;
ROLE:               'ROLE' ;
SELECT:             'SELECT' ;
SPACE:              [ \t\r\n]+    -> skip;
ID:                 [A-Z]+ ;
```

```TSqlParser.g4
parser grammar TSqlParser;

options { tokenVocab=TSqlLexer; }

sql_clause       : select_statement EOF ;
select_statement : SELECT column_name FROM table_name  ;
column_name      : id ;
table_name       : id ;
id               : ID | ROLE ; //ここを変更
```

今度は解析成功。
【Rails6】cocoon_導入方法

__開発環境__

* Rails 6.0.3.4
* Ruby 2.6.5
* MySQL 5.6.47
* yarn 1.22.4

__目次__
__１）cocoon導入方法　←
　　1. jQuery導入　[まだの方はこちら](https://qiita.com/matata0623/items/a908d14adb329f07234f)
　　2. cocoon導入　
　　3. application.js へ「import "cocoon";」を記述
２）[動的にフォームを追加](https://qiita.com/matata0623/items/8868a7fcb6ec0817d064) 
３）[追加するフォームにidやdata属性を付与する](https://qiita.com/matata0623/items/9c1cd8f8fbae779a7202)__

#1） cocoon導入方法
##　1. jQuery導入
__[まだの方はこちら](https://qiita.com/matata0623/items/a908d14adb329f07234f)__

##　2. cocoon導入

```html:Gemfile
gem 'cocoon'
＊ bundle installとサーバーを立ち上げ直すの忘れないように ＊
* もしかしたらgemfileへの記述なくても大丈夫かもしれません。（次の工程で"yarn add"するので。） *
```

```html:ターミナル
yarn add github:nathanvda/cocoon#c24ba53
```
実行後にpackage.jsonへ以下の記述が追加されていればOKです。

```app/package.json
"cocoon": "github:nathanvda/cocoon#c24ba53"
```

__*補足：[公式レポジトリ](https://github.com/nathanvda/cocoon)__には、Rails5までのインストール方法しか説明されていません。そのため、公式には「yarn add @nathanvda/cocoon」と記述されていますが、上記コマンドでなければ、正しく追加されませんので注意してください。
　__[＊＊cocoonの後ろに"#c24ba53"をつけなければいけない理由について](https://qiita.com/hirocueki2/items/5ae31737cc5705ae492e)__


##　2. application.js へ記述
```Javascript/packs/application.js
require('jquery') //jQeryに依存しているため、ないと動かないです
import "cocoon"   //cocoonを動かすための記述
```
__*補足：__公式サイトの説明にある「require("@nathanvda/cocoon")」←この記述と併用したらボタンをクリックした時に２つずつ動的にフォームが追加されるので併用しないように気をつけてください。試してないですが、どっちでも動くということなんだと思います。

__上記で導入は完了です。__



## 続きはこちら
__２）[動的にフォームを追加](https://qiita.com/matata0623/items/8868a7fcb6ec0817d064) 
３）[追加するフォームにidやdata属性を付与する](https://qiita.com/matata0623/items/9c1cd8f8fbae779a7202)__

以上です。
Jitsi Meetサーバ使用メモリの設定

## はじめに
Jitsi Meetサーバをメモリの少ないVPSで利用していたところ、利用していくうちにメモリが枯渇し、接続が切れたりOOM落ちしたりと不安定な状態になり、再起動で対応していました。
起動直後では10人程度で利用していても、さほどメモリを利用していないようなのになぜだろうと考えておりましたが、よくよく調べてみるとjicofoとjitsi-videobridge2のデフォルトヒープサイズが3072MBに設定されているためメモリが枯渇することがわかりました。
以下はそれぞれのヒープサイズ設定の方法です。

## jicofo
/etc/jitsi/jicofo/config に最大ヒープサイズを指定します。下記は256MBを指定した例です。

```properties:/etc/jitsi/jicofo/config
JICOFO_MAX_MEMORY=256m
```

## videobridge
/etc/jitsi/videobrigde/config に最大ヒープサイズを指定します。下記は512MBを指定した例です。

```properties:/etc/jitsi/videobrigde/config
VIDEOBRIDGE_MAX_MEMORY=512m
```

## まとめ
VPS上のjitsi meetを安定して運用できるようになりました。
Bash から Gmail API でメール送信

## はじめに
クラウド上で稼働しているシステムがあり、シェルスクリプトにて稼働監視を行っており、問題が発生した場合にGmailを利用してメール通知を行っています。
2019年10月よりSMTP接続でのGmailの利用が安全性の低いアプリ扱いになり、「安全性の低いアプリのアクセス」を有効にしなくてはならなくなりました。有効にした場合でも、長期間に渡ってメール送信を行わなかった場合、自動的に「安全性の低いアプリのアクセス」が無効になってしまい、メールが送信されなくなってしまいます。
稼働監視にて極稀にしか発生しないシステムエラーでメールが送信されず、ちょっと問題となったため、対応としてGmail APIを利用することとしました。
SMTPでOauth2認証もありかなとは思いましたが、いろいろとめんどくさそうなので…
Gmail APIの設定方法や高級言語での利用方法はあちこちに情報がありますが、シェルスクリプトでの事例が見つからなかったので、メモとして情報を共有します。
## 前提事項
* Google側の設定
 * Google Developpers Consoleにてプロジェクトを作成していること
 * APIと認証でGmail APIがONになっていること
 * OAuthクライアントIDが作成されていること
 * アクセストークンを取得していること

上記については他に様々な記事があるので割愛します。

* パッケージ
 * nkf、jq、curl、tr、base64 など

## リフレッシュトークンを元にアクセストークンを取得
GMAIL APIを叩くに先立ち、アクセストークンを取得する必要があります。アクセストークンは一度発行されても30分で期限が切れてしまうため、先に更新しておく必要があります。

```bash:アクセストークン取得
REFRESH_TOKEN="<リフレッシュトークン>"
CLIENT_ID="<クライアントID>"
CLIENT_SECRET="<クライアントシークレット>"
ACCESS_TOKEN=$(curl -s -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d "refresh_token=$REFRESH_TOKEN" -d "grant_type=refresh_token" https://accounts.google.com/o/oauth2/token | jq '.access_token' | tr -d '"')
```

## メールサブジェクトのエンコード
メールのサブジェクトのエンコードはいろいろとめんどくさいのでnkfにおまかせです。下記はSUBJECT_NONENCODEに代入した文字列をnkfにてエンコードしている例です。

```bash:サブジェクトのエンコード
SUBJECT_NONENCODE="ほげほげ"
SUBJECT=$(echo $SUBJECT_NONENCODE | nkf -W -M -w)
```

### 本文の作成（メール宛先を含む）
GMail APIを利用する場合、メール宛先を含んだメール本文をBase64URLエンコードし、生成した文字列をJSON文字列に加工しておく必要があります。下記はMESSAGE_FROMに送信元のメールアドレス、MAIL_TO、MAIL_CC、MAIL_BCCにそれぞれ送信先のメールアドレスが代入され、MAIL_TEXTに本文が代入されている前提で、JSON文字列を作成する例です。
MAIL_BODYにメール本文、MAIL_BODY_ENCODEにBase64URLエンコードしたメール本文、MESSAGE_PARTにJSON化したメール本文を代入しています。

```bash:サブジェクトのエンコード
LF="
"
MAIL_BODY="Content-type: text/plain; charset=utf-8
MIME-Version: 1.0
From: $MESSAGE_FROM
Content-Transfer-Encoding: 7bit
Subject: $SUBJECT
To: $MAIL_TO
"
if [ "$MAIL_CC" != "" ]; then
        MAIL_BODY=$MAIL_BODY"Cc: $MAIL_CC"$LF
fi
if [ "$MAIL_BCC" != "" ]; then
        MAIL_BODY=$MAIL_BODY"Bcc: $MAIL_BCC"$LF
fi
MAIL_BODY=$MAIL_BODY$LF$MAIL_TEXT
MAIL_BODY_ENCODE=$(echo "$MAIL_BODY" | base64 | tr '/+' '_-' | tr -d '=\n')
MESSAGE_PART="{\"raw\": \"$MAIL_BODY_ENCODE\"}"
```

## メール送信
作成したJSON文字列をGmail APIで送信します。RESPONSEに送信が成功するとRETCODEにSENTが代入されます。

```bash:メール送信
RESPONSE=$(curl -s -X POST -H "Content-type: application/json" -H "Accept: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" -d "$MESSAGE_PART" https://gmail.googleapis.com/gmail/v1/users/me/messages/send)
RETCODE=$(echo "$RESPONSE" | jq '.labelIds[0]' | tr -d '"')
```

## まとめ
Gmail APIを利用することで、確実にシステムエラーメールを送信できるようになりました。
AWSに仮想サーバーを構築する方法

# はじめに
AWSはAmazon Web Servisesの略で、Amazonが提供しているクラウドサーバーのサービスです。
AWSへの理解度を高めるために、学んだことをアウトプットします。
今回はネットワークとの関係を記載します。


# 今回の流れ
AWSに仮想サーバーを構築する

* EC2インスタンスの作成
* パブリックIPアドレスを固定化する


# EC2インスタンスの作成

### インスタンスとは
AWS内のサービス「Amazon EC2」で作成した仮想サーバーのこと。
インスタンスは作成したVPCのサブネット内に作成されます。
作成したインスタンスには「VPC内で通信するためのプライベートIPアドレス」と「インターネットで通信するためのパブリックIPアドレス」を割り当てます。

### インスタンスの作成手順

* AWSマネジメントコンソールで「EC2」と検索します。
* サイドバーの「インスタンス」をクリックし、「インスタンスの起動」をクリックします。
* AMIの選択画面になるため、「Amazon Linux 2 AMI」を選択します。（LinuxのOSを選択したということ）
* インスタンスタイプを「t2.micro」にして「次のステップ」をクリック（t2.microは無料利用枠の対象のため）
* 「ネットワーク」に作成したVPC、「サブネット」に作成したサブネットを選択します。
（VPCとサブネットの作成の仕方が分からない人はこちらを参考にしてください。https://qiita.com/daisuke30x/items/7501f9018386240c09a8）
* 「自動割り当てパブリックIP」を有効にします。（インターネットからアクセスできるようにするため）
* 「ネットワークインターフェース」の「プライマリIP」に割り当てるプライベートIPアドレスを入力します。（今回はサブネットが「10.0.1.0/24」のため、その中の「10.0.1.10」を入力します。IPアドレスの参考も記載します。https://qiita.com/daisuke30x/items/2ef2233001c14f8076d3） 
※「10.0.1.0」「10.0.1.255」などのCIDRの先頭と末尾は入力できません。

* 「次のステップ」をクリックし、ストレージ設定でデフォルトの設定８GBを変更したい場合は数字を変更し、「次のステップ」をクリックします。（今回はストレージの変更はしません）
* インスタンスに名前を付けます。今回はキーに「Name」、値に「Webサーバー」とします。
* 「セキュリティのグループの設定」をクリックし、セキュリティグループ名を付けます。今回は「WEB-SG」と入力します。
* 「確認と作成」をクリックします。
* 「起動」をクリックします。
* 「新しいキーペアの作成」を選択肢、キーペア名を適当に決め（今回は「my-key」と入力し）、「キーペアのダウンロード」をクリックします。（作成したインスタンスにログインするための鍵となるファイルなので、必ず取得しましょう。ファイル名は「my-key.pem」となります。）
* 「インスタンスの作成」をクリックします。
※インスタンスが作成され、自動で「実行中」となるため、停止する時は「インスタンス」ページの「アクション」ボタンから選択できます。（インスタンスの実行時間が月750時間を超えると有料になるため、気を付けましょう。）


# パブリックIPアドレスを固定化する

インスタンス作成時に割り当てられたパブリックIPアドレスは、起動した際や時間がたった際に別のIPアドレスに変更される「動的IPアドレス」です。
Amazon EC2には「Elastic IP」という機能でIPアドレスを固定化することができます。

* サイドバーの「Elastic IP」をクリックします。
* 「Elastic IPアドレスの割り当て」をクリックし、「割り当て」をクリックします。
* 「アクション」から「Elastic IPアドレスの関連付け」をクリックし、「インスタンス」に作成したインスタンスを選択し、「関連付ける」をクリックします。



# 参考
「Amazon Web Services 基礎からのネットワーク＆サーバー構築」
著者：大澤文孝、玉川憲、片山暁雄、今井雄太

# 最後に
本投稿が初学者の復習の一助となればと幸いです。
InteliJの絶対に覚えておきたいショートカットキー５選

#初めに
普段InteliJで最もよく利用する5つのショートカットキーをメモしておくことに。

ああ、<b>触った時にこれ早く知ってればよかったなって後悔したものだけ</b>厳選して紹介しますmm

#開発環境
Windows 10
InteliJ IDEA 2020.1.3

#ショートカットキー
##1. アクティブタブ閉じ
Ctrl + F4
![スクリーンショット 2020-10-29 203415.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684145/dfddf21d-7562-7a19-c7ef-e93229bfe8cb.jpeg)

わざわざ緑色の箇所をポチポチしてた時間を返して欲しい。。
カーソル移動、削除が面倒。。これで一発解決。

コレ知っとけば２番目のと併用すれば、開発中のストレスはほぼ０

##2. 選択したタブ以外すべて閉じる
Alt　＋　クリック
![スクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684145/82895ff2-d6d7-c793-fc76-c7396ac92d71.png)

よく使う第2位で、選択した以外のタブが全部消えてくれる。
InteliJの「ctrl＋クリック」とか使ってると、タブ開きすぎ状態になるから
スッキリ整理される。マジで神

##3. 使用箇所にジャンプ（Goto機能）
Ctrl + メソッドorクラスにカーソルを置く
![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684145/c8ab4bbb-835b-14cb-201a-24cc586f4245.png)

メソッドやクラスがどこで使用してるか勝手に飛んでくれる優れもの。
引数とかなんだっけなーってときは、使用すると一発でわかる

ただ
使用箇所が多いと何がなんだかわからなくなるのは難点。。。

##4. クラス検索
Shift+ Shift 
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684145/9aae1588-f2ee-62d9-4461-850d11ff26b5.png)

あのクラスどこだっけな…ってときに非常に便利。
パッケージ構成が複雑になればなるほど活躍するスーパーマン。

##5. コメントアウト
Ctrl + /
![スクリーンショット 2020-10-29 204331.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684145/27897543-80d4-8a56-8601-f476d630237b.jpeg)

言わずもがな。一行ごとコメントアウトしてくれます。
使用頻度は少ないですが、
全選択(Ctrl+A)からのCtrl+/で全部コメントアウトできます。



##最後に
今後は、他のショートカットキーも使って開発速度を爆上げしよう。
【JS】配列に要素が入っているかで処理を分岐する方法

配列に要素があるかどうかで処理を分岐する方法について。

配列の長さが空（null）になることを利用する。

・`if(arr.length == 0){処理}`

```js:
arr = [ ]

if(arr.length == 0){
    console.log('配列は空です')
}else{
    console.log('要素があります')
}

//出力
配列は空です
```

この方法を使えば、配列の要素に意図的に`null`という要素を入れた場合も、要素ありとして認識できる。

```js:値がnullの場合
arr = [null]

if(arr.length == 0){
    console.log('配列は空です')
}else{
    console.log('要素があります')
}

//出力
要素があります
```



<br>
##nullの罠（ただのパターン思考不足です、、）
要素がない場合、配列はnullを返す。

これを利用して要素の有無を判定しようとすると、意図的にnullが入っている場合に、要素なしと出てしまう。

・`if(arr[0] == null){処理}`

<br>

```js:要素がない場合は機能（限定的）
arr = []

if(arr[0] == null){
    console.log('配列は空です')
}else{
    console.log('要素があります')
}

//出力
配列は空です
```


<br>
**▼【NG】0番目にnullが入っていると欲しい挙動と異なる**

```js:NG
arr = [null,1,2,3]

if(arr[0] == null){
    console.log('配列は空です')
}else{
    console.log('要素があります')
}


//出力
配列は空です
```

<br>
@vf8974さん、@sdkeiさんからのありがたいご指摘に感謝。
BNO055のpythonサンプルコードをI2Cで動かす（Raspberry Pi 3B）

#要略
Raspberry Pi3BでBNO005をi2Cで動作させる機会があり、躓いたので備忘録として残します。

使用言語はPythonです。
開発環境の設定は[こちら](https://learn.adafruit.com/bno055-absolute-orientation-sensor-with-raspberry-pi-and-beaglebone-black/hardware)の通りに進めました。
[こちら](https://github.com/adafruit/Adafruit_Python_BNO055/blob/master/examples/simpletest.py)の公式サンプルを動かしたところ、そのままではI2Cで動作しませんでした。

#解決策

30行目当たりにある次のコードを変更しました。

**Befor**

```python
# Create and configure the BNO sensor connection.  Make sure only ONE of the
# below 'bno = ...' lines is uncommented:
# Raspberry Pi configuration with serial UART and RST connected to GPIO 18:
bno = BNO055.BNO055(serial_port='/dev/serial0', rst=18)
# BeagleBone Black configuration with default I2C connection (SCL=P9_19, SDA=P9_20),
# and RST connected to pin P9_12:
#bno = BNO055.BNO055(rst='P9_12')
```

**After**

```python
# Create and configure the BNO sensor connection.  Make sure only ONE of the
# below 'bno = ...' lines is uncommented:
# Raspberry Pi configuration with serial UART and RST connected to GPIO 18:
bno = BNO055.BNO055(rst=18)
# BeagleBone Black configuration with default I2C connection (SCL=P9_19, SDA=P9_20),
# and RST connected to pin P9_12:
#bno = BNO055.BNO055(rst='P9_12')
```

何をしたかというと、BNO005の初期化時に通信インターフェースを指定しないようにしました。こうする事で自動的にI2Cの設定がなされるようになっていました。

BNO005.pyライブラリの次の箇所を見ると分かりました。

```python
if serial_port is not None:
            # Use serial communication if serial_port name is provided.
            # Open the serial port at 115200 baud, 8N1.  Add a 5 second timeout
            # to prevent hanging if device is disconnected.
            self._serial = serial.Serial(serial_port, 115200, timeout=serial_timeout_sec,
                                         writeTimeout=serial_timeout_sec)
        else:
            # Use I2C if no serial port is provided.
            # Assume we're using platform's default I2C bus if none is specified.
            if i2c is None:
                import Adafruit_GPIO.I2C as I2C
                i2c = I2C
            # Save a reference to the I2C device instance for later communication.
            self._i2c_device = i2c.get_i2c_device(address, **kwargs)
```

#おわりに
I2Cを使う場合も、サンプルコードの説明に書いといてほしかったです...。
お使いのデバイスには、重要な更新プログラムがありません

![Ejo1rhbUwAUPcYi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591973/54c01d0c-eb22-5909-ae1f-db365f30bcce.png)
ある時突然、こんなメッセージが出ました。
アイコンをクリックしてもWindowsUpdateに飛んで同じメッセージが出るだけでさっぱりわかりません。しかも、Updateができなくなってしまいました。

このメッセージで検索すると、対処方法がいくつか見つかりましたが、どれをやってもダメでした。
しかたなく、『復元』を使って以前の更新前まで戻したところ出なくなりましたが、数日経ったらまた出てきました。

あれこれ試行錯誤し、結果的には解決しましたので、その方法を書きます。

<ul>
<li>ウイルスチェックソフトをアンインストール
（停止させるだけではダメでした。）</li>
<li>USBに刺さっているものを、キーボードとマウス以外全部外す</li>
<li>内部のスロットに刺さっているボードを、グラボ以外全部外す</li>
<li><a href="https://www.microsoft.com/ja-jp/software-download/windows10">Windows10更新アシスタント</a>をDLして走らせる</li>
</ul>

当時、古めのバージョンを使っていたので、バージョンも最新になりました。

さて、これで治ったということは、どれかのハードウエアに問題があったものと思われます。一つずつ外しながら試せばどれが問題かわかったのですが、なにしろWindows10更新アシスタントの実行に2時間位かかるので試しませんでした。
【Rails】開始時間(datetime型)と終了時間にバリデーションをつける

## 実装内容
掲示板に開始時刻と終了時刻を設定し、人を募集するサイトを作成する。終了時間が開始時間より前にならないようにバリデーションを設定する。(※ついでに、開始時刻が現在日時より前にならないバリデーションも付ける)

## 環境
Ruby 2.5.0
Rails 6.0.3.4

## 前提
募集板を作成する時、開始時刻と終了時刻を入力できるように設定しておく。

## 実際のコード

今回は、募集版をboardと置く。
テーブルの内容は↓の通り

```ruby:XXXX_create_boards.rb
class CreateBoards < ActiveRecord::Migration[6.0]
  def change
    create_table :boards do |t|
 　　　（中略）
      t.datetime :start_time, null: false
      t.datetime :finish_time, null: false
      
      t.timestamps
    end
  end
end
```

開始時間を「start_time」カラムに、終了時間を「finish_time」カラムに設定。

※終了時間は「end_time」でも良かったんですが、「end」を入力する度に、インデントが変わる現象が発生してしまい(恐らくエディタの設定)鬱陶しかったので、「finish_time」にしました。

### モデル

以下のようなバリデーションを設定しました。
※一部抜粋しています

```ruby:models/board.rb
  validates :start_time, presence: true
  validates :finish_time, presence: true
  validate :start_finish_check
  validate :start_check

  def start_finish_check
    errors.add(:finish_time, "は開始時刻より遅い時間を選択してください") if self.start_time > self.finish_time
  end

  def start_check
    errors.add(:start_time, "は現在の日時より遅い時間を選択してください") if self.start_time < Time.now
  end
```

それぞれ、軽く解説いたします。

```ruby:models/board.rb
  validate :start_finish_check
  validate :start_check
```
ここで、それぞれの処理を検証しています。`validates`ではなく、`validate`になっていることに注意してください。`validate`になっていると、「You need to supply at least one validation」と言うエラーが出ます。


```ruby
  def start_finish_check
    errors.add(:finish_time, "は開始時刻より遅い時間を選択してください") if self.start_time > self.finish_time
　#↑ で開始時間と終了時間の比較
  end

  def start_check
    errors.add(:start_time, "は現在の日時より遅い時間を選択してください") if self.start_time < Time.now
　#↑ で開始時間と現在日時の比較
  end
```
ここで、それぞれの時間を比較しています。`if`が真だった場合は、エラー文が追加されます。書くまでもありませんが、不等号`>`の向きや、`if`を`unless`と書き換えて書くことも可能です。また、`self`を書かなくても同様の処理が行われます。
その辺は、お好みで。


### 時間の取得
今回はDatetime型の比較でしたが、他の型の場合も同様に実装できると思います。
↓が日時取得の参考記事です
[rubyで現在日時を取得する](https://qiita.com/ta1kt0me@github/items/e890a568d0440b371880)
[RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い]
(https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c)

## 参考記事
[railsでdatetime型の前後入力のバリデーションをかける方法](https://qiita.com/nip0p0/items/b2ee87e416dfbcf27239)
[【バリデーション】rails 今日の日付以降を指定する方法](https://qiita.com/tanaka-yu3/items/8c15b59f562c4974da37)
「後で読む」サービスのwallabagを自分で建てよう


# wallabagを建ててPocketやInstapaperにサヨナラしよう

DropboxもどきのNextCloud、gmailもどきのrainloop、google calendarもどきのAgenDAVなど様々な「自分で建てちゃえ〜」はございますが、本日は「後で読む記事ストック」「ブックマーク代わり」に使えるwallabagのご紹介です。

![wallabag.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204320/b52a00fb-0b0f-94fb-79a7-7b4f6352b6c2.jpeg)


# インストールしよう

## dockerで建てる

以下の内容で建てます。

``` docker-compose.yaml
version: "2.2"
networks:
  wallabag:

services:
  wallabag:
    container_name: wallabag
    image: wallabag/wallabag
    hostname: wallabag
    volumes:
      - /どこか適当な場所/wallabag/data/:/var/www/wallabag/data/:rw
    environment:
      MYSQL_ROOT_PASSWORD: 下記のmysqlと同じpasswd
      SYMFONY__ENV__DATABASE_DRIVER: pdo_mysql
      SYMFONY__ENV__DATABASE_HOST: wallabag-db
      SYMFONY__ENV__DATABASE_PORT: 3306
      SYMFONY__ENV__DATABASE_NAME: wallabag
      SYMFONY__ENV__DATABASE_USER: wallabag
      SYMFONY__ENV__DATABASE_PASSWORD: 下記のsqlと同じpasswd
      SYMFONY__ENV__DATABASE_CHARSET: utf8mb4
      SYMFONY__ENV__MAILER_HOST: メールを出してくれる子のホスト名
      SYMFONY__ENV__MAILER_PORT: 同じくポート
      SYMFONY__ENV__MAILER_USER: 認証で使うユーザ名
      SYMFONY__ENV__MAILER_PASSWORD: パスワード
      SYMFONY__ENV__FROM_EMAIL: 差出人につかうメアド
      SYMFONY__ENV__DOMAIN_NAME: このwallabagのURL
      SYMFONY__ENV__REDIS_HOST: wallabag-redis
    ports:
      - "80:80"
    networks:
      - wallabag
    depends_on:
      - wallabag-db
      - wallabag-redis

  wallabag-db:
    hostname: wallabag-db
    image: mariadb
    container_name: wallabag-db
    volumes:
      - /どこか適当な場所/wallabag/mariadb/:/var/lib/mysql/:rw
    environment:
      MYSQL_ROOT_PASSWORD: dbのrootパスワード
    networks:
      - wallabag

  wallabag-redis:
    hostname: wallabag-redis
    container_name: wallabag-redis
    image: redis:alpine
    networks:
      - wallabag
```
上記のままだと80で露出するので、ちゃんとhttpsで聞いてくれるフロントも用意します。
そしてID `wallabag`, パスワード`wallabag`でログインして画面にしたがってポチポチと初期設定してください。

Pocketなどからのインポートも可能です。

## ブラウザにプラグインを入れる

chrome系の方はこちら。

[Wallabagger - Chrome ウェブストア](https://chrome.google.com/webstore/detail/wallabagger/gbmgphmejlcoihgedabhgjdkcahacjlj)

firefox系の方はこちら。

[Wallabagger – 🦊 Firefox (ja) 向け拡張機能を入手](https://addons.mozilla.org/ja/firefox/addon/wallabagger/)

先程建てた自分のwallabagに行って、クライアントを登録してclient idとsecretを取得、コピペして入力します。

あとはそのアイコンをポチっとするだけで登録してくれます。

## スマホにアプリを入れる

iOSのアプリがあります。
読むだけなら普通にsafariからでもOKですが、アプリを入れることによってsafariから共有アイコン経由で簡単に記事を登録できるようになります。

[‎「Wallabag 2 official」をApp Storeで](https://apps.apple.com/jp/app/wallabag-2-official/id1170800946)

androidは多分これ。使ってないので知りませんが。

[wallabag - Google Play のアプリ](https://play.google.com/store/apps/details?id=fr.gaulupeau.apps.InThePoche&hl=ja)

# サムネイルを全部に追加しよう

標準ではwallabagは記事の最初のほうにある代表的な画像をサムネイルにしてくれます。
PDFやそのような画像が見つからない記事を登録すると、「サムネイル無し」になってしまいます。
これはちょっと悲しいので、「サムネイルが無い記事があったらサイトのスクショをサムネイルに登録」するコンテナをつくって定期的に走らせましょう。

# おわり

クラウド全盛のご時世に自前で建てるという時代に逆行するようなお話かもしれませんが、自分でたてればゼロ円！
但し、リスクも自分で背負いますのであしからず😂
Rails学習　4日目

#Ruby on Rails速習実践ガイド chapter8

##8-2　AjaxでRailsサーバと通信する
>Ajax：webブラウザ上で非同期通信を行い、ページの再読み込みなしにページを更新するためのJavascriptのプログラミング手法。

普段はページを更新するのもサーバーからリクエストをもらい少しだけ変わったページであろうとページの全ての部分を読み込み、毎回１から表示していたがAjaxを使えば部分だけを動的に処理しあとはサーバーにも介さずそのままページを使うというもの

今回はタスクを削除する処理をAjaxを用いて処理していく。
Ajaxの処理で行う動作は主に2つある。
>①タスクを削除する操作。
②削除したタスクが反映された一覧画面を表示する操作。

この2つのコードを書く。

①のタスクを削除する。だがこれはサーバーがないと何もできないのでAjaxを用いてサーバーを使う。
①のタスクを削除するには、❶Ajaxを使い削除アクションにビューから飛ぶようにして、❷削除アクションをAjax用に書き換えるということをしないといけない

>❶Ajaxで削除アクションへリンクを飛ばす方法

変更前のコード

```ruby:app/views/tasks/index.html.slim
=link_to '削除', task, method: :delete, data:{confirm: "タスク「#{task.name}」を削除します。よろしいですか？"},class: 'btn btn-danger'
```


変更後のコード

```ruby:app/views/tasks/index.html.slim
=link_to '削除', task, method: :delete, remote: true, data:{confirm: "タスク「#{task.name}」を削除します。よろしいですか？"},class: 'btn btn-danger'
```

Ajax機能を使うにはremote: trueと入れるだけ。

>❷削除アクションをAjax用に書き換える

変更前のコード

```ruby:app/controller/tasks_controller.rb
def destroy
  @task.destroy
  redirect_to tasks_url, notice: "タスク「#{@task.name}」を削除しました"
end
```

変更後のコード

```ruby:app/controller/tasks_controller.rb
def destroy
  @task.destroy
  head :no_content
end
```

コントローラーの操作でわざわざサーバーを介する必要がない物をAjaxに置き換えると良い。今回はredirect_toメソッドを置き換えた。redirect_toメソッドは一度サーバーを経由してブラウザに反映させるようなルートを通るのでそこをAjaxで経由せずに反映させるようにした。head :no_contentを代わりに書くことでサーバーを経由しなくても削除の成功というレスポンスがくるようになっている。

>②削除したタスクが反映された一覧画面を表示する操作。

イベントハンドラという物を使って一覧画面を操作する。イベントハンドラとは何かきっかけが来た時にそれを受け取り何か処理を行う仲介役的な立場。

まずイベントハンドラの対象となるきっかけを見ただけでわかりやすくするためにdeleteクラスをつける。

```ruby:app/views/tasks/index.html.slim
=link_to '削除', task, method: :delete, remote: true, data:{confirm: "タスク「#{task.name}」を削除します。よろしいですか？"},class: 'btn btn-danger delete'
```

そしてdeleteクラスを使ってイベントハンドラと実行する行動を書く

```ruby:app/assets/javascript/tasks.js
document.addEventListener('turbolinks:load', function(){
  document.querySelectorAll('.delete').forEach(function(a){
    a.addEventListener('ajax:success', function(){
      var td = a.parentNode;
      var tr = td.parentNode;
      tr.style.display = 'none';
    });
  });
});
```


##8-2-3　コントローラーからJavascriptを返して実行する
上記ではサーバーでタスクを削除し、用意していたイベントハンドラのなかのJavascriptによってビューを変更した。しかし同じ結果になる操作がもう１パターンある。

ブラウザからサーバーへ「削除してください」という情報を渡し、今回はサーバーがJavascriptを含んだレスポンスを返す方法。（SJRという）削除されたタスクをサーバーはid要素で判断し、レスポンスの時に「そのidのタスクを消す」というJavascriptの操作を返す形となる。

サーバーがどのタスクが削除されたか把握するためにまず専用のid要素をつける

```ruby:app/views/tasks/index.html.slim
table.table.table-default
...
tbody
  - @task.each do |task|
    tr id="task-#{task.id}"
      td= link_to task.name, task
      ...
```

id="task-#{task.id}"という専用のid要素を作った。

destroyアクションに対応するファイルapp/views/tasks/destroy.js.erbを作成してそこにサーバーから欲しいjsの操作を書く。

```ruby:app/views/tasks/destroy.js.erb
document.querySelector("#task-<%= @task.id%>").style.display = 'none'
```
これでサーバーからのレスポンスの時にjsの操作が一緒についてくる
最後にdestroyアクションのredirect_toを消す

```ruby:app/controller/tasks_controller.rb
def destroy
  @task.destroy
end
```

##8-3　Turbolinks
turbolinks：全てのリンククリックに対するページ遷移を自動的にAjax化することで高速化をはかったもの

##8-4-2-2　コンパイルの実行
コンパイル：コンピュータが実行できるファイル等はコンピュータが理解しやすいように言語化されている。それは人間が見てもわからない。しかしそのファイルは人間が作らなければならない。その時にまず初めに人間が理解できる言語でファイルを作りそれをコンピュータが理解しやすい言語に自動的に変えてくれる作業をしなければなならない。その操作をコンパイルを呼ぶ
std::cout 乗っ取り計画

某所で話題に上っていた記事より：

> ```c
> #include <stdio.h>
> int main() {
>   printf("Hello World\n");
>   return 0;
> }
> ```
> C言語だと、！を表現するのに『\n』を使います。

いいえ、使いません。

# ...でもC++なら？
[https://wandbox.org/permlink/k7Ixa2G9UzGeKlRd](https://wandbox.org/permlink/k7Ixa2G9UzGeKlRd)

```cpp:prog.cc
#include <iostream>
int main()
{
  std::cout << "Hello World\n";
}
// 出力: Hello World!
```

そうそう、下記コードも忘れずにコンパイル＆リンクしてね😜

```cpp:magic.cc
#include <iostream>
auto magic = []{
  struct custom_streambuf : std::streambuf {
    std::streambuf& sb_;
    custom_streambuf(std::streambuf& sb)
      : sb_(sb) { setp(0, 0); }
    virtual int overflow(int ch) override
      { return sb_.sputc((ch != '\n') ? ch : '!'); }
  };
  return std::cout.rdbuf(new custom_streambuf(*std::cout.rdbuf()));
}();
```

より実用的(!?)なサンプルと動作原理解説は、別サイト記事「[FizzBuzz化ストリーム](https://yohhoy.hatenablog.jp/entry/2013/04/14/000616)」を参照のこと。
【随時更新予定】GitHub,InteliJの小ワザ

#■GitHub便利な小ワザ集
##❶プルリクエストから相手に質問する方法
>「…」→「ViewFile」 

あとは普段通り質問箇所を選択、リンク作成

<br>


#■InteliJ便利な小ワザ集
##❶誰がいつどう編集したかわかる
>「左クリ」→「注釈表示(いつどう編集したか)」
→「右クリで修正内容が確認可能」
Ubuntuでコマンドを確認・作成

##Ubuntuコマンドの確認
```:terminal
     $ alias
```

##コマンドの作成
###[1].bashrcを使った設定
####.bashrcを編集
```:terminal
     $ cd
     $ vi .bashrc
```

```vim:.bashrc
     //iを押して編集モード
     alias 作成したいコマンド='コマンドの内容'
　　　//eg) alias home='/mnt/c/'

     //escを押してコマンドモード
     //:wqをコマンドで打ち込みセーブして終了
```
####.bashrcを反映
```:terminal
     $ source .bashrc
```

###[2].bash_aliasesを使った設定
####.bash_aliasesを作成・編集
```:terminal
     $ cd
     $ vi .bash_aliases
```

```vim:.bash_aliases
     //iを押して編集モード
     alias 作成したいコマンド='コマンドの内容'
　　　//eg) alias home='/mnt/c/'

     //escを押してコマンドモード
     //:wqをコマンドで打ち込みセーブして終了
```
####.bash_aliasesを反映
一旦ターミナルを閉じて再起動する
【Java】Calendarクラスの使い方 

#プログラミング勉強日記
2020年10月29日
JavaでCalendarクラスを扱ったので使い方を簡単にまとめる。

#Calendarクラスとは
　日付や指定した年月日時の操作を行うためのクラス。Javaで日時の計算や取得、設定をするために使う。

　Calendarクラスでは演算子`new`ではなく、`getInstance`メソッドを呼ぶことでオブジェクトを生成する。getInstanceメソッドは呼び出すごとに現在の日付と時間に初期化された状態でオブジェクトを返す。使用するときは、必ず`import java.util.Calendar`を指定する。

##サンプルコード

```java
import java.util.Calendar

public class Main {
  public static void main(String[] args) throws Exception {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.get(Calendar.YEAR) + "年" + calendar.get(Calendar.MONTH)+ "月" + calendar.get(Calendar.DATE) + "日" );
  }
}
 ```

```:実行結果
2020年10月29日
```


#基本的なメソッドの使い方

##setメソッド
　setメソッドは日時を設定するために使う。なので、日付を処理するプログラムでよく使われている。

　以下の例では指定した要素(今回だと年数)以外は前の値のままになるので注意する。

```java:使い方
import java.util.Calendar

public class Main {
  public static void main(String[] args) throws Exception {
    Calendar calendar = Calendar.getInstance();
    System.out.println("現在日時：" + calendar.getTime().toString());

    // 年を2022年に設定する
    calendar.set(Calendar.YEAR, 2022);
    System.out.println("年に2022年を設定：" + calendar.getTime());
  }
}
 ```

```:実行結果
現在日時：Thu Oct 29 10:43:58 UTC 2020
年に2022年を設定：Thu Oct 29 10:43:58 UTC 2022
```

##addメソッド
　Calendarクラスで日付の加減算を行うときにはaddメソッドを使用する。

```java:使い方
import java.util.Calendar

public class Main {
  public static void main(String[] args) throws Exception {
    Calendar calendar = Calendar.getInstance();
    System.out.println("現在日時：" + calendar.getTime().toString());

    // 年を1加算する
    calendar.add(Calendar.YEAR, 1);
    System.out.println("1年増やす：" + calendae.getTime());
  }
}
 ```

```:実行結果
現在日時：Thu Oct 29 10:43:58 UTC 2020
1年増やす：Thu Oct 29 10:43:58 UTC 2021
```

##formatメソッド
　文字列で日付のフォーマットを指定することができる。

```java
import java.text.SimpleDateFormat;
import java.util.Calendar;
 
public class Main {
  public static void main(String[] args) {
     Calendar calendar = Calendar.getInstance();

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
    System.out.println(sdf.format(calendar.getTime())); 
  }
}
```

```:実行結果
2020年10月20日
```


#参考文献
[【Java】日時の計算や取得に使えるCalendarクラスの使い方！](https://eng-entrance.com/java-time-calendar)
[【Java入門】Calendarクラスの使い方を完全解説(set/add/format)](https://www.sejuku.net/blog/20275)
[【Java入門】Calendarの日付フォーマットを指定する(format)](https://www.sejuku.net/blog/20325)
[【Java入門】Calendarの使い方(add)](https://www.sejuku.net/blog/20238)
pythonで複数のprotoファイルをまとめて変換する

ちょっとハマったので備忘録として書き残しておく。

# やりたいこと
- 依存関係のある複数のprotoをきちんと変換したい
- `protoc.main()`にどういう引数を渡せば良いのか分からない


# やったこと

## ハンズオンの内容振り返り
protoをpython用へ変換するには、下記の2つの手法がサポートされている。


#### シェルから直接叩く

```shell
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto
```


#### pythonの中から呼び出す

```python
from grpc.tools import protoc

protoc.main((
    '',
    '-I.',
    '--python_out=.',
    '--grpc_python_out=.',
    'helloworld.proto',
))
```

結論から言うと、「ディレクトリを指定して再帰的にprotoを読み込む」のような機能は無く、引数にひとつずつprotoを指定していく必要がある。
そのためシェルでやるのは現実的ではなく、pythonの`glob.glob()`を使うのが近道だと推測できる。


## protoc.main()の引数確認

```python
from grpc.tools import protoc

protoc.main((
    '',                       # おまじない
    '-I.',                    # protoを走査するディレクトリの指定 --proto_path=***でもよい
    '--python_out=.',         # ***_pb2.pyの保存場所
    '--grpc_python_out=.',    # ***_pb2_grpc.pyの保存場所 ↑と別々にする利点はほぼ無いと思われる
    'helloworld.proto',       # 変換するprotoファイルの指定
))
```
つまり、 `protoc.main()`にタプルを渡せばよい。

#### ベタ書きする場合
お尻にどんどん追加するだけでよい。

```python
from grpc.tools import protoc

protoc.main((
    '',
    '-I.',
    '--python_out=.',
    '--grpc_python_out=.',
    'helloworld.proto',
    'hoge.proto',
    'fuga.proto',
    'poyo.proto',
    'piyo.proto'
))
```

#### リストを使う場合
前述したように、`glob.glob()`を用いるとスムーズ。

```python
from grpc.tools import protoc
import glob

protos = glob.glob('hoge/**/*.proto', recursive=True)

protoc.main((
    '',
    '-I.',
    '--python_out=.',
    '--grpc_python_out=.',
    'helloworld.proto',
    *protos
))
```
`*`とは何ぞや！？と思った方は「python 可変長引数」でググるとよい。
平たくいうと、リストの頭にアスタリスクを付けることで複数の変数に展開してくれる便利なやつ。
Thymeleaf 使い方メモ

Model の addAttribute() メソッドを使って、
画面で出力したい情報を設定

th:text 属性で、指定した値をテキストとして出力する。
th:text の値には、 ${...} のように出力する値を指定


th:each で、指定したコレクションを繰り返し処理




https://qiita.com/opengl-8080/items/05d9490d6f0544e2351a
記事が有能
デプロイ参考記事

https://ecl.ntt.com/documents/tutorials/rsts/Paas/deploy/java.html



https://qiita.com/ShandyGaffLover/items/0dad6ebf48d649aab761

単純にデプロイできる方法
https://www.cloudn-service.com/guide/manuals/html/paas-v2/deploy/java/examples/simple_java_web.html
パケ構成

個人的に気に入っているパッケージ構成がこちら


前提としてパッケージ構成は
controller,service,domain,repositoryとする。
DBアクセスは無し。

DBアクセスするときはDaoパッケージがあってもいいかも

#controller層
外部からのリクエストを受取、entityのrequestに格納
returnでentityのresponseにレスポンス情報を詰めて返す。

#service層
controllerにIFへ送るレスポンスを組み立てて返す。
使用する箇所は、domain層のFactory

#domain層
Serviceクラスに呼ばれ、
Controlleに返すレスポンスを組み立てる。
APIや外部に送るrepository層と独立？？

#repository層
前提：外部に送る分だけパッケージができる。
（例：2つのAPIに送るなら２パッケージ）


APIや外部に送るリクエストパラメータ、
受け取るレスポンスパラメータを詰めるentityパケ。

また、実行するRepositoryクラスと
その為に必要な情報の詰め替えを行うFactoryクラ
インデックスを作成するとき、利用するときの基本

#じゃぁいつインデックスを利用すべきなのか
そもそもいつ、どうやってINDEXを張っていくのか？
基本的には検索時に利用される項目に対して、作成していく。
実際に有効な項目についてあげていく。

####・検索キーとして利用する項目
　　WHERE句で利用される項目について設定すると効果あり

####・結合キーとして利用する項目
　　JOIN句で利用される項目について設定。

####・項目の値が複数存在しうる項目
　　項目内の実データが多い項目ほど効果が高い。
　　逆に0か1しかない項目に設定しても、効果は得られない。

####・ソート項目として利用する項目
　　OREDR BY句でも利用可能。

####・集約キーとして利用する項目
　　GROUP BYでも利用可能。


テーブル設計時には実際にそのテーブルの利用され方をイメージして、INDEXを作成していく必要がある。

###インデックス作成時の注意
####・複合INDEXの場合でも、順番が正しいなら検索のキーが不足していてもINDEXは有効
```sql
Create INDEX TR_CDName
ON Shain(CD,Name)
--CDでの検索でもINDEXは有効
```
####大量に作ればいいというものでもない。
　　以前の記事にも記載したが、INDEXが存在するテーブルへの更新はコストがかかる。
　　そのため、、更新が頻発するテーブルにINDEXをたくさん張るのは控える。


#クエリやストアド作成時に気を付けること
実際に必要なINDEXが作成されている環境でストアド等を作成する場合について。
折角適切なINDEXが作成されているのに、思ったレスポンスが出ない場合がある。
このようなSQL文の書き方は出来る限り避けよう。

####・否定条件で検索している。
```sql
WHERE ShouhinCD <> 'Shouhin001'
```
####・Select *を利用している
```sql
SELECT *
FROM ~~
```

####・NULLを検索している。
```sql
WHERE ShouhinName IS NULL
```

####・前方一致以外の曖昧検索を行っている。
```sql
WHERE ShouhinName LIKE ’%みかん%’
```
####・暗黙的型変換が発生している
```sql
WHERE NyuukaDate = '2020/10/28'
```

以上のようなSQL文の書き方は回避できる場合は回避すべき。

#まとめ
レスポンスって、開発時は小さい問題にもならない場合があるが、稼働後に発覚すると大問題になっていることが多々。。。。
普段の何気ないストアドでも気を付けるようにしていきたい。
もちろん設計する際はINDEXの状態までしっかり把握して、結合条件を設定することを意識する。
【JS】if文の値を変数に代入する方法

超基本的なことですが、ハマったので念のためメモ。


結論、条件分岐を使って変数に入る値を指定したい場合は、**変数の定義とif文は別々に記述する**。

一つの式でやろうとするとできない、、

##条件分岐による代入方法

```js:
i = 10
result = 0

if (i == 10){
    result = i
}

console.log(result)

//出力
10
```


<br>
##NG事例

**▼直接ifを書く**

```js:NG
i = 10
result = 0

result = {
    if (i == 10){
        result = i
    }
}

//出力
Uncaught SyntaxError: Unexpected token '=='
```

そもそもif文が使えない、、



<br>

**▼関数で結果を入れようとする**

```js:NG
i = 10
result = 0

result = () => {
    if (i == 10){
        result = i
    }
}

console.log(result)

//出力
() => {
    if (i == 10){
        result = i
    }
}
```

関数がそのまま入ってしまう。
リンクを別タブで開く(target="_blank")を使うときの注意点

## 概要

`target="_blank"`のみを使うのはセキュリティとパフォーマンスに悪影響があるため非推奨

##理由
リンク先のページに悪意のあるJavaScriptが仕組まれている場合、
リンク元のページを操作できてしまうから

##対策
`rel="noopener"` 属性または `rel="noreferrer"` 属性と併用する
これによってリンク先のページで`window.opener`での参照が不可能になる。

```NG例.html
<a href="リンク先URL" target="_blank">
```
```OK例.html
<a href="リンク先URL" target="_blank" rel="noopener noreferrer">
```

##参考
[グーグルのデベロッパーサイト](https://web.dev/external-anchors-use-rel-noopener/
)

>原文
・ The other page may run on the same process as your page. If the other page is running a lot of JavaScript, your page's performance may suffer.
・ The other page can access your `window` object with the `window.opener` property. This may allow the other page to redirect your page to a malicious URL.

>和訳
・ リンク先のページは、あなたのページと同じプロセスで実行される場合があります。他のページが多くのJavaScriptを実行している場合、ページのパフォーマンスが低下する可能性があります。
・ リンク先のページは`window、window.opener`プロパティを使用してオブジェクトにアクセスできます。これにより、他のページが悪意のあるURLにリダイレクトする可能性があります。
新潟県の Go To EATの加盟店一覧をスクレイピングしてCSVに変換

前回公開していた

新潟県の Go To EATの加盟店一覧のPDFをCSVに変換
https://qiita.com/barobaro/items/74fb5bdedbf1ae7267a0

はPDFが見つからないのでスクレイピングで一覧表を作成

# スクレイピング

```python
import re
import time

import requests
from bs4 import BeautifulSoup

url = "https://niigata-gte.com/shop/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
}

result = []

while True:

    r = requests.get(url, headers=headers)
    r.raise_for_status()

    soup = BeautifulSoup(r.content, "html.parser")

    for shop in soup.select("div#result > div.cont"):

        data = {}

        data["取扱店コード"] = (
            shop.select_one("div.no").get_text(strip=True).split("：", 1)[-1]
        )

        span = shop.select("div.tag > span")

        data["エリア"] = span[0].get_text(strip=True)
        data["ジャンル"] = span[1].get_text(strip=True)

        if len(span) > 2:
            temp = {i.get("alt"): "○" for i in span[2].select("img")}
            data.update(temp)

        h4 = shop.select_one("h4")

        data["店舗名"] = h4.get_text(strip=True)

        if h4.select_one("a"):

            link = h4.a.get("href")

            if link:
                data["ホームページ"] = link

        p_add = shop.select_one("p.add").contents

        postcode, address = p_add[0].split(sep=None, maxsplit=1)

        # google mapのリンクから緯度・経度を抽出
        gps = re.search(r"(?<=@)(.+?),(.+?)(?=,\d{1,2}z)", p_add[1].a.get("href"))

        if gps:
            data["緯度"] = float(gps.group(1))
            data["経度"] = float(gps.group(2))

        data["郵便番号"] = postcode.strip()
        data["所在地"] = address.strip()

        data["電話番号"] = shop.select_one("p.tel").get_text(strip=True)

        result.append(data)

    tag = soup.select_one("li.next")

    if tag:

        m = re.search("https://niigata-gte.com/shop/page/\d+/", tag.a.get("onclick"))

        if m:
            url = m.group(0)

    else:
        break

    time.sleep(3)

result
```
# CSVに変換
```python
import pandas as pd

df = pd.DataFrame(result)

df.index += 1

df.to_csv("niigata.csv", encoding="utf_8_sig")
```
Reactの初めてのエラー

いざアプリケーションを作って行こうと思った段階で、Reactでの初めてのエラーに打ち当たりました

```react:ターミナル
src/Blog.jsx 

Line 8:3: Your render method should have a return statement react/require-render-return 

Search for the keywords to learn more about each error.
```

お願いGoogle翻訳！！！！！

と思ったのですが、流石にこれはわかる
`render`メソッドに`return`がないよってことよ

まだ書き方に慣れていないからついつい`return`忘れてしまう、、、
私が作ったことあるアプリだと`Rails`使っていて、あんまり`return`記述することなかったんでねぇ

```react
render() {
    return (

    )
  }
```
こうしてみると`render`と`return`って似ていてややこしいし、どの括弧使うのかもまだピンとこないもんですねえ
これは使っていって慣れるしかないのかな、、、

すごい簡単なエラーの解決でした！！
【gnuplot】三次元の点を消す

gnuplotを使ったときに三次元の図の点や線を消せなくて困ったのでここに書く。

#点、線を消す
以下のように　with　の後に　pm3d　をつける、または、何もつけないと点も線もなくなる。

```gp:test1
set terminal x11
set pm3d map

splot[0:200][0:200] 'file' using 1:2:($3) with pm3d

set out
```

```gp:test2
set terminal x11
set pm3d map

splot[0:200][0:200] 'file' using 1:2:($3) 

set out
```

このようにすると三次元でも点、線がなくなる。
Docker＋laravel＋MySQLのアプリをHerokuへデプロイできた！

#この記事を読んでわかること
※本記事は僕の備忘録として記載している面があります。
・laravelで作成したアプリのHerokuへデプロイする方法。
・基本的に参考に記載の方法を参考にして初学者の私でもデプロイできました。
　その際に注意した点などの補足を本記事で追記します。

#参考
https://qiita.com/okita_kamegoro/items/3278d62dd0174960b7f7
https://team-lab.github.io/skillup/step2/10-heroku.html
↑特にこの記事参考にしました。
https://shiney-life.com/web/heroku/

#補足・注意点
・しっかり読んで進めていけば、デプロイできます。落ち着いていきましょう。
・上記参考サイトでは、Laradockを使用していますが、Laradockでなくても可能です。
・ローカルではMySQLを使用し、HerokuではPostgreSQLを使用しているが、ローカルのコードは書き換えないので、HerokuではPostgreSQLで問題なし。

#簡単な流れ

・Herokuへ登録
・Heroku上にアプリ（雛形）を作成
・Herokuアプリをリモートリポジトリとして設定する
・HerokuアプリにAPP_KEYを追加する
・HerokuでPostgreSQLを利用設定をする
・Herokuアプリの画面パス設定ファイルを作成する
・Herokuアプリのリモートリポジトリにpushする
・マイグレーションを実行
 （Herokuのデータベースにテーブルや設定を反映させる）
・Herokuアプリの動作を確認し、完了！！
GolangでJSONを受け取るAPIサーバーのサンプル


## サンプルコード

```main.go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

// 構造を宣言
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
	// POST
    http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
        var user User
        json.NewDecoder(r.Body).Decode(&user)

        fmt.Fprintf(w, "%s is %d years old!", user.Name, user.Age)
    })

    // GET
    http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
        yuta := User{
            Name:  "yuta",
            Age:       666,
        }

        json.NewEncoder(w).Encode(yuta)
    })

    http.ListenAndServe(":8080", nil)
}


```


### PostとGet

```
yuta:~ $ curl -s http://localhost:8080/get
{"name":"yuta","age":666}
yuta:~ $ 
yuta:~ $ 
yuta:~ $ curl -s -XPOST -d'{"name":"tadokoro","age":24}' http://localhost:8080/post
tadokoro is 24 years old!yuta:~ $ 
yuta:~ $ 
yuta:~ $ 
```
VSCodeの選択範囲や検索単語の色設定を変更してもっと目立たせる

タイトルのとおりです．

選択範囲や検索ヒット部分にかかる網掛けの色を変更して，派手派手しく目立つようにします．
**目が疲れているとき**に便利です．
自分の場合はTeXを書いていてテキスト検索のときにどこにカーソルがあるかわからず発狂しそうになったので設定しました．

環境:

- VSCode 1.50.1 (mac/win)
- GitHub Theme 1.1.5 (GitHub Dark)

-------------

## 設定

ワークスペースまたはユーザー設定を開き，`settings.json`を開きます．
`Cmd(Ctrl) + ,` で設定画面が開くので，右上のほうに出てくるファイルアイコンに矢印が生えたものをクリックすると，jsonが開きます．
(画像の黄色い丸部分)

![スクリーンショット 2020-10-29 18.11.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99470/163867db-c27d-9c1f-ac51-821c40799444.png)

開いたjsonファイルのグローバル域に`workbench.colorCustomizations`というキーを作ります．
この中に選択範囲や検索ヒット時の網掛け色を書いていきます．

てきとうに書いたものがこちら↓

```json
{
    "workbench.colorCustomizations": {
   
        "editor.selectionBackground": "#c979f7",
        "editor.selectionHighlightBackground": "#394185",
        "editor.findMatchHighlightBackground": "#264eff",
        "editor.findMatchBorder": "#ffffff",
        "editor.findMatchBackground": "#ae00ff"
    },
}
```

`Background`が網掛けの色になります．`Border`は網掛けの枠線です．
上記の設定では，以下のパターンについて色をカスタマイズしています(テキトーに説明を書きましたが，設定エディタがヒントを出してくれるはずです)．

- selectionBackground: 選択中のテキスト．マウスドラッグやShift+矢印キーで範囲選択したときの色．
- selectionHighlightBackground: 選択中のテキストと同じ文字列．またはカーソルがある行の色．
- findMatchHighlightBackground: 検索にヒットした文字列．
- findMatchBorder: 検索にヒットした文字列群のうち，現在フォーカスしているものの枠線
- findMatchBackground: 検索にヒットした文字列群のうち，現在フォーカスしているもの．

実際のスクショがこれ(検索で"editor"と入力し，selectionBackgroundに対応する部分にフォーカスした状態)．

![検索時.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99470/67fd117c-5f02-eead-6d6b-71ea8e3506aa.png)

テキスト選択時はこんな感じです("Background"を選択しました)．

![テキスト選択時.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99470/fbd0e3fd-67f5-a2e5-db9c-dea174306f2e.png)

文字の色はもとのままで若干テキストが読みづらかったり，そもそも色がダサいなどの難点はありますが，下のデフォルト設定よりは見やすいと思います．
自分でカスタマイズしてレッツ・ハイ・ビジビリティー・VSCode!

↓デフォルト例("Background"を選択しました)．

![デフォルト例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99470/27b81288-57df-4bd4-6ece-aace34719502.png)
Python Jupyter lab のインストールで躓いた

# 目的：Jupyter labのインストール

# 現象：
Jupyter labをpipでインストールし、コマンドプロンプトでjupyterlabを実行すると、以下のエラーが出た！

```cmd:コマンドプロンプト
C:\Users\<username>>jupyter lab
'jupyter lab' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
```

# 原因
+ 直接の原因は、pipでjupyterlabをインストールする際、Pythonの意図しないバージョンに紐づいてインストールされてしまった。
⇒ [pip install jupyter lab]  or [python -m pip install jupyter lab] のどっちなの？問題

+ 副次的な要因として、Pythonやpipのバージョン違いがあちこちに保存されており、PATH設定が汚れていた。
⇒コマンドプロンプトでPythonを実行したとき、どのバージョンのPythonが実行されたか分からない問題に直面した。


# 環境：
+ windows10 64 bit
+ Python38
+ jupyter lab 2.2.6

# 補足：
+ これまでPythonのインストールをする際に、バージョン管理せず、適当なフォルダに保存していたため、どこに何があるか分からなくなった。
+ そのため、PATHが汚れたり、PIPがあちこちに点在することでどのPIPを実行しているか(PIPでインストールしたパッケージの保存先が分からない)という状況に陥った。

# インストール方法：
これらの原因の解決策として、以下の手順でjupyter labのインストールを行った。

## 1) 元のPython環境を削除
下記サイトを参考にさせていただき、作業を行った。
https://gammasoft.jp/support/uninstall-python-completely-on-windows/

本作業のフローと各作業での補足を以下に記す。

1. ソフトのアンインストール
 補足は特になし

2. 残ったフォルダを手動削除
注意) 幾つものバージョン違いのPythonをあちこちに保存している場合は、以下のキーワードをファイルエクスプローラーで検索し、見つかったフォルダを手動で(親子皆諸共に！)削除する。
検索ワード：　python,  pip

3. 環境変数の削除 
 以下のサイトを参考に環境変数を削除した。
★★注意★★) 何度もPythonをインストールするうちに、環境変数の設定が汚れてしまい、掃除に苦労した。確実に消すためには、ユーザ環境変数とシステム環境変数の両方を削除すること。また、設定内容を確実に更新するためには、PCを再起動すること。
設定変数の更新を確認するには、システムプロパティを見るよりも、コマンドプロンプトでPATH　と入力する方が確実です。

環境変数の削除方法：　ttps://www.javadrive.jp/python/install/index3.html


## 2) インストール
 1. 元の環境を削除　が完了したら、下記のインストール作業を実施する。
+ Python インストール
 以下のサイトに従ってインストールする。
Pythonインストール：　https://www.javadrive.jp/python/install/index1.html
このとき、「Add To Python PATH」にチェックを入れることで、環境変数の設定を自動で行ってくれる。
また、Pythonのインストール先は階層が浅い場所に保存するのが、個人的にお勧め。

+ pip アップグレード
    * pipはPythonインストール時に一緒にインストールされるはず。
    + 保存先はPythonフォルダ直下のScriptsフォルダの中
    + コマンドプロンプト上で以下のコマンドによりアップグレード
    
```cmd:プロンプト
# 通常
pip install pip --upgrade

# pipが複数存在し、どのpipが呼び出されているか分からない場合
python -m pip install --upgrade pip

# 実行したいpipの場所を明示して実行したい場合
cd <pip保存先フォルダのフルパス> -> pip install pip -upgrade  
```
※2つ目のコマンドは、パスが通っているpython直下のpipが実行される。
※3つ目のコマンドは、pipの保存先を明示して実行するので、一番確実。
  

+ jupyter lab インストール
 下記サイトを参考に実行した。上記の手順が行えていれば、驚くほど簡単にインストールが終わった。

[jupyter lab インストール方法の解説ページへ](https://qiita.com/kirikei/items/a1639954ce5ccaf7ac3c)
https://qiita.com/kirikei/items/a1639954ce5ccaf7ac3c

## 3) まとめ
+ Jupyter labをインストールして実行したところ、jupyter が見つからない的なエラーが生じた。
+ 過去のPythonを削除するところから始めたらドツボにはぱった。
+ その原因は、Python pip が複数存在しており、ちゃんと削除できていなかった。
+ PATH設定の更新がうまくできなかった。
+ pipが複数存在する状態でpipを実行したところ、予期せぬpipが実行されてしまった。
+ 結果的に、不要なファイルの削除と、PATHの設定を正しく行う、pipでインストールされたパッケージの保存先を理解する、この3点を抑えれば、うまくjupyter labがインストールできた。
[Vueサーバサイドレンダリング] webpackでoutput.libraryTarget should be commonjs2 と怒られた話[Vue SSR]

# Vue SSR 公式ガイドに沿って実装したら怒られた
公式ドキュメントを参考にして、Vue SSR を実装しようとしたところ、
webpackを経由してサーバーバンドルを作ろうとしたら、タイトルの通り怒られた。
`vue-ssr-server-bundle.json` が出来上がらない。。。

[公式ドキュメント]
https://ssr.vuejs.org/ja/guide/

[バグ]

```
[vue-server-renderer-webpack-plugin] webpack config `output.libraryTarget` should be "commonjs2".

[webpack-cli] Uncaught exception: Error: Entry "app" not found. Did you specify the correct entry option?
[webpack-cli] Error: Entry "app" not found. Did you specify the correct entry option?
    at /Users/s04693/Documents/sandbox/micres-vss/node_modules/vue-server-renderer/server-plugin.js:76:13
    at Hook.eval [as callAsync] (eval at create (/Users/s04693/Documents/sandbox/micres-vss/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:7:1)
    at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/s04693/Documents/sandbox/micres-vss/node_modules/tapable/lib/Hook.js:18:14)
    at Compiler.emitAssets (/Users/s04693/Documents/sandbox/micres-vss/node_modules/webpack/lib/Compiler.js:763:19)
    at /Users/s04693/Documents/sandbox/micres-vss/node_modules/webpack/lib/Compiler.js:412:10
    at processTicksAndRejections (internal/process/task_queues.js:75:11)
```

ちゃんと、`commonjs2` にもしてるし、 `entry` も正しい。。。なぜ？


# 解決方法
Webpackのバージョンが4から5に上がったのが理由だった。Issueにもなっている。

https://github.com/vuejs/vue/issues/11718

なのでバージョンを下げれば無事 `webpack` でバンドルが作られる。めでたしめでたし。

``` 
npm install --save-dev webpack@4 webpack-cli@3
```

早く治らないかなー。
MAMP WINDOWS環境で　メールが送れない

XAMP　のMAIL機能を使う


php.iniの設定

```
[mail function]
; For Win32 only.
;SMTP = localhost
;smtp_port = 25


; For Win32 only.
;sendmail_from = me@example.com

; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
;sendmail_path =
;xamppを使用
sendmail_path = "C:\xampp\mailtodisk\mailtodisk.exe" -t
;(↑ここだけを有効にする)
```

C:\xampp\sendmail\sendmail.ini
最下段に追記

```
;(元の記述で残しているもの)
smtp_ssl=auto
error_logfile=error.log

;(最下段に追記したもの)
smtp_server=smtp.gmail.com
smtp_port=587
auth_username=メールアドレス
auth_password=パスワード

;(それ以外は無効にする：例)
;pop3_server=
;pop3_username=
;pop3_password=
```
iOS(iPad/iPhone)Safariでのデスクトップ用ページの開き方と挙動の違い

iOS Safariでデスクトップ用サイトを表示する方法と、挙動についてまとめました。
## SafariからPC用ページを開く方法
SafariからPC用ページを開く方法は以下の2通りがあります。
　① OSの設定 > Safari > デスクトップ用Webサイトを表示の項目をONにする
　② SafariでWebページを開く > ヘッダーの「デスクトップ用Webサイトを表示」をTapする
    　※ スマホ用(モバイル用)を表示したいときは、↑の部分が「モバイル用Webサイトを表示」に変わっている

#### ① OSの設定 > Safari > デスクトップ用Webサイトを表示の項目をONにする
[![Image from Gyazo](https://i.gyazo.com/162ab3e56ee9029080ba501d7056a68b.png)](https://gyazo.com/162ab3e56ee9029080ba501d7056a68b)

#### ② SafariでWebページを開く > ヘッダーの「デスクトップ用Webサイトを表示」をTapする
[![Image from Gyazo](https://i.gyazo.com/be987960ec7ec10b5ba96c9a3c69f3f6.png)](https://gyazo.com/be987960ec7ec10b5ba96c9a3c69f3f6)

## User Agentの情報の違い
デスクトップ用で開くときとスマホ用で開くときで、UAが変わっています。

・デスクトップ用(↑の①②)で開いたとき

```
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4)
AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2
Safari/605.1.15
```

・モバイル用で開いたとき

```
Mozilla/5.0 (iPad; CPU OS 13_7 like Mac OS X) AppleWebKit/605.1.15
(KHTML, like Gecko) Version/13.1.2 Mobile/15E148 Safari/604.1
````


## おまけ
たとえばデスクトップ用とスマホ用でURLを分けて管理しているサイトだと少し挙動が変わってくるみたいです。

①の方法(OS設定でデスクトップ用表示に設定してアクセスした場合
　・PC用のサイトが表示される
　・PC用の画面で評価を入力することができる

②の方法(モバイル用の状態でサイトにアクセスした後、デスクトップ用に切り替えた場合)
　・モバイル用のページが表示され続けるかもしれない
　・デスクトップ用にリダイレクトされるかもしれない
　※ ②の方法はUAを変更してページを再読込しているっぽいので、該当ページの実装次第かと思います。