OmniAICreator/Qiita-1.07M huggingface dataset:
Git で.  ! [rejected] ブランチ名->ブランチ名 (non-fast-forward) のエラーが出たときの対処法

##はじめに

こんにちは。エンジニアとして働き始めて4日目になりました。
さて今回は、実務でgitで悩んで先輩エンジニアの方に助けていただきました。
その解消法についてまとめましたので、ぜひ参考にしていただけると幸いです。

##対象読者
- 駆け出しエンジニア
- プログラミング初心者
- gitがわからない人
- gitが苦手な人

## エラー内容
1. gitでブランチを切って、問題となっているところを修正した
1. git add .
1. git commit -m "hogehoge"
1. git push ブランチ名

4でpushしたとき、下記のようなエラーが...

```

 ! [rejected] ブランチ名->ブランチ名 (non-fast-forward)
error: failed to push some refs to 'hogehogehogehogehogehogehogehogehoge.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

```

pushが出来なかったようです。
リモートのブランチがリベース?されていて衝突しまったみたい。
リベースの意味はまた別途調べたいと思います。

##解決方法

先輩エンジニアさんが助けてくれたことをそのまま順を追って調べてみました。
今回の解決方法はおそらく、
**ローカルのブランチを一旦消して再度作り直す**というやり方です。

gitのローカルの中身(実際にはもっとブランチがありますが今回は省略)

```
- master
- feature/hogehoge(修正していたブランチ)
```

手順
1. **git reset --soft HEAD^** → feature/hogehogeブランチをコミット前に戻す。
(参考： https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe)
2. **git stash** → add した部分の履歴を保管しておく
3. **git checkout master**　→ masterにチェックアウトする
4. **git branch -D feature/hogehoge** → feature/hogehogeブランチを一旦消す
5. **git pull** → masterのリモートの最新情報をローカルに持ってくる
6. **git checkout feature/hogehoge** → 再度、feature/hogehogeブランチを作ってチェックアウトする
7. **git stash pop** → 手順2で保管しておいた履歴を復元する
8. **git add .** → addする
9. **git commit -m "hogehoge"** → commitする 
10. **git push origin feature/hogehoge** → pushする

上記でpushに成功しました。
他にもやり方があるみたいですが、今回はこちらのやり方を紹介いたします。


##終わりに
実務に入ってみて、gitって難しいなあと感じました。
多分これからもgitに悩まされるのだろうと思いますが1つ1つ覚えて行けたらなあと思います。

参考になりましたら幸いです。

##参考記事

https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe
SpringBootの@Transactionalが効かない！

# 結論

### DIでインジェクトしたクラスのpublicメソッドじゃないとだめ。

# それまでの流れとか

SpringBootのCommandLineRunnerを使ってバッチ処理を作っていました。
もともと入力のCSVから別形式のCSVへの変換だったのですが、仕様変更で急遽DB操作を行うことになり大慌てで対応することに。
今後改修もないようなちょっとしたバッチ処理なので`JDBCTemplate`で実装することにしました。

1ステップ処理でワークテーブルをdelete-insertで洗い替えするだけなので、トランザクション不要でExitコードがエラーなら後続バッチを動かさなきゃいいかな、と考えていたのですが運用上ロールバック欲しいらしいので後付けで対応。

## publicメソッドにしか@Transactionalをつけてもだめ。

publicメソッドにしか@Transactionalつけられなかったはず、と考えながら実装しても機能せず:0

https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html

このあたりを読んでみるとAOPProxy使っているから、なのかな。
setodaNoteCTF - Nothing 120 

#問題

https://ctf.setodanote.net/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/c31ffb95-b48f-a718-48cd-47b95d6ca935.png)

#解いてみた

ファイルを見ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/cb0a5d7c-1477-31c5-371e-49c097549847.png)

スペースとタブのようなものが書かれています。
昔ほかのCTFでやったwhitespaceとかいうやばい言語な気がする。

Whitespace - Wikipedia
https://ja.wikipedia.org/wiki/Whitespace

タブとスペースくらいしか使えない謎の言語。

Ideone.com
https://ideone.com/

で実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/d5756a32-ce22-5d18-3e77-572de8fd0f09.png)

あってました。
モバイルページ高速表示の手法『AMP』の基礎

#AMPって何？
AMP　＝　Accelerated Mobile Pages
直訳すると『加速するモバイルページ』ですがこれでは訳が分からないので、もう少しわかりやすく意味を噛み砕くと『高速表示されるモバイルページ』となります。
これだけでもなんとなくモバイルページを高速表示するために使われる何かなんだ、ということが分かりますがせっかくなのでもう少しAMPについて調べていきます。


#なぜAMPを使うと表示速度が速くなるのか？
通常のサイトではユーザーがサーバーにリクエストを送ったあとサーバー上にあるHTMLやCSS・画像ファイルなどを読み込んでサイトを生成してからレスポンスを返すため、データ量が大きいサイトほど表示に時間がかかります。

それに比べてAMPページでは、事前にサイトのデータをキャッシュ（一時的に保存）しておくことでサイトの生成を待たずレスポンスを返すことができるので、読み込みの時間を大幅に削減することが可能です。

またAMPページは大きく分けて
* AMP　HTML
* AMP JS
* AMP Cache
の3つの要素から構成されていますが、それぞれの要素ごとに『読み込み速度が遅くなることをさせない』という目的の元、厳しい仕様が決まっています。
サイトをとにかく早く表示させること1点に集中させ、無駄を省くことで読み込みの負荷を減らしサイトを一瞬で表示させることができるのです。

#なぜサイトの表示高速化が必要なのか
①ユーザビリティ向上のため
　→サイトの表示速度が1秒から3秒になると32％、5秒になると90%直帰率が増加するといわれている
　→表示に3秒以上かかるサイトは、表示される前に約50％のユーザーが離脱してしまう
②SEOに良い影響を与える
　→サイトの表示速度を高速化させることで、検索順位をあげることにつながる
setodaNoteCTF - ransom_note 100 

#問題

https://ctf.setodanote.net/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/146547ed-26ff-2ad8-5732-b410115825d6.png)

#解いてみた

ファイルを見てみます。

暗号化されたファイル。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/c522c840-e041-fd37-fcc4-a5ace8a46a69.png)

脅迫文。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/d3b0af10-e77f-81d8-0192-3ec27eaa2205.png)

画像。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/42b6b804-024d-60c3-944e-a866eb612b50.png)

とりあえず脅迫文にランサムウェアの種類「GANDCRAB V5.0.3」が書いてあるのでそれで検索してみます。

NO MORE RANSOM
https://www.nomoreransom.org/ja/decryption-tools.html

が見つかりました。
ここから復号ツールをダウンロードして実行します。

英語でよくわかりませんでしたが、次へ みたいなのを連打してたらできました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/cc7defd5-76ed-2988-b4fd-d64d24564fd9.png)

復号されたファイルにflagが書かれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/24968d56-fa80-9e24-1669-5ac7b6f42855.png)
setodaNoteCTF - morse_zero 100 

#問題

https://ctf.setodanote.net/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/f557d9a5-6424-8495-c02e-25b78d0835aa.png)

#解いてみた

ファイルを見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/3821095a-449c-bfcc-65c6-7b135e0a02d3.png)

タイトル的にはモールス信号なんですが、Zとスペースの二種類しかありません。
モールス信号なら「.」と「-」と区切り文字の3種類必要です。

バイナリエディタで見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/cbfec254-f8ea-1c14-fe94-e4ee00a166bf.png)

Zの5Aの前を見てみると「E2 80 8C」と「E2 80 8B」の二種類のスペースがあることがわかりました。
元のエディタで1文字目のスペースを検索してみると確かに全部のスペースが黄色になるわけではありませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/decbd763-dd4b-1ea9-7614-1b24c4530c16.png)

ということで

- スペース1つ目
- スペース2つ目
- Z

の三種類見つかりました。
モールス信号の区切り文字は連続することはないとも思うのでZが区切り文字ということになります。

変換してみると
..-- - -.- ..... --..-. -.. -.... .-- . ---- --..-. --- -..- -. .-.- -
か
--.. . .-. ----- ..--.- .-- .---- -.. - .... ..--.- ... .--. .- -.-. .
ということになります。

cyberchefで変換してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17681/17c1cfe4-b866-6c89-7ff2-de2e5fd90cdb.png)

どちらも文字列になったので順番に入力してみると後の方が正解でした。

#ちなみに

e2808bは何なんだろうと思って調べてみるとwikipediaにありました。

ゼロ幅スペース - Wikipedia
https://ja.wikipedia.org/wiki/%E3%82%BC%E3%83%AD%E5%B9%85%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9

改行していい場所を指定するための幅のないスペースらしい。

e2808cは調べたけどよくわからず。
さらば Error: Node Sass version 6.0.1 is incompatible with ^4.0.0.

# node-sass インストール時のエラー

SCSS を使いたくて node-sass インストールしたら yarn start でエラー吐いた。

```terminal:Terminal
$ yarn add node-sass
$ yarn start
Error: Node Sass version 6.0.1 is incompatible with ^4.0.0.
```

> 日本語訳
> NODE SASS バージョン 6.0.1 は ^4.0.0 と互換性がありません。

ネット上を探してみると `node-sass` をバージョン指定でインストールすれば OK みたいな記事が多くヒットするけど、そもそも [npm : node-sass](https://www.npmjs.com/package/node-sass)  に以下の記載がありました。

:::note warn
Warning: LibSass and Node Sass are deprecated. While they will continue to receive maintenance releases indefinitely, there are no plans to add additional features or compatibility with any new CSS or Sass features. Projects that still use it should move onto Dart Sass.
:::

> 日本語訳
> 警告 : LibSass と NodeSass は非推奨です。今後もメンテナンスは継続されますが、新規機能の追加や、新しい CSS または Sass 機能との互換性の追加は行われません。まだこれらを使用しているプロジェクトは Dart Sass に移行する必要があります。

これからは Dart Sass 使ってね！ってことみたいです。

# Node Sass のアンインストール

特に Node Sass に思い入れがある訳でもないので、サクッと `node-sass` を削除します。

```terminal:Terminal
$ yarn remove node-sass
```

# Dart Sass のインストール

Dart Sass をインストールします。パッケージ名が `dart-sass` ではなく `sass` なので注意。

```terminal:Terminal
$ yarn add sass
```

# コンパイル

早速、や～んスタート！

```terminal:Terminal
$ yarn start
Compiled successfully!
You can now view ReactTest in the browser.       
  Local:            http://localhost:3000        
  On Your Network:  http://172.18.112.1:3000     
Note that the development build is not optimized.
To create a production build, use yarn build. 
```

解決！

# 参考サイト様
- [npm : node-sass](https://www.npmjs.com/package/node-sass)
- [Dart Sass](https://sass-lang.com/dart-sass)
- [sass-loader で dart-sass を使う](https://qiita.com/0x50/items/9d586c7923be345f83c1)
- [CSS Module と dart-sass を使う](https://noarts.net/archives/3300)
GitHub Desktop 初心者的メモ

gitもgithubも初めて使った私が、自分のために覚書。
ほぼ初めて使っているので、用語等の誤りはたくさんあると思います。ご容赦ください。

# 目的

- 自分のプロジェクトを GitHub にバックアップ、公開する。
- 複数の自分のマシンでアクセスする。
- 履歴を管理する。

# 確認環境


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/da13f95a-1381-b18e-39a5-4885f255784a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/d691421f-2d04-9395-3b7f-43de1e0933f7.png)


# 準備

- GitHubアカウント取得。手順省略します。
- GitHub Desktop 起動。アカウントにログインします。

# 自分のフォルダーを新規でリモートRepositoryに登録する

例として、ここにある A というフォルダーをリポジトリとして登録する。**その中に、すでに .git フォルダーがある場合は、削除しておく。**

clone等でどこからか持ってきたフォルダーだとある場合があり、新規登録の場合は障害となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/ec561f66-b836-f6a9-fbec-727692576ac1.png)

Mac Finder の場合は [command] + [shift] + [.] で確認。

GitHub Desktop で、File - New Repository を選ぶ。
Name欄はフォルダーと同じ名前を、**Location Path はその上の階層を指示する。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/9fcfc386-0b93-41a2-8961-a7958a3a020c.png)

Publish repository をクリックするとリモートRepositoryに登録される。公開設定の確認を忘れずに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/abc6df13-a9fe-0fb5-d973-711e12752366.png)

# 他のマシンで作業する ~ clone ~

GitHub Desktop をインストール、自分のアカウントでログインする。

File - Clone Repository... を選ぶ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/15c7c9f2-0968-3ed7-c486-b7aa6e84cb46.png)

Your Repositories で選び、Local Path は [A] を置きたい場所を指定する。

# 連携後の作業の流れ

- 他のマシンで Fetch したのなら，今から作業するマシンでもまずは Fetch する。
- 作業の区切りが良いところで，commitする。historyとしてローカルに保存される。
- Fetchすれば他のマシンでも反映できるようになる。他のマシンでもhistoryが参照できるようになる。

これで main ブランチだけでも十分使える。
少し大掛かりに分けたいときは，ブランチを分ける。

# 素朴な疑問

- web上で名前を変えたらどうなる？
-- 連携先が変わるだけ。ローカルはかわらない。

- ローカルのフォルダーの名前を変えたら？
-- 次回GitHub Desktop時に怒られる。再配置する。

# 追記

会社の先輩から「公開しなくても，ローカルだけでも十分に役に立つよ！」とアドバイスを受けました。その通りのようですね。うまく使いたいです。
GCLB(Google Cloud Load Balancing)で、別プロジェクトのGCSをオリジンにする方法

GCLBでは、同一PJの場合は「[バックエンドバケット](https://cloud.google.com/load-balancing/docs/https/ext-load-balancer-backend-buckets)」でGCSをオリジンに設定することが出来ます。
一方で、別のPJのGCSをオリジンにする場合は、[インターネットNEGを用いて「バックエンドサービス」として取得](https://cloud.google.com/load-balancing/docs/https/setting-up-https-external-backend-internet-neg)する必要があります。

ここではこのような場合のポイントを記載します。

# ポイント
- ~~GCSにカスタムドメインを振っている場合、実際にそのドメインでアクセスできること~~
    - ~~アクセスできない場合、試した限りではバックエンドサービスとして取得出来ませんでした（やり方わかる方コメントください！）~~
    - カスタムドメインの場合でも取得できました。設定後述します。
- GCSは公開に設定していること
- Hostヘッダを指定すること


## GCSバケット名に「.」を含まない場合

- インターネットNEG
    - FQDNは「**[バケット名].storage.googleapis.com」**とする
    - **GCSはインターネット公開**に設定する
- GCLB
    - バックエンドサービスのバックエンドタイプを「インターネット・ネットワークエンドポイントグループ」にし、プロトコルは「HTTP/2」にする
    - バックエンドサービスの「高度な設定」にて、**カスタムリクエストヘッダーを追加**する。
        - **Host : [バケット名].storage.googleapis.com**
        - ※これがないと404になります。
   

このポイントを押さえれば、GCSの公開URLに直接アクセスした際のパスのまま、ドメインをGCLBのものに変更することで、GCLBから外部PJのGCSデータを返すことが出来ます。

例）

- GCS URL
    - https://[バケット名].storage.googleapis.com/test.jpg
- LB URL
    - https://lb.example.com/test.jpg

## GCSバケット名に「.」を含む場合（ドメイン名のバケット）

- インターネットNEG
    - FQDNは「**storage.googleapis.com」**とする
    - **GCSはインターネット公開**に設定する
- GCLB
    - バックエンドサービスのバックエンドタイプを「インターネット・ネットワークエンドポイントグループ」にし、プロトコルは「HTTP/2」にする
    - バックエンドサービスの「高度な設定」にて、**カスタムリクエストヘッダーを追加**する。
        - **Host : storage.googleapis.com**
        - ※これがないと404になります。
    - ホストとパスのルールで「詳細なホストとパスのルール（URL リダイレクト、URL の書き換え）」に設定
        - 「アドオンアクション（URLの書き換え）」の「パス接頭辞の書き換え」で **/カスタムドメイン** を追記する
            - 例）「/custom.example.com」
   
カスタムドメインを利用している場合は、GCSのURLは

- [カスタムドメイン、GCS直接](https://cloud.google.com/storage/docs/hosting-static-website-http?hl=ja)（※DNS設定が必要）
    - http://custom.example.com/test.jpg
- カスタムドメイン、同一PJのGCLB経由（※未確認）
    - https://custom.example.com/test.jpg
- GCS URL
    - https://storage.googleapis.com/custom.example.com/test.jpg
    - ✕これはできない https://custom.example.com.storage.googleapis.com/test.jpg

となります。
上記の設定を利用すると、

- LB URL
    - https://lb.example.com/test.jpg

として、カスタムドメイン名バケットでも、別PJのGCLB経由のアクセスが可能になります。
Railsランキング機能(同率含む)

Railsでレビュー評価に基づくランキング機能の実装方法です。ランキングの作り方は他にも記事が出ていますが、同率（例えば1位が２つ合った場合、次が3位になる）の実装方法の記事はあまり見られなかったので、記事を残したいと思います。

**条件**

- 投稿(post)の評価（1~5）の平均に応じた降順のランキング
- Userモデル,Postモデル（投稿）が多対多の関係であり、中間テーブルのReview（口コミ）モデルが存在する
- ルーティングは任意

**STEP1** 
任意のコントローラーにアクションを作ります。今回は`ranking`にしました。アクション内で評価に基づくデータの所得のコードを記入します。また、Reviewモデルの対象カラムは`rate`です。取得したデータを`@rank`に格納します。
データの取得方法ですが2つあります。

**ActiveRecordのメソッドを利用する場合**

```
@ranks=Post.find(Review.group(:post_id).order('avg(rate) desc').pluck(:post_id))
```
**ポイント**
Reviewモデルに対して`.group(:post_id)`でPostモデルのidごとにグループ化します。平たく言うと、Postモデルのidごとに口コミを集計します。続いて、`.order('avg(rate) desc')`で評価を平均にし、降順で並べます。そして、`.pluck(:post_id))`でReviewモデルからPostモデルのidを取得し、Postモデルのデータを`@rank`に格納します。

**Rubyのsortメソッドを利用する場合**

```
@ranks=Post.all.sort {|a,b| b.reviews.average(:rate) <=> a.reviews.average(:rate)}
```
**ポイント**
これはPostモデルに`has_many :reviews`を追加した場合に使用できます。Postモデルの各要素の評価の平均値で`.sort`メソッドを使い、並べ替えています。

**STEP2** 
ビューファイルを作成し、コードを記述します。同率を実現するために、`<% last_rate = 0 %>,<% j = 1 %>`を用意します。last_rateが前回の口コミの評価平均値、jが順位に使うものになります。
そして、last_rateと今回の評価平均値でif文で分岐をしていく形です。どちらも最後に今回の値をlast_rateに格納します。それで.each文でループさせていくことにより、同率を含むランキングが実現できます。

```
<% last_rate = 0 %>
<% j = 1 %>
  <% @ranks.each.with_index(1) do |post,i|%>
    <% if post.reviews.average(:rate).to_f.round(1) != last_rate %>
      <% j = i %>
      <div>
       <%=j%> 位<%=post%>
      </div>
      <% last_rate = post.reviews.average(:rate).to_f.round(1) %>
    <% else %>
      <div>
       <%=j%> 位<%=post%>
      </div>
      <% last_rate = post.reviews.average(:rate).to_f.round(1) %>
    <% end %>
  <% end %>
</div>
```
Moralis Pricing API でトークンの価格履歴を取得し DeFi 損益計算するメモ

## 背景

DeFi での税金の損益計算で Uniswap で交換したときのトークン価格とか知りたい...

etherscan で DeFi トランザクションを集計して税計算を行う(だいたいできた. 進捗 89%)
https://qiita.com/syoyo/items/c75bad1d98981ae6fa21

があるが, 特定のブロックのときの価格を直接取得はできないのでめんどい... もっとお手軽ぺろっとやりたい...

Moralis(archive node アクセスなど 155 万回/month まで無料) で価格の履歴(各ブロック単位)取れました.

Moralis フリー archive node アクセスサービスで QuickSwap の USDC/JPYC 流動性ペア残高の履歴を取得するメモ
https://qiita.com/syoyo/items/a81f772394bf31432856

その他, Web3 API サービスでいろいろ情報取得できます!





## JPYC 価格を取得する

参考として ERC20(Eth mainnet)の JPYC 価格を取得してみます.

![Screenshot from 2021-10-06 21-16-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36275/f53774c9-bb57-91a5-710f-ffbb1fb925a4.png)

実際には流動性プール(exchange. e.g. Uniswap)での価格になります.
デフォルトでは Uniswap が使われました(ブロックが新しければ v3, 古ければ v2).
(Matic の場合は QuickSwap)

```
curl -X 'GET' \
  'https://deep-index.moralis.io/api/v2/erc20/0x2370f9d504c7a6e775bf6e14b3f12846b594cd53/price?chain=eth' \
  -H 'accept: application/json' \
  -H 'X-API-Key: <APIKEY>'
```

```
{
  "nativePrice": {
    "value": "2253446224184",
    "decimals": 18,
    "name": "Ether",
    "symbol": "ETH"
  },
  "usdPrice": 0.007632394984635316,
  "exchangeAddress": "0x1f98431c8ad98523631ae4a59f267346ea31f984",
  "exchangeName": "Uniswap v3"
}
```

Voila! :tada:
`usdPrice` が取れました.

特定のブロックでの価格も取得してみます.


```
curl -X 'GET' \
  'https://deep-index.moralis.io/api/v2/erc20/0x2370f9d504c7a6e775bf6e14b3f12846b594cd53/price?chain=eth&to_block=12298371' \
  -H 'accept: application/json' \
  -H 'X-API-Key: <APIKEY>'
```

```
{
  "nativePrice": {
    "value": "3793535942220",
    "decimals": 18,
    "name": "Ether",
    "symbol": "ETH"
  },
  "usdPrice": 0.00890160133955811,
  "exchangeAddress": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
  "exchangeName": "Uniswap v2"
}
```

Ultra super coool!

## LP の時価?

LP(liquidity pair)のトークンの時価もほしいですよね.
(farming などしている場合, きちんとした DeFi 税金計算するには, 年末, 年度末時点での LP などの時価算出が必要)

残念ながら Pricing API で直には求めることができません.

reserve を調べる API ありますので, これで残高を調べ, Uniswap など流動性プールの計算式に当てはめて LP 時価を求めることになるでしょう.

![Screenshot from 2021-10-06 21-31-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36275/8ffccf3e-e546-b8c7-2d6a-91b1968c6c8e.png)

Impermax など通常の(?)流動性プール以外の場合では, reserve 取得は対応してないでしょうから, 本記事最初にある別記事を参考にして自前でゴリゴリ書かないとだめそうです.

## 税金計算?

価格は取引所の価格ですからいくらか価格に歪みがありますので, 税金の損益計算は, ここから coingecko なり, ステーブルコインの場合は為替レートで参照価格を求めて補正することになるでしょう.

## TODO

* [ ] 自身のトレードのトランザクションログと, Moralis が出した価格がマッチしているか調べる
* [ ] Liquidity-Provider fee を考慮してより正確な損益計算を行う
SQSの遅延キューとメッセージタイマー

SQS　＝　Amazon Simple Queue Service
フルマネージドなメッセージキューイングサービス。

（AWSサービス群の中では最古！）

## メッセージを遅延させる方法2つ
**遅延キュー**・・・SQSに届いたすべてのメッセージを遅延させる
**メッセージタイマー**・・・SQSに届いたメッセージのうち特定のものだけど遅延させる


要件に応じて使い分ける！

## メッセージサイズ
メッセージは最大**256KB**
画像などのためサイズがたりないときは、S3やDynamoDBへ格納、パスやキーといったポインター情報を受け渡すことで対応

～私のSAA対策のため、アウトプットしております～
やっぱ Azure Cosmos DB 辛えわ (OFFSET LIMIT編)

# どんな記事？
Azure Cosmos DBの辛い点を書く記事。

# Azure Cosmos DBとは

- AzureのNoSQLサービス

- 以下のDBエンジンから選択して利用する形式 (Amazon Aurora的なイメージ)

 - Azure独自ドキュメントDB (通称SQL API)　←　本記事の対象

 - Mongo DB (互換)

 - Cassandra (互換)

 - Gremlin (互換)

 - Azure Table Storage (互換)

# 辛い点
OFFSET LIMITが全然OFFSET LIMITしてない。

ページング処理をしたい場合OFFSET LIMIT使いますよね？
100ページ目のデータを見たい時に1～100ページのデータを全部取得しませんよね？
そんなことしたらDBの負荷がヤベえことになるのは簡単に想像できると思います。

ところがどっこい！
Azure Cosmos DBのOFFSET LIMITは全部取得しやがるんです！
マジで辛い。

(正確に言うと、DBは1～100ページのデータを全て返してSDKの中でデータのスキップ処理が行われます。つまりDBの負荷はそのままでソースコード側の負荷だけ軽減されるという事です。)

# 参考
https://docs.microsoft.com/ja-jp/azure/cosmos-db/sql/sql-query-offset-limit
VSCodeの拡張を自作する：2_文章を取得する

# この記事について

概要は
[概要のページ](https://qiita.com/wakakusa_ryuuzi/items/49d08f56a3ca653db025)
をご確認ください。

この記事で出来ることは、エディタのテキストを読み取ってポップアップに表示するところまでです。



# エディタ操作

基本的な操作になるようなものは、大体vscode.window配下にいる。


## Windowの構造

[ドキュメントの該当箇所](https://code.visualstudio.com/api/references/vscode-api#window)

大体こんな構造になっている

- vscode
	- window
		- activeTextEditor (:TextEditor)：現在フォーカスされているエディタ（タブ）
			- document (:TextDocument)：エディタ内の本文とか
		- visibleTextEditors (:TextEditor[])：表示されているすべてのエディタ（タブ）
		- activeTerminal (:Terminal)：表示されているターミナル

表示中の文章等はvscode.window.activeTextEditor.document.getText()で取得できます。


# 文章を取得してポップアップに表示するサンプル


```typescript

	//フォーカスされてるエディタの選択文字列を表示
	let testCommand1 = vscode.commands.registerCommand('vscodeExtensionTest.command1', () => {
		const editor:vscode.TextEditor | undefined = vscode.window.activeTextEditor;
		if ( editor !== undefined ){
			const document:vscode.TextDocument = editor.document;
			const selection:vscode.Selection = editor.selection;
			const word:string = document.getText(selection);
			vscode.window.showInformationMessage(word);
		}
	});
```
MacでImageMagickインストール後にbundle installを実行するとエラー（No such file or directory）

# 概要
画像を読むこむアプリケーションを作成しようとして、ImageMagickを導入した。
その後、Gemfileで「mini_magick」などを追記して「bundle install」を実行したところ
No such file or directoryとなってしまった。

# 事象

① まず、ImageMagickをHomebrewでインストールする。

```shell:Macターミナル
username@MacBookAir ~ % brew install imagemagick
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> New Formulae
aws-sso-util                git-cliff                   libavif                     python-tk@3.10
ca-certificates             git-credential-libsecret    lilypond                    python@3.10
clickhouse-cpp              gomodifytags                lunzip                      texlive
clickhouse-odbc             gotests                     lziprecover                 viddy
cmake-docs                  k2tf                        neovim-qt
cyral-gimme-db-token        lastz                       openssl@3
==> Updated Formulae
Updated 658 formulae.
==> Deleted Formulae
ocamlsdl

==> Downloading https://ghcr.io/v2/homebrew/core/libpng/manifests/1.6.37
######################################################################## 100.0%
・
（省略）
・
==> Upgrading 5 dependents:
nghttp2 1.44.0 -> 1.45.1, node 16.9.1 -> 16.10.0, rbenv 1.1.2 -> 1.2.0, ruby-build 20210825 -> 20210928, yarn 1.22.11 -> 1.22.15
・
（省略）
・
==> Pouring yarn--1.22.15.all.bottle.tar.gz
🍺  /usr/local/Cellar/yarn/1.22.15: 15 files, 5MB
Removing: /usr/local/Cellar/yarn/1.22.11... (15 files, 5MB)
Removing: /Users/username/Library/Caches/Homebrew/yarn--1.22.11... (1.2MB)
==> Checking for dependents of upgraded formulae...
==> No broken dependents found!
username@MacBookAir ~ %
```
② ImageMagick をRailsで利用するため、Gemfile に関連パッケージを追記する。

```file:Gemfile
gem 'mini_magick'
gem 'image_processing', '~> 1.2'
```

③ Gemをインストルールするため「bundle install」を実行した。

```shell:VSCodeターミナル
username@MacBookAir ~ % bundle install
rbenv/shims/bundle: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory
```

# 原因
ImageMagickをインストールした際に「Auto-updated Homebrew」とあるのが見えると思います。
そして、その後、「Upgrading 5 dependents:」という書き出しの後に「rbenv」があるのが見えます。
つまり、そのタイミングでrbenvが意図しないアップデートが発生しました。
アップデート自体は悪くないのですが、残念ながらアップデート後はPathが通らなくなっています。

# 対策①
rehashを行うことで、再度コマンドが使える。
（私はこの方法を使っていないので、情報だけ記載します。）

```
% rbenv rehash
```

# 対策②
ターミナルを再起動する。
これは私もやりました。再起動すると、どうしてPathが通るか理由はあります。
rbenvのインストール時に「eval "$(rbenv init -)"」を書き込んだと思います。
これがrehashと同じようにPathを通してくれるコマンドになります。

```
% cat ~/.zshrc 
eval "$(rbenv init -)"
```

# 参考
[teratail - ターミナルでRails関連のコマンドが一切使えなくなってしまいました。](https://teratail.com/questions/225461)
[Github - rbenv/rbenv](https://github.com/rbenv/rbenv#how-rbenv-hooks-into-your-shell)
[mogulla3.tech - rbenv rehashをちゃんと理解する](https://mogulla3.tech/articles/2020-12-29-01)
KQLでのHex→ASCII文字列変換

Microsoft AzureのAzure Monitorで16進数文字列を取り扱うという、
かなりニッチな案件で、本件の問題にあたりました。

KQLではhex2stringが無いので、思案しました。
全網羅的に検証していないので、不具合はあるかもしれません。

```
extend ascii = url_decode(strcat('%',strcat_array(extract_all(@'(..)', hex),'%')))
```

解説：
1. extarct_allで正規表現(任意の2文字)を分割します
2. 各2文字分割を%を付けながら結合します
3. 先頭にも%を付けます。
4. URLデコードをします

所感：
KQLでの実現方法に相当悩みましたが、
URLデコードで変換できることに気づいた瞬間テンション上がりました。
割とよくある関数を使用しているので、他の言語にも応用は効きそうです。
Oracle Databaseを自宅で飼ってみよう (1)

# 自分で自由にできるOracle Database欲しいよね

**「Oracle Masterの学習をしたいんだが、実際にOracle Databaseをインストールして動かしてみたりすることはできないか？」**

Silver DBAくらいまでなら実際に触ったことがなくても十分取れます。が、当然のことながら経験はあるに越したことはありません。また、実務でOracle Databaseを扱っていても、管理者でないとできないことはたくさんあります。

そこで、Oracle Databaseを自宅で飼うことにします。
大企業のシステムで用いられるOracle Databaseですが、一般のご家庭でも飼うことができます。

今回飼うことにしたのは **Oracle Database 19.3 Enterprise Edition** です。

# でもお高いんでしょう？

詳しくは知りませんが、数百万円から数千万円かかると言われています。

でも大丈夫。学習用であれば無償で使えます。

https://cosol.jp/techdb/2019/05/use_oracle_database_no_charge/

> > オラクルはお客様に、本契約に明示する制限に従って、お客様のアプリケーションの開発、テスト、プロトタイプ作成、及びデモンストレーションのみを目的として（かつ、お客様のアプリケーションが、データ処理、業務、商用又は本番利用を目的として使用されたことがない場合に限られます）、その他のいかなる目的でもなく、本プログラムを内部的に使用するための、非独占的、譲渡不能の限定的なライセンスを許諾します。
>
> Oracle Technology Network License Agreementにわかりやすく明記されていないがですが、個人の学習での利用はOKなようです。

「OTNライセンスの下でOracle Databaseを使う」場合、学習用であれば制限なしで全機能使えるようです。
Linuxで使う印象の強いOracle Databaseですが、Windowsでも動かすことができます。というか、実際にWindows Serverで本番運用している事例もあります。なので、今回はWindowsのPCにインストールしてしまいましょう。

お金の心配が無くなったところで、以下からインストーラをダウンロードします。

https://www.oracle.com/jp/technical-resources/

# でもPCの性能が…

残念ながらここでハマってしまいました。

Oracle Databaseの起動だけでメモリが3.4GB強必要ですが、手元のSurface Pro 6（メモリ8GB）では要件を満たしませんでした。なんでこんなにメモリが必要なのか……まあ、勉強した人ならわかると思います。

Oracle Master Platinum DBAの合格者が、学習で一番最初にやったことはサーバーの購入だと言っていたのですが、あれは正しかったんですね。
まずサーバーを買う必要があって、それもデータベース専用にしないのであればメモリが8GBあっても厳しいということがわかりました。

# まとめ

~~いかがでしたか？~~

誰もが敬遠してしまいがちなOracle Databaseのインストールでしたが、実際にやってみた結果、以下のことがわかりました。

* 実は学習用途であれば無償で使える
* 性能要件が非常に厳しい

このままだと諦め切れないので、サーバーを買うか、あるいは他の方法が見つかったタイミングで続きを書こうと思います。
Windows 11 にアップデートしたら、日本語入力IMEに ATOK を使用しているとメモ帳がフリーズするようになった。

Windows 10 から Windows 11 にアップデートしたら、
日本語入力 IME に ATOK を指定していると、
メモ帳を起動時にフリーズするようになった。

日本語入力 IME に Microsoft IME を指定している場合、
メモ帳は特に問題なく起動する。


明日以降に調査予定。
JavaScriptでコピーボタンを実装する方法

## はじめに
JavaScriptを使ってコピーボタンを作る方法を知ったので、書いていく。
少し衝撃的だった

## 手順
1. textareaタグを生成する
1. textaretaタグの中にコピーしたい文言を入れ込む
2. bodyタグを取得してappendChildでtextareaを入れ込む
3. select()で選択状態にする
4. execCommand('copy')でコピー
5. textareaを消す

## コードの例
```javascript
const textarea = document.createElement('textarea')
textarea.textContent = password

const body = document.querySelector('body')
body.appendChild(textarea)
textarea .select()
document.execCommand('copy')
body.removeChild(textarea);
alert('copied password')
```
## ポイント
上記のコードでは変数passwordにコピーしたい文言が格納されていることを想定しています。
textareaタグを作って格納されている値をコピーして消去する。
これを一瞬で行っているわけですね。

## おわりに
一見複雑に見えて慣れてしまえば簡単ですね。
Emacs on Ubuntuでやっとしっくりくるフォント設定ができた件

# Emacs on UbuntuでNasuMフォントを使う

Emacs on Ubuntuでやっとしっくりくるフォント設定ができた。

## 先人の設定

先人たちの知恵により今までは以下のような設定をしていた。
でも、日本語とスペースが混じったコードでは悲しいことに少しずれる。

```elisp
(set-fontset-font t 'japanese-jisx0208 "TakaoGothic")
(add-to-list 'face-font-rescale-alist '(".*Takao .*" . 0.85))
```

例えばフォントをNotoの等幅にして、face-font-rescaleを1.1にしたりとか色々やってみたが日本語の有無で行間に影響を与えたりとどうもしっくりこない。

そういえば、VSCode on WindowsでNasuMフォント ([Nasuフォント](https://itouhiro.hatenablog.com/entry/20140917/font)の等幅版) というフォントを利用しているのを思い出した (作者様、公開に感謝申し上げます)。これを、Ubuntuでも使えるようにすれば良いと天啓を受けた。

## Ubuntuへのインストール

以下のリンクからアーカイブを取得。

<https://itouhiro.hatenablog.com/entry/20140917/font>

適切なディレクトリ (`~/tmp` など) で以下のコマンドを実行して取得したZIPファイル (バージョン相当の日付は取得したファイルに合わせる) を展開する。

```shell-session
unzip NasuFont-20200227.zip
```

展開されたファイルを `/etc/fonts/fonts.conf` に登録されているディレクトリにコピーする。今回は無難に `/usr/local/share/fonts` を選択した。用途を考えるとNasuMフォントだけでも良いのだが、他で使うかもしれないので全部コピーした。

```
sudo cp NasuFont20200227/*.ttf /usr/local/share/fonts
```

フォントが登録されていることを確認。

```shell-session
fc-list | grep -i nasu
```

## Emacsへの登録

もう、`init.el` を個別に書くのも面倒になってきたので、横着をしてOptionsから登録する。

1. これまでのフォント設定はコメントアウト。
1. メニューバーの「Options」から「Set Default fonts」を選択。
1. 検索ボックスに「NasuM」を入力して「NasuM Regular」を選んで、「選択(S)」をクリック。
1. メニューバーの「Options」から「Save Options」を選択。

私の環境 (Ubuntu 21.04) では、以下のコードが `~/.emacs.d/init.el` に追加されていた。

```elisp
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "NasuM" :foundry "Mig " :slant normal :weight normal :height 98 :width normal)))))
```

あとは、Emacsを再起動すれば反映されているはず。

あ、でも、まだ微妙に (全角1文字当たり1pxくらい) ズレている？

## 2021年10月15日追記

Optionsからフォントサイズを9pt (出力としては `:height 90`) にしたらズレなくなった。
レンダリングの誤差だろうか。
Apacheゼロデイ脆弱性 ～ドキュメントルート外にあるファイルにアクセスされる

##Webサーバー「Apache」にゼロデイ脆弱性 ～ドキュメントルート外にあるファイルにアクセスされる

自分のメモとして


###ディレクトリトラバーサル
ディレクトリトラバーサルとは、ネットワーク上の脆弱性を利用した攻撃手法の一種で、「../」を利用してディレクトリを遡り、本来はアクセスが禁止されているディレクトリにアクセスする手法のことである。または、そのような脆弱性のことである。

###参考サイト
https://www.jpcert.or.jp/at/2021/at210043.html

https://wizsafe.iij.ad.jp/2021/10/1285/

https://www.security-next.com/130424

https://yamory.io/blog/about_path_traversal_attack/


https://forest.watch.impress.co.jp/docs/news/1356326.html

https://www.javadrive.jp/apache/allow/index1.html

https://article.auone.jp/detail/1/3/7/48_7_r_20211006_1633487722507077

https://news.infoseek.co.jp/article/mynavi_2301876/

https://news.mynavi.jp/article/20211006-2008922/

https://forest.watch.impress.co.jp/docs/news/1356957.html


https://gigazine.net/news/20211006-apache-servers-exploited-zero-day-vulnerability/
ファイアウォールの影響で WSL2 からインターネットアクセスができないときの対処法

[一部のセキュリティ対策ソフトウェアがインストールされている環境では、WSL2 からインターネットアクセスできないことがあります](https://docs.microsoft.com/ja-jp/windows/wsl/faq#wsl---------------------------)。ファイアウォールの設定で除外することがベストですが、WSL2 はその特異さから相性が悪い製品・環境も存在します。[^1]

[^1]: 許可リストの制御に Windows のプロセス名や宛先ホストなどしか指定できず、WSL2 上のプロセスを安全に許可できないパターンなど。

WSL2 の主な用途は開発環境の構築でしょうから、いまどきスタンドアローンなのは辛すぎます。どうにかしたいですね。

## ローカル HTTP プロキシを利用する

そんなときは、[stone](http://www.gcd.org/sengoku/stone/Welcome.ja.html) や [Squid for Windows](https://squid.diladele.com/) などを利用してローカル HTTP プロキシを立てることで問題を回避できる可能性があります。まず、Windows 側でプロキシサーバを起動します。

```powershell
# Windows 側の 3128 ポートでプロキシを起動
stone proxy 3128
```

この状態で、HTTP プロキシとして Windows 側の 3128 ポートを参照するとインターネットと通信できるようになります。環境変数を設定してあげましょう。[^2] [^3]

[^2]: 出来ない場合はファイアウォールのログを確認し、必要に応じてプロキシのプロセスを許可リストに追加してください。

[^3]: 非対話シェルでも環境変数が設定されるよう、`~/.profile` か `~/.bash_profile` に追記することをお勧めします。`~/.bashrc` だと、たとえば VSCode の `devcontainer.json` でホスト側の環境変数を渡せなくなります。

```bash
proxyAddress=$(ip route | grep 'default via' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
proxyPort=3128
export http_proxy="http://${proxyAddress}:${proxyPort}"
export https_proxy="http://${proxyAddress}:${proxyPort}"
export HTTP_PROXY=${http_proxy}
export HTTPS_PROXY=${https_proxy}
```

WSL2 から Windows 側にアクセス可能な IP アドレスは `localhost` で引けないので、デフォルトルートとして指定されている IP アドレスを参照し動的に設定します。この IP アドレスは Windows を再起動するたびに再作成される WSL2 用の仮想アダプタのものなので、静的に設定するべきではありません。

## 参考リンク

* [WSL2側からWindows側のIPアドレスを調べる方法](https://qiita.com/samunohito/items/019c1432161a950892be)
* [devcontainer.json not loading custom localEnv values correctly using WSL2](https://github.com/microsoft/vscode-remote-release/issues/3456)
* [Linuxがほぼそのまま動くようになった「WSL2」のネットワーク機能](https://atmarkit.itmedia.co.jp/ait/articles/1909/09/news020.html)
pip listで比較元リストと比較先リストの比較（pythonでterminalを操作）

### 何故このようなprogramを作ろうと思ったのか
>というと、pipに、”python3 -m pip insutall モジュール名”でパッケージをインストールすると、インストールしたモジュール以外の関連モジュールもインストールされる。
そこで何がインストールされたか調べるために、Numbsers（exact関数）でセコセコ調べていたが、手間がかかるので　pythonで処理できないかと考えて作ってみた。
# ここで学んだこと[pythonの学習]
>①Terminalを返さずに直接”pip list”をテキストファイルに書込
②pythonからTerminalを操作する —- subprocess
③テキストファイルに書き込む
④テキストの前後の一部抹消
⑤テキストファイルから読み込みlistにする
⑥ファイルをコピーする　—-  shutil
⑦llistの比較（差集合）
⑧if \_\_name__ == ‘\_\_main__’の使い方


## ■環境
>macOS Ventura 13.2
python 3.10.4

## ■事前準備
このprogramと同じディレクトリに「o_list.txt」というテキストファイルを事前に作成してください。（中身は入りません）

## ■Terminalへの比較結果の表示
![IMG_6971.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634708/449a9a2b-8ad6-da53-d417-5f1124c711cc.png)




## ■完成
```python
# Terminalで出力したpip listを前回出力したpip listと比較して変化を確認するprogram
# -------------------------------------------------------------
import subprocess as sb     # pythonからterminalを操作するpacgage
import pprint               # 整形して出力・表示
import shutil               # Fileの copy
import platform             # OS（プラットフォーム）の判別
# -------------------------------------------------------------]
# ターミナル表示の際、罫線を表示
def p():
    print('-'*50)
    print('')

# --------------------------------------------------------------
# o_list.txtファイルからdataをo_list(旧リスト)に代入する
with open('o_list.txt','r') as f:
    o_list = f.read().split('\n')

# -------------------------------------------------------------
# pip listを実行し、出力を取得する
result = sb.run(['pip', 'list'], stdout=sb.PIPE)
# --------------------------------------
# 出力を文字列に変換し、行ごとに分割する
output = result.stdout.decode().split('\n')
# --------------------------------------
# リストの最初の2行と最後の1行は不要なため、スライスで除外する
package_list = output[2:-1]
# --------------------------------------
# 各行の文字列をタブで分割し、パッケージ名とバージョンを取得する
package_info_list = []
for package in package_list:
    name, version = package.split()
    lst = name + ' ' +version
    package_info_list.append(lst)
# --------------------------------------
# package_info_listをテキストファイルに書き込む
# ファイル名と書き込みモードを指定してファイルを開く
with open("output.txt", "w") as f:
    # リストの要素を1行ずつファイルに書き込む
    for item in package_info_list:
        f.write(str(item) + "\n")
# -------------------------------------------------------------
# テキストファイルからn_list(新リスト)に代入する
with open('output.txt','r') as f:
    n_list = f.read().split('\n')
# --------------------------------------
# 「output.txt」を次回の比較用に「o_list.txt」に書き込む
shutil.copy('output.txt', 'o_list.txt')
# --------------------------------------
# listの比較（差集合）
p()
result = list(set(n_list) - set(o_list))

if n_list==o_list:
    print('***比較した結果,前回と差異はありません***')
else:
    print('[***比較した結果,次の"Package"が "追加"もしくは"update"、されています。***]')
    pprint.pprint(result)
p()
print('pip listの比較（差集合）を終了します。')
p()
```
Javaのequalsメソッドについて


```equalsSample.java

//(~省略~)

// 正しい例
private final String 定数1 = "hogehoge";
private String 変数1 = null;

if(定数1.equals(変数1)){
    System.out.println("同じ値です");
}else{
    System.out.println("違う値です");
}

// nullPointExceptionは発生せずに、「違う値です」が出力される。

// 悪い例
private final String 定数1 = "hogehoge";
private String 変数1 = null;

if(変数1.equals(定数1)){
    System.out.println("同じ値です");
}else{
    System.out.println("違う値です");
}

// nullPointExceptionが発生する。

```

上記のように、equalsメソッドで定数と変数を比較する場合は、定数を先頭に記述すると良い。コードレビューで指摘を受けた内容になります。

コメントでご指摘がありました、nullPointerExceptionを回避するためには、nullを明示的にチェックをする処理を追加すべきなので、下記のように修正したいと思います。

```equalsSampleCheckIsNotNull.java

//(~省略~)

// 正しい例（nullチェックを追加)
private final String 定数1 = "hogehoge";
private String 変数1 = null;

if(変数1 != null){
    if(定数1.equals(変数1)){
        System.out.println("同じ値です");
    }else{
        System.out.println("違う値です");
    }
}else{
    System.out.println("nullです");
}

// nullPointExceptionは発生せずに、「nullです」が出力される。

// nullチェックを行って改善した例
private final String 定数1 = "hogehoge";
private String 変数1 = null;

if(変数1 != null){
    if(変数1.equals(定数1)){
        System.out.println("同じ値です");
    }else{
        System.out.println("違う値です");
    }
}else{
    System.out.println("nullです");
}
// nullPointExceptionが発生せずに、「nullです」が出力される。

```
リモートワークにおける会議ツールの変遷

2020年はコロナによる緊急事態宣言が出され、今までリモートワークしていなかった企業が続々とリモートワークへ移行した年だと思います。DMMも元々は出社前提の勤務体系でしたが、緊急事態宣言以降はリモート推奨に代わり、かれこれ1年半程はフルリモートで業務を行なっています。そういえば会議のツールも変遷があったなぁと思ったので、記録的な意味でも書いておこうかなと思いました。

# 混乱期（GoogleMeet、Slack通話、Zoom乱立時代）
初期の頃はZoomがまだそこまで全社的に導入されていなかったため、GoogleMeetやSlackの通話を使ったりもしていました。一瞬WebEXを使ったりとか割と手探り状態だった気もします。Zoomが導入された直後も今までの流れからSlack通話で会議をしたりもしていました。

# Zoom一択期
Zoomが導入されたものの、わざわざ設定しなくてもSlack通話できるし、設定の面倒さもないそんな時代に終わりを告げたのはZoomの品質の高さです。GoogleMeet、Slack、Zoomの中で映像と音声の品質が一番良かったがZoomでした。Zoomで会議をした人が、Zoomいいよって広めていった気がします。SlackからZoomの会議を開始できる様になったのもあり、Zoomに置き換わっていった気がします。

すこし話題はずれますが、障害対応の時にフルリモートだと関係者あつまれーってZoomのURLをSlackに貼るとみんなで即会話できたので、ある意味オフラインでどこかの会議室に集まるより手軽で便利でしたね。リモートワークでZoomでの会議が広まるにつれて会議室の予約も不要だし、いつでも会議したいメンバーの空き時間さえあわせれば会議を開催できたのは良かった気もします。
一方で、Zoomの会議を連続で行うとやけに疲れたのもこの辺です。移動時間がないので会議が連続したりしていた所為なのかも？あと、今は慣れちゃいましたけどオンラインの会議って慣れない時には疲れましたよね。

# チームにdiscord導入期
Zoomで会議を常にしていたんですが、ここで新たに勢力を拡大してきたのがdiscordです。Zoomの会議で顔を出すのか出さないのか各社色々とあると思いますが、DMMの場合は完全に個人の自由です。さすがに面談や面接の時には出さないといけないと思いますが、それ以外は各人の判断に任されています。たとえば、会長と会議をする際に会長が顔出していても一方のメンバーはアイコンなんて時もあります。それはさておき、チームでの会議、特に何か相談する時に一々Zoomを起動するっていうのがだんだんと面倒になってきて、そこでdiscordが広まってきました。他部署との会議はZoom、チームの朝会や夕会、なんらかのイベントや相談などはdiscordでやるのが確かに気楽でいいですね。相談したい時に他の人を引っ張ってこられるのも便利だったりします。

# Zoom再考期
Zoomとdiscordな感じで1年以上やってきたのですが、Slackにハドルミーティングが導入されたり、GoogleMeetがやたら機能追加、品質改善されたりしてきたのが最近かなと思っています。Zoomって謎の設定がかなりたくさんあったりするので、社外との会議はZoomでもいいのかもしれないですけど社内ならもしかしてGoogleMeetの方がブラウザから起動して便利なのでは？という気もしています。Googleカレンダーとの相性も悪くないですしね。

# （番外編）イベントの懇親会でSpatial Chat流行った回
会社の勉強会やLT会、ミートアップなどを行った際に、Spatial Chatが一瞬流行りましたね。その後有料になって、あまり使われなくなってしまった気がします。面白かったんですが、なんか違う気もして…Zoomのブレイクアウトルームも何か違う気がするので、大人数が集まって何か話す際のいいツールっていうのはまだ無いのかもしれません…。

今はこんな感じなんですが、こんなツールを使うといいよとか、こんな時にはこれ、みたいなのがあればぜひ教えてください〜。
Zshでコマンドを実行したらプロンプトを書き換える

# やりたいこと
プロンプトの右側にコマンドを実行した時刻を残したい。でも、現在のプロンプトには時刻の代わりに、gitのブランチとかの情報を表示したい。

以下のような感じで
![2110061950.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44208/d486b232-99bc-300b-1b16-9978d5b41a97.gif)

# 解決策
`precmd`フックと`accept-line`を使って、`RPROMPT`変数を交互に書き換えてやるとよいです。

```zsh:.zshrc
# setopt等は適当にやってある

# 現在のプロンプトにはVCSの情報を表示
current-rprompt() {
  RPROMPT='${vcs_info_msg_0_}'
}
add-zsh-hook precmd current-rprompt

# 以前のプロンプトにはコマンドラインを確定した時刻を表示
update-rprompt-accpet-line() {
  RPROMPT='%D{%y/%m/%d %H:%M:%S}'
  zle .reset-prompt
  zle .accept-line
}
zle -N accept-line update-rprompt-accpet-line
```
[CakePHP] 型情報付きでテーブルオブジェクトを取得する

PHPカンファレンス2021でPHPStormがジェネリクス対応したと聞いて。
(追記)[エンティティ版はこちら](https://qiita.com/hoogee/items/545e045e01f9b9cc2526)

### 課題
通常CakePHPではテーブルオブジェクトをTableRegistry経由で取得するが、これだと型はCake\ORM\Table型としか分からない。

```
$articles = TableRegistry::getTableLocator()->get('Articles'); //Cake\ORM\Table型とIDEに認識される
```

実際にはメソッド等を定義している具体的なArticlesTable型として認識してほしい。
Lean controllerだFat modelだと綺麗にしたいのにこれではIDE補完が使えない。
かと言って毎度```@var```を書くのも間抜けすぎる。
世界中のCakePHPユーザーが同じことで困ってそうだがなぜか解決策が見つけられなかった。

### 解決策
ということで、ジェネリクスを使用した回避策がこれ。

```
use Cake\ORM\TableRegistry;

/**
 * @template T
 * @param class-string<T> $fullTableName 完全修飾テーブル名
 * @return T of \Cake\ORM\Table
 */
public static function getTable(string $fullTableName)
{
    return TableRegistry::getTableLocator()->get($fullTableName);
}
```

使用例

```
$articles = getTable('\App\Model\Table\ArticlesTable');

または

$articles = getTable(\App\Model\Table\ArticlesTable::class);

または

use \App\Model\Table\ArticlesTable;
$articles = getTable(ArticlesTable::class);

いずれも\App\Model\Table\ArticlesTable型としてIDEに認識される

```

### 留意事項
ジェネリクスを使う都合上、普通のTableLocatorのように単にgetTable('Articles')と書くことはできず、完全修飾名で指定する必要がある。
使用例の真ん中の書き方であればIDE補完付きで書くことができるのでそれほど手間ではない。
オンライン診療はヘルステック領域に影響を与えたのか

#はじめに

私は精神科医として在宅医療に携わっています。在宅医療は医療業界で新しい分野ということもあり、まだ仕組みが固定されておらず工夫の余地が多いので自分たちをスタートアップ企業だと考えて、様々なことにチャレンジしてきました。
新たなチャレンジの一つとして、今回プロトアウトスタジオに入学したところ、その宿題としてこのQiita記事を書いています。
今まで全くプログラミングをやったことがない超初心者なのですが、調べたり教えてもらったりしながら現場で作りたいと思ったものを自分で作れるようになれればと考えていますので、よろしくお願いします。

#オンライン診療との関わり

私自身、2018年4月からオンライン診療が保険適応となる際、ここで参考とされた[ICTを活用したかかりつけ医機能強化事業](https://www.integrity-healthcare.co.jp/pdf/press%20release.pdf)に参加をしたこともあり、仕組みが作られるところから関わることができました。
「保険適応化することでオンライン診療が一気に盛り上がる！」と期待されていたなか、実際に保険適応となってみるとその条件が厳しく（疾患と治療の限定）、診療報酬も安かった（対面診療の３割ほど）ことから、ほとんどの医療機関で利用されない状況でした。

しかし、新型コロナウイルスの感染拡大下で医療への負担が増す中、2020年4月に厚労省からの事務連絡がありオンライン診療の大幅な規制緩和が行われました。これにより、疾患と治療の制限は無くなり、ほぼ対面診療と同じことがオンライン診療でも認められることになりました。このため、一般にもオンライン診療への注目が集まり、LINEヘルスケアがオンライン診療に参入することも[話題](https://corporate.m3.com/press_release/2020/20200428_001583.html)になりました。

オンライン診療の仕組みづくりに関わるなかで保険診療内で新しいことを始める難しさを感じた一方で、ヘルステックの活用によって医療がこれから大きく変わるであろうことを痛感したことが、今回のプロトアウトスタジオへの入学にも影響しています。

#今回の課題

そして今回の課題、「QiitaのAPIにアクセスしてデータの取得及び、表示方法や統計をとり、Qiita記事としてまとめましょう。」というものだったので、オンライン診療の保険診療化と規制緩和の二つのタイミング（2018年4月と2020年4月）にQiitaでオンライン診療について書かれた記事の数に変化があったかを調べました。

#環境

・node v16.10.0
・Visual Studio Code 1.60.2
・axios 0.22.0

#サンプルコード

```JavaScript:app3.js
const axios = require("axios");

async function main() {
  let response = await axios.get(
    "https://qiita.com/api/v2/items?per_page=100&page=1&query="+encodeURIComponent("オンライン診療")
  );

  for (let i = 0; i < response.data.length; i++) {

   console.log(response.data[i].created_at);
 }
}

main();
```


#結果と考察

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2051906/8fde50c8-79bb-c795-60e2-4a5fd604abf3.png)


オンライン診療では執筆時点（2021年10月5日）で、31件しか記事がなかったため、３ヶ月ごとの記事数としました。
少ない記事数ではありますが、オンライン診療が保険適応となる見込みがたった2017年10月から12月と、オンライン診療の規制緩和があった2020年4月に記事数が増えていることが確認できました。

#「ヘルスケア」についても調べる

とはいっても、オンライン診療に関する記事自体が少ないとう課題も感じました。
そこで、私のように保険診療内でのオンライン診療への限界を感じながら保険診療外でのヘルステックの盛り上がりがあるかもしれないと考えました。
「ヘルスケア」の記事数について調べると、326件。
この月別の記事数を調べてみました。

＃サンプルコード

```JavaScript:app4.js
const axios = require("axios");

async function main() {
  let response = await axios.get(
    "https://qiita.com/api/v2/items?per_page=100&page=1&query="+encodeURIComponent("ヘルスケア")
  );

  for (let i = 0; i < response.data.length; i++) {

   console.log(response.data[i].created_at);
 }
}

main()
```


一回に100件しか記事数を取ってこれなかったので、
2,3,4と繰り返して記事数を取ってきました。

#「ヘルスケア」についての結果と考察

それをまとめたのがこちら。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2051906/0b07423d-671f-b830-adcb-c402a75f8014.png)

二つのタイミングの影響はあまり感じられませんでした。

＃「医療」についても調べてみる

今度は「医療」について調べてみると記事数は1375件。
これも、先ほど同様の方法で２、３、４、、、１４と繰り返して記事を取ってきました。
（もっと上手なやり方があるんでしょうが、宿題の期限もあり調べる余裕なし、、）

#「医療」についての結果と考察

まとめたのがこちら。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2051906/af660491-889c-09c6-c5c8-25c1ee72a137.png)

「ヘルスケア」同様に、二つのタイミングの影響はあまり感じられませんでした。
一方で、なぜか毎年12月に記事数が増えていることに気づきました。
ヘルスケアを見返してみても同様の傾向があるようです。
Qiitaの記事自体が12月に多いのか、それとも他に要因があるのか、調べる時間の余裕がないです。
ということで、今回はここまで。

結論は、**Qiitaで見る限り「オンライン診療がヘルステック領域に与えた影響はごく軽度」**ということでお願いします。
[GAS]GoogleAdSenseAPI v2 スプレッドシート記入スクリプト

##背景

AdSenseAPIv1.4が2021年10月12日付で終了するのでそれに伴ってGASを書き直さなければならない

https://developers.google.com/adsense/management/reference/rest



##スクリプト
```javascript:v1.4

function writeAdsenceReport() {
  var spreadSheet = SpreadsheetApp.getActive();
  var sheet = spreadSheet.getSheetByName("raw(AdSence)");
  var adClientId = 'ca-pub-xxxxx';
  var startDate = Moment.moment(spreadSheet.getSheetByName("Variables").getRange(1, 2).getValue()).add(1, 'days').format('YYYY-MM-DD');
  var endDate = Moment.moment(spreadSheet.getSheetByName("Variables").getRange(2, 2).getValue()).add(1, 'days').format('YYYY-MM-DD');
  
  var metrics = ['PAGE_VIEWS', 'INDIVIDUAL_AD_IMPRESSIONS', 'CLICKS', 'PAGE_VIEWS_RPM', 'INDIVIDUAL_AD_IMPRESSIONS_RPM', 'EARNINGS']
  var args = {
    'metric': metrics,
    'dimension': ['DATE'],
    'filter': ['AD_CLIENT_ID==' + adClientId, 'DOMAIN_NAME==tsukulink.net'],
    'useTimezoneReporting': true,
  };
  var report = AdSense.Reports.generate(startDate, endDate, args).getRows();
  sheet.getRange(sheet.getLastRow()+1, 1, report.length, metrics.length + 1).setValues(report);
}
```

```javascript:v2

function writeAdsenceReport() {
  var spreadSheet = SpreadsheetApp.getActive();
  var sheet = spreadSheet.getSheetByName("raw(AdSence)");
  var accountName = 'accounts/pub-xxxxx';

  
  var metrics = ['PAGE_VIEWS', 'INDIVIDUAL_AD_IMPRESSIONS', 'CLICKS', 'PAGE_VIEWS_RPM', 'INDIVIDUAL_AD_IMPRESSIONS_RPM', 'ESTIMATED_EARNINGS']
  var report = AdSense.Accounts.Reports.generate(accountName, {
    filters: ['DOMAIN_NAME==tsukulink.net'],
    metrics:  metrics,
    dimensions: ['DATE'],
    dateRange: "LAST_7_DAYS",
    orderBy: ['+DATE']
  })

  if (report.rows) {
      // Append the results.
      sheet.getRange(sheet.getLastRow()+1, 1, report.rows.length, report.headers.length)
        .setValues(report.rows.map(row => row.cells.map(cell => cell.value)));

      Logger.log('Report spreadsheet created: %s',
        spreadSheet.getUrl());
    } else {
      Logger.log('No rows returned.');
    }
}
```

#主な変更点

###ClientID

https://developers.google.com/adsense/management/release_notes
リリースノートより

>>All deprecated methods in v1.4 have been removed. This includes resource methods that did not require an accountId. In v2, the accountId is specified in the parent field.
Per Google API standards, resources are now identified by a name field. For example, an AdClient’s name would look like accounts/{accountId}/adclients/{adClientId}. Additionally, in v1.4 the resource ID was used as a reporting dimension, but in v2 that value is now available through a reporting_dimension_id field on several resources.

上記のような変更点があったみたいですが、なんのことかあまり理解できていないです（わかる方いたらご教授いただきたいです）


とりあえずv2ではIDではなく名前で識別するっぽいので``accounts/pub-数字``にすれば良さそう。



###クエリパラメーターの命名が一部複数形に
``metrics``, ``dimensions``などのフィールド名が複数形に変更されている。細かい部分で気づきにくいので注意。

###reportの出力形式がJSONに
今まではreportの出力形式は二次元配列だったのですが、v2ではJSONになっています。

ただし、
``report.rows.map(row => row.cells.map(cell => cell.value))``
こちらを実行していただければ今まで通り二次元配列で出力してくれます。



##終わり
他にも細かい変更点ありますがざっ影響与えそうな変更点だけ挙げました。
参考になれば幸いです。
[Ruby] AtCoder過去問 D - Brick Break

##はじめに
AtCoder過去問D問題をRubyで解いてみました。
よろしくお願いします。

問題はこちらから確認してください↓

https://atcoder.jp/contests/abc148/tasks/abc148_d

##D - Brick Break
まずは入力を受け取ります。
後に、繰り返し文を使用して答えを出力させるので変数cntを用意して0を代入しておきます。

```ruby
n = gets.to_i
a = gets.split.map(&:to_i)
cnt = 0
```

この問題の引っかかりやすいポイントとして、レンガを一つも壊す必要がなかった時と、レンガを一つも壊せずすぬけが満足しなかった場合の出力を同じにしてしまうと不正解です。

前者は0を出力させます。後者はすぬけが満足していないので-1を出力させなければなりません。

簡単に言えば、とりあえず配列aに1が存在していなかったらすぬけは絶対に満足できないので-1を出力します。逆に1があればすぬけは必ず満足するので、レンガを壊した数を出力させます。

if文で-1を出力させる記述を先に書いておきます。

```ruby
n = gets.to_i
a = gets.split.map(&:to_i)
cnt = 0

if a.include?(1)

else
  puts -1
end
```


それではif文の```a.include?(1)```がtrueのときの処理を書いていきます。
とりあえず書き終えたコードを見てください、解説を後述します。

```ruby
n = gets.to_i
a = gets.split.map(&:to_i)
cnt = 0

if a.include?(1)
  (1..n).each do |i|
    if a.index(i) == nil
      break
    else
      cnt += a.index(i)
      a.shift(a.index(i)+1)
    end
  end
  puts cnt + a.length
else
  puts -1
end
```

解説します。

まず、ブロック変数iに1~nの数字が一つずつ入れていくeach文を作ります。
```a.index(i)```とすることで1から順に各々のインデックス番号を表現できます。
もし対応するインデックス番号が存在しない数字が入ったときはそのとき処理を止めます。

インデックス番号が存在しているならばelseで処理を続けます。
cntにa.index(i)を足して代入します。

例えばiが1であれば配列aの1の場所を特定します。それはa.index(i)として表現されています。
a.index(i)はiのインデックス番号を表現しているのと同時に、iにたどり着くまでのレンガの数を表しています。つまり壊したレンガの数をcntに足しているということです。

足した後は左から（配列の先頭から)iまでをshiftメソッドで消します。
そして繰り返すと言った感じです。

最後にcntとa.lengthを足したものを出力すればすぬけが壊すレンガの数が分かります。
a.lengthを足すのは最後に余った(配列aに残った)レンガも壊すこと可能なのでcntに含めてあげています。

##おまけ
a.index(i)がいくつもeach文の中に出てくるので変数に代入しておくと少し処理が早くなりました。

```ruby
n = gets.to_i
a = gets.split.map(&:to_i)
cnt = 0

if a.include?(1)
  (1..n).each do |i|
    x = a.index(i)
    if x == nil
      break
    else
      cnt += x
      a.shift(x+1)
    end
  end
  puts cnt + a.length
else
  puts -1
end
```
Cloud9で削除してしまったファイルの復元方法

こんにちは！テックアカデミーのWebアプリコースを受講している駆け出し高校生エンジニアの安田駿介です。

今回は、「Cloud9で削除してしまったファイルの復元方法」というテーマで解説していきます。

なお、基本はGitHubから持ってくるのが基本です。

しかし、commitしていなかったら、この手法を使っていきましょう。

#復元手順
１．該当のファイル（もしくは同名のファイルを再作成して）を開く
２．上部メニューの「File」そして「show file revision history」をクリック
３．巻き戻しのヤツ（名前わかんない）が出てくる
４．Revertボタンを押す

##１．該当のファイル（もしくは同名のファイルを再作成して）を開く
![d75143acae4660244ddc317fd9cb925b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903726/cbb94918-ccfe-0474-7a2b-837741b6bb0a.png)
僕の場合は、favorites_controller.rbというファイルを開きました。

##２．上部メニューの「File」そして「show file revision history」をクリック
![184f687db8987158d9595624c88d3d59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903726/5bab7751-54eb-0326-7c2b-e4c65e2c5947.png)

##３．巻き戻しのヤツ（名前わかんない）が出てくる
![40972d98cb54431bfe2e77c099def7d3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903726/82c95bcb-fe21-d160-4ea5-2820cd022690.png)
左側に動かすと、保存履歴から順に表示されます。

##４．Revertボタンを押す
戻したい時点で「Revert」を押してください。
![23e84f0173a30d4c1828aa35d62bf1c6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1903726/4f64f02c-eb29-326c-c22f-9887c8c8dd89.png)
これで、巻き戻し完了です。

#開発環境
・AWS
・Cloud9
・Ruby 3.0.0
・Rails 6.1.3.1
Androidアプリを作ってストアに公開してみる

# 概要
先月、Googleから届いた「開発アカウント消しちゃうぞ」という警告メールから重い腰をあげ、
ひとまず、３つほどアプリを開発してストアに公開してみた。
その時の、備忘録です。

あ、もっと細かく知りたいことがあれば、コメントください。
記事書きます。

https://qiita.com/TAKANEKOMACHI/items/6aaf6e6ffe9899140945

# 備忘録いろいろ
## 1) コンソールがかなり違っている
正直、Google Play Consoleにログインするのも久しぶりだったぐらいなので、自分の中のうろ覚えの状況から「差異」の確認に手間取りました。

## 2) AABって何？
以前触っていたときは、会社のアカウントだったのでAPKファイルを登録して・・・と思ったらAAB？なにそれ？
みたいになった。

ちなみに、AABを生成して登録しただけだと、難読化が・・・とか警告をくらいますが、対応すると容量がさらに半分ぐらいまで減ります。（今回、「まとめテミル」に入れました）

## 3) SHA1いろいろ
### 3-1) SHA1確認コマンドがない！
これは、Android Studio側の件ですが、Android Studioの右側にあるGradleタブから、signingReportを選んで・・・・って、ないぞ！！
→設定から表示させることができます。
### 3-2) 内部テストだと、SHA1が違うといわれる
Android Studioで生成するSHA1はDebug用なので、Release用のSHA1が必要なのです。
特に、FirebaseでAnalyticsを使う、GCPのGoogle MAPを使うなどアプリで実装している場合は、Release用のSHA1も登録しないとエラーになります。
ストア登録するとSHA1のRelease版は入手可能です。（2種類あります）

## 4) AABが作れない！
署名済みバンドル/APKの生成からAABを作ろうとすると、キーの作成で「キー保管パス」と「キー」のところでそれぞれパスワードが必要ですが、別のパスワードを設定するとエラーになります。
2つとも同じパスワードにする必要がある。（Android Studio4.2ぐらいから変わった）

## 5) 内部テストでも審査がいるの？
ストアにAABを登録して、内部でテストしようとすると、審査が必要になります。
適当なストア説明文や画像を貼っただけだと、審査NGになる・・・・
一応、内部テストはできますが、結構面倒。

## 6) AdMobの広告が表示されない
これは、結構悩んだ。
結局は、「時間が解決する」という状況が多い。
AdMob側で口座登録して、すべての承認が通っていても出てこない。
プライバシーポリシーへのリンクを張ったり、App-ads.txtをサーバーへ設置してステータスがOKになるのをみたり、いろいろやっても「数分」～「数時間」では解消しないので「数日待つ」のがいいらしい。

ちなみに、AdMobメディエーションを試すため、NENDへアカウントを作成して追加してみた。
NENDのメディエーション対応は、「build.gradle」（アプリ、プロジェクト）への追記だけではダメで「settings.gradle」へも追記が必要だった。

## 7) 内部テストを終了してもベーターユーザーが解除されない
内部テストへの招待をした人に、アプリが公開されたので、招待時のURLからテスト終了をしてもらい、本番のアプリをインストールしてもらう流れにしたが、いつまでたっても「あなたはベーターテスターです」の表示が出てきてしまう。

私は、結局テスト端末を初期化したが、どうやらこれも数日待つのがいいっぽい。
（ただ、内部テスターを終了して本番アプリを入れている人のカウントがGoogle Play Consoleには反映されないのが謎）

# 実績
頑張った！1か月で3つ即興で作ったのでヘロヘロ・・・

https://play.google.com/store/apps/developer?id=TakanekoSoft&hl=ja

https://play.google.com/store/apps/details?id=com.sekai.tsukuru.app.quickmatome&hl=ja

https://play.google.com/store/apps/details?id=com.sekai.tsukuru.app.deviceinfoviewer&hl=ja

https://play.google.com/store/apps/details?id=com.sekai.tsukuru.app.messageticker&hl=ja
.values if is_a?

* source

```ruby
a = ["apple", "bread", "cookie"]
ah = {"a":"apple", "b":"bread", "c":"cookie"}

def p_v(v)
  r = v.values if v.is_a?(Hash)
end

p p_v(a)
p p_v(ah)
```

* result

```sh
nil
["apple", "bread", "cookie"]
```

## Refs.
* [is_a?(Arry)メソッド](https://qiita.com/ryuuuuuuuuuu/items/a6a610e1acbbcff45da8)
* https://docs.ruby-lang.org/ja/latest/method/Hash/i/values.html