OmniAICreator/Qiita-1.07M huggingface dataset:
【CSS Hover】CSSだけで、円のアニメーションを表現する

#CSSだけで、円のアニメーションを表現する
今回のこの記事のゴール
**要素のHover時に、動く円を再現できるようになる**
ことです

以下が、デモになります

<p class="codepen" data-height="716" data-theme-id="dark" data-default-tab="css,result" data-user="kazuhito-nakayama" data-slug-hash="WNbOydE" style="height: 716px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Circle Animation">
  <span>See the Pen <a href="https://codepen.io/kazuhito-nakayama/pen/WNbOydE">
  Circle Animation</a> by Kazuhito Nakayama (<a href="https://codepen.io/kazuhito-nakayama">@kazuhito-nakayama</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

ポイントは、
```@keyframes```
と
```border-radius: 40% 40% 50% 40%/30% 50% 50% 50%;```

の部分。

@keyframesとは,
animationプロパティとセットで使うプロパティで、
animationは「設定」、
@keyframesは「動き」を形作ります。

今回では、
circle:hoverセレクターに対して、

border-animation（animation-name:適用させたい動きの名前）、
3s(animation-duration：アニメーション一回分の時間の長さ)と、
infinite(animation-iteration-count：アニメーションの繰り返し回数を指定する)、
liner(animation-timing-function：アニメーションのタイミング・進行割合)

を定義しています。

その上で、@keyframes上で、transformプロパティを用いて、３６０回転のバリューを設定しているので、永遠にぐるぐる楕円が回転し続けるのです

詳しいanimationプロパティ
https://sterfield.co.jp/designer/css3%E3%81%AEanimation%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%E3%82%89%E4%BA%88%E6%83%B3%E4%BB%A5%E4%B8%8A%E3%81%AB%E7%B0%A1%E5%8D%98%E3%81%A0%E3%81%A3%E3%81%9F%EF%BC%81/

また、

```ruby:CSS
border-radius: 40% 40% 50% 40%/30% 50% 50% 50%;
```

こちらのコードについては、「え、こんなborder-radiusの書き方あるんだ！」と驚かれる方も中にはいらっしゃるかと思います。
しかし、不規則な丸を描く際には、このような記述方法も用いたりします！
この書き方はつまり、

```ruby:CSS
border-top-left-radius: 40% 30%;
border-top-right-radius: 40% 50%;
border-bottom-right-radius: 50% 50%;
border-bottom-left-radius: 40% 50%;
```

と同義で、それぞれの四辺に対して、楕円形を描くように角をborder-radiusで表現しているのです


**参考**

webクリエイターボックス
https://www.webcreatorbox.com/tech/border-radius

HPコード
https://haniwaman.com/css-animation/
WEBサイトの最終更新日を知る方法

今見ているWEBサイトがいつ更新されたか知りたいという時が、10年に1回くらいはあるかもしれません。ないかもしれません。

そんな時に使える魔法の呪文がこちらです。

```
alert(document.lastModified);
```

ブラウザのデベロッパーツールを開いて、
Consoleに上記の呪文をコピペしてエンターキーを叩けば、そのページの最終更新日時（かもしれない）がアラートで表示されます。

## lastModifiedとは

「lastModified」は和訳すると「最終更新日」。

上記の呪文は、
**ファイルがサーバにアップロードされた日時（タイムスタンプ）**を
返してくれているんですね。

なので、WEBサイトの最終更新日が分かる、は
実は正しいようでちょっと間違っています。

何も情報を更新していないのに、ファイルをアップロードし直している可能性だってあります。

さらにHTMLファイルが更新されていなくても、
CSSファイルや画像ファイルが更新されている可能性はあるのです。

逆にいうと、ファイル毎で最終更新日時を知ることが出来ます。
例えばそのページのCSSファイルを別タブで開くなどして、上記の呪文をConsoleで叩けば、CSSファイルの最終アップロード日時がわかります。

ちなみに、デベロッパーツールの「Network」から、そのページで使用されているファイルの各種情報を確認できたりもします。
「Headers」の中の「Response Headers」に「last-modified」というのがあり、
それがそのファイルがサーバにアップロードされた日時になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132363/8026eaf1-a256-fa81-be08-d9b4fbaec3b1.png)
※時間の表記がGMT(国際的基準時刻)なのでそこだけ要注意です。

## あくまで静的なページでのみ

この方法で最終更新日時を知ることが出来るのはあくまで静的なページになります。

動的に生成されたページでは、ページを見始めた日時（サーバにリクエストした日時）が返ってくると思います。

例えば、Qiitaがまさにそうです。
Qiitaの無数の記事のページというのは、
サーバにファイルがアップロードされているわけではなく、データベースに保存された情報をもとに動的に生成されているページになります。

FacebookとかTwitterとかGmailも同様ですね。

SPA（シングルページアプリケーション）のサイトでは、最終更新日時とはつまりユーザーが閲覧（更新）した時間になります。
Illuminate\Database\QueryException  : SQLSTATE[HY000]: General error: 3780 Referencing column 'user_id' and referenced column 'id' in foreign key constraint

user.idとhoges.user_idでリレーションをはるmigrationを作るとき。

#### 前提
laravel version 6.9.0

hogesテーブルのエラー

```
Illuminate\Database\QueryException  : SQLSTATE[HY000]: General error: 3780 Referencing column 'user_id' and referenced column 'id' in foreign key constraint 'hoges_user_id_foreign' are incompatible. (SQL: alter table `hoges` add constraint `hoges_user_id_foreign` foreign key (`user_id`) references `users` (`id`))
```


古いやり方を見て、そのままやってうまく行かなかったパターン。
公式を見るのが大事ですね。

```php:失敗時
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
```


```php:成功時
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
```

[Database: Migrations \- Laravel \- The PHP Framework For Web Artisans](https://laravel.com/docs/6.x/migrations#foreign-key-constraints)
yum/rpmコマンドがフリーズする

#序章
yumコマンドのinstallもsearchもinfoも全部止まる。
なぜかsudoしないときは動く。
rpmでのインストールも止まる。
なんでやねん！

#答え
まずはどうしたら治ったか。

```
rm /var/lib/rpm/.rpm.lock
rm /var/lib/rpm/.dbenv.lock
rm /var/lib/rpm/__db*
rpm --rebuilddb -vv   
yum clean all
```

#経緯

```
sudo yum install httpd
```

しーん

このアンサーを参考にするも治らず。
https://superuser.com/a/518252

```sh:これだけじゃダメだった。
rm /var/lib/rpm/__db*
rpm --rebuilddb -vv   
yum clean all
```

固まる。


どれかのリポジトリがおかしいわけでもない
yumのエラーメッセージは空。

yumではなくrpmコマンドでインストールはできるのか？と思い


```
sudo rpm -ivhv ***.rpm
```
↑ -vv とすると詳細なログが出る

ログは、

```
D: ============== ./***.rpm
D: loading keyring from pubkeys in /var/lib/rpm/pubkeys/*.key
D: couldn't find any keys in /var/lib/rpm/pubkeys/*.key
D: loading keyring from rpmdb  ←ここで止まる。
```

永久に返ってこない。
エラーもヤッパリでない。


なんでかな～～


と思ったら、

```sh:/var/lib/rpm/の下。なんかいる
...
-rw-r--r--  1 root root    0 Dec 26 16:52 .dbenv.lock
-rw-r--r--  1 root root    0 Dec 26 16:52 .rpm.lock
...
```

これ消していいのかな？と思いググったら、消しているstackoverflowがあったので、実施。

無事直りました。
これでAnsibleを入れて...ぐふふ


#まとめ
（これは経験則ですが、）コマンドの置いてあるディレクトリを見よう。

```sh:パスが通ってればコマンドファイルの場所が出るよ
whereis rpm
```
あとpython3を入れてしまって、というパターンの場合は他をあたってください。


#おまけ
```sh:yumがフリーズしたときにkillするワンライナーコマンド
pgrep yum | xargs sudo kill -9
```
Reactでeventの属性の取得


Select, Optionで各種属性を取得する方法に迷ったのでメモ。
見た目は下記のような最低限。

![スクリーンショット 2019-12-26 16.43.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/0598ba28-179c-2873-0712-5be127ae6775.png "スクリーンショット 2019-12-26 16.43.02.png")


## valueの取得

まずはvalue。普通にe.target.valueで取れる。

```diff
import React from 'react';

const items = [
  { id: 1, name: "hoge", address: "tokyo", age: 11 },
  { id: 2, name: "foo", address: "osaka", age: 22 },
  { id: 3, name: "boo", address: "fukuoka", age: 33 },
]

function App() {
  return (
    <div style={{ margin: 40 }}>
      <select
        name="username"
+        onChange={(e) => alert(e.target.value)}
      >
        {
          items.map(item => (
            <option value={item.id} key={item.id}>{item.name}</option>
          ))
        }
      </select>
    </div>
  );
}

export default App;
```

## 要素名

ここでいうusernameを取得する。あまり単体では利用しないが、

```
func = (e) => {
	this.setState({[e.target.name]:e.target.value);
}
```

という感じで利用することが多い。
要素名はnameで取得できる。

```diff
import React from 'react';

const items = [
  { id: 1, name: "hoge", address: "tokyo", age: 11 },
  { id: 2, name: "foo", address: "osaka", age: 22 },
  { id: 3, name: "boo", address: "fukuoka", age: 33 },
]

function App() {
  return (
    <div style={{ margin: 40 }}>
      <select
        name="username"
+        onChange={(e) => alert(e.target.name)}
      >
        {
          items.map(item => (
            <option value={item.id} key={item.id}>{item.name}</option>
          ))
        }
      </select>
    </div>
  );
}

export default App;
```

## Text（表示項目）

これが意外とめんどくさい。いくつか方法があるようですが、ワンライナーで書ける下記の形式を一旦採用。

```diff
import React from 'react';

const items = [
  { id: 1, name: "hoge", address: "tokyo", age: 11 },
  { id: 2, name: "foo", address: "osaka", age: 22 },
  { id: 3, name: "boo", address: "fukuoka", age: 33 },
]

function App() {
  return (
    <div style={{ margin: 40 }}>
      <select
        name="username"
+        onChange={(e) => alert(e.target.selectedOptions[0].text)}
      >
        {
          items.map(item => (
            <option value={item.id} key={item.id}>{item.name}</option>
          ))
        }
      </select>
    </div>
  );
}

export default App;
```

## data属性

最後にデータ属性。これもめんどいがいつか役に立つはず。

```diff
import React from 'react';

const items = [
  { id: 1, name: "hoge", address: "tokyo", age: 11 },
  { id: 2, name: "foo", address: "osaka", age: 22 },
  { id: 3, name: "boo", address: "fukuoka", age: 33 },
]

function App() {
  return (
    <div style={{ margin: 40 }}>
      <select
        name="username"
+        onChange={(e) => alert(e.target[e.target.selectedIndex].getAttribute('data-address'))}
      >
        {
          items.map(item => (
            <option value={item.id} key={item.id} data-address={item.address}>{item.name}</option>
          ))
        }
      </select>
    </div>
  );
}

export default App;
```

ひとまず以上です。
【Laravel】ビューコンポーザーの使い方

#ビューコンポーザーとは
ビューをレンダリングする際に自動的に実行される処理を用意するための部品です。
ビューがレンダーされるたびに結合したい情報があるなら、ビューコンポーザがロジックを一箇所にまとめるのに役立ちます。
###サービスプロバイダについて
ビューコンポーザーを利用するためにサービスプロバイダを使う必要があります。
**サービスプロバイダの使い方については[こちら](https://qiita.com/hitochan/items/91ecbd74e6567b07d460)から**
#実装する機能
**今回はビューコンポーザーで設定した文字列をビューファイルで表示します。**

#どうやって実装するのか
登録済みのサービスプロバイダから指定したビューをレンダリングする時にビューコンポーザーを呼び出します。
##サービスプロバイダの作成
ターミナルからサービスプロバイダを作成します。

```:ターミナル
php artisan make:provider MessageServiceProvider
```

`MessageServiceProvider`を作成します。

##サービスプロバイダの登録
上記で作成した`MessageServiceProvider`をアプリケーションに登録しましょう

```php:config/app.php
'providers' => [
    // 他のサービスプロバイダ

    App\Providers\MessageServiceProvider::class,
],
```

`config/app.php`の`providers`配列に上記を追記してください

##サービスプロバイダの編集
上記で作成したサービスプロバイダを編集しましょう

```php:app/Providers/MessageServiceProvider
<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class MessageServiceProvider extends ServiceProvider
{
    public function boot()
    {
        View::composer(
            'welcome', 'App\Http\Composers\MessageComposer'
        );
    }

    public function register()
    {
        //
    }
}
```

######`View::composer('welcome', 'App\Http\Composers\MessageComposer');`
ビューコンポーザーの設定です。
`welcome`をレンダリングした時に`App\Http\Composers\MessageComposer`が呼び出されます

##ビューコンポーザーの編集
`app/Http/Composers`フォルダを作ってください

```php:app/Http/Composers/MessageComposer
<?php

namespace App\Http\Composers;

use Illuminate\View\View;

class MessageComposer
{
    public function compose(View $view)
    {
        $view->with('message', 'View Composer message!!');
    }
}
```

######`$view->with('message', 'View Composer message!!');`
`message`に`'View Composer message!!'`を代入してレンダリングします

#ビューファイルの追記
`resources/views/welcome.blade.php`に下記を追記してください。

```resources/views/welcome.blade.php
{{ $message }}
```

welcomeにアクセスした時ににView Composer message!!が表示されれば設定完了です。

#参考記事
+ [【Laravel】ビューコンポーザって何？](https://qiita.com/harunbu/items/8743f3093705c8051c44)
+ [ビューコンポーザ](https://readouble.com/laravel/5.6/ja/views.html#view-composers)

**疑問、気になるところがございましたら、質問、コメントよろしくお願いします！！！**
flatpickrで期間が指定範囲より大きいときにアラートを出したい

カレンダー表示する際に便利なflatpickrで
アラートなどを出してあげたいときに以下の書き方をしました。
※jQueryを使っています

```js
/**
 * カレンダーテスト
 * @return なし
 */
// flatpickr実行 以下はクラス名
$('.inputDate').flatpickr({
  mode: 'range',
  locale: 'ja',
  maxDate: 'today',
  dateFormat: 'Y/m/d',
  defaultDate: ['2019/12/01', '2019/12/10'],
  allowInput: true,
  onClose: function(selectedDates, dateStr) {
    // 値が変更された時の処理
    if (!periodJudgment(selectedDates)) {
      alert('指定範囲が長すぎます！！');
    }
  },
});

function periodJudgment(selectedDates) {
  // 1年を日数で設定
  const oneYearDayCount = 365;
  // 選択期間が１年以上の日数の場合はfalseを返す
  let diffDate        = selectedDates[1] - selectedDates[0];
  const diffDateCount = Math.floor(diffDate / (1000 * 60 * 60 * 24));

  if (diffDateCount > oneYearDayCount) {
    return false;
  }
  return true;
}
```

HTMLは以下

```
<!-- flatpickrを使うために必要 --!>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/ja.js"></script>

<!-- 日にち期間選択用inputフォーム --!>
<input class="inputDate" type="text" value="" />
```
homebrewで移行先macへ簡単にアプリを移動させる

macをCatalinaにクリーンインストールするときなどに使うと便利です。

```
// 移行元でBrewfileを作っておく。githubなどにあげておく
brew bundle dump

// 移行先macでの作業。ここから以降は移行先macでの作業
// fileを作って書き込む
touch Brewfile

vim Brewfile
```

Brewfileにコピペする

```bash:Brewfile例
tap "homebrew/bundle"
tap "homebrew/cask"
tap "homebrew/core"
brew "git"
cask "cmd-eikana"
cask "deepl"
cask "docker"
cask "libreoffice"
cask "mapture"
cask "rubymine"
cask "sublime-text"
```

```bash:
// homebrew install
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

// インストール
brew bundle
```

参考サイト
[macOS用パッケージマネージャー — Homebrew](https://brew.sh/index_ja)

[GitHub \- Homebrew/homebrew\-bundle: 📦 Bundler for non\-Ruby dependencies from Homebrew, Homebrew Cask and the Mac App Store\.](https://github.com/Homebrew/homebrew-bundle)
Amazon Linux2でSelenium環境を最短で構築する

この記事は、[Amazon LinuxでSelenium環境を最短で構築する](https://qiita.com/onorioriori/items/4fa271daa3621e8f6fd9)のAmazon Linux2版です。
Amazon LinuxでのSelenium環境を構築したい方は上記リンクを参考にしてください。

# 概要
2020年6月30日に Amazon Linux のセキュリティ更新提供が終了するということで、
Amazon Linux2でもSelenium環境を構築出来るようにしました。
ほとんど手順は変わりませんが、一部変更点がありました。以下は全てrootで実施したものです。

# 手順
・**Google Chrome** のインストール
・**GConf2** のインストール ※変更あり
・**ChromeDriver** のインストール
・**Google Noto Fonts** のインストール
・**Selenium** のインストール ※変更あり

## Google Chromeのインストール
`yum install google-chrome-stable`でやると依存関係で死にます。
以下を実行しましょう。

```shell
curl https://intoli.com/install-google-chrome.sh | bash
```
最新のGoogle Chromeがうまく入るはずです。

## GConf2のインストール
Amazon Linuxの時はリポジトリを追加しないとインストール出来なかったのですが、
Amazon Linux2では簡単にインストール出来ます。

```
yum -y install GConf2
```

## ChromeDriverのインストール
現時点でインストールしたGoogle Chromeが`79.0.3945.88`なので、それに対応する`79.0.3945.36`を取得します。
Google Chromeのバージョンは`google-chrome-stable -version`で確認できます。
※ Google Chromeのバージョンと対応するChromeDriverのバージョンの確認は[こちら](https://sites.google.com/a/chromium.org/chromedriver/downloads)

```shell
wget https://chromedriver.storage.googleapis.com/79.0.3945.36/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
```

## Google Noto Fontsのインストール
このままだとSeleniumで画面のスクリーンショットを撮った際に
日本語が文字化けしてしまうのでフォントをインストールします。
https://www.google.com/get/noto/

```shell
cd ~/Downloads   # どこか適当な場所で
wget https://noto-website-2.storage.googleapis.com/pkgs/Noto-hinted.zip
unzip Noto-hinted.zip
mkdir -p /usr/share/fonts/opentype/noto
cp *otf *ttf /usr/share/fonts/opentype/noto
fc-cache -f -v # optional
```

## Seleniumのインストール
ここがAmazon Linux2になって面倒になったところです。
Amazon Linux2では、pipがデフォルトで入っていないため、まずはpipをインストールしましょう。
Amazon Linux2のデフォルトのpythonのバージョンが`2.7.16`なので、本当は3系に上げてからpipをインストールしたいですが、
yumが動かなくなったりする懸念があったため、そのままインストールしています。
※yum周り含めて、安全にpython3系に上げる方法をご存知の方は是非ご教授を…

```shell
yum install -y python-pip
```
その後、seleniumをインストールすることが出来ます。

```shell
pip install selenium
```

インストールするものは以上になります。

# Seleniumを使ってみよう
googleのトップページのスクリーンショットを撮る簡単なテストコードを作成して
実行してみましょう。

```python:test.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--window-size=1280,1024')
driver = webdriver.Chrome(chrome_options=options)
driver.get('https://www.google.co.jp/')
driver.save_screenshot('test.png')
```
実行ディレクトリにtest.pngが作成されます。

ちなみにoptionsに追加している引数ですが、
・--headlessでヘッドレスモード
　(これで実行しないと動きません)
・--no-sandboxはgoogle-chrome-stableが動くために必要
　(`google-chrome-stable`を実行すると分かるはず)
・--disable-gpuで描画周りが安定するらしい
　(これ無しで実行したら作られたpngファイルが真っ黒だった)
・--window-sizeはそのままウィンドウサイズの指定
となります。

# 最後に
作成されたtest.pngは以下のようになっていました。文字化けしていませんね。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169832/0ec0e471-670b-ea0a-9a01-4d8922b26bfd.png)
Amazon Linux2への移行で大きく変更があるかと思いましたが、そこまで影響はなさそうです。
UISliderをタップで選択可能にする (iOS12以上の場合)

### UISliderをタップで選択可能にするには

```swift:iOS12より前
class MySlider: UISlider {
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        return true
    }
}
```

## しかしiOS12以降だとSliderの値が変わらない

```swift:iOS12以降
class MySlider: UISlider {
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let tapPoint = touch.location(in: self)
        let fraction = Float(tapPoint.x / bounds.width)
        let newValue = (maximumValue - minimumValue) * fraction + minimumValue
        if newValue != value {
            value = newValue
        }
        return true
    }
}
```

これでiOS12以降でもタップで選択可能になり、`.valueChanged`イベントも送ってくれます

## 参考

- [UISliderのUXをトコトン追究して改善してみる | Qiita][ref-1]
- [UISlider - beginTracking behaviour changed on iOS 12 | Apple Developer Forums][ref-2]
- [beginTracking(_:with:) - UIControl | Apple Developer Documentation][ref-3]

[ref-1]:https://qiita.com/ruwatana/items/01a9c43057f71560580f
[ref-2]:https://forums.developer.apple.com/thread/108317
[ref-3]:https://developer.apple.com/documentation/uikit/uicontrol/1618227-begintracking
雑記(忘備録など)

#技術的なあれこれ
##ファイルを指定した容量で作成するコマンド
```cmd:コマンド
fsutil file createnew <FileName> ＜Size(Bite)＞
```
ファイルを作成し、ネットワーク、ディスク負荷等を確認することに利用できる。


---


# 不具合対処方法

##Firefox

###1. Firefoxが勝手に起動、終了を繰り返す事象

####対処法
extensionsディレクトリを
以下の配下[C:\Program Files\Mozilla Firefox\browser\]
に作成し、assetview@hammock.co.jp.xpiファイルが生成されていることを確認する。

またコマンド上では以下のようになる。

```
md C:\Program Files\Mozilla Firefox\browser\extensions
```

####原因
AssetViewのアドオン機能が対象ディレクトリを作成できず、失敗を繰り返すため、起動と終了を繰り返すこととなる。その為、対象ディレクトリを作成し、assetview@hammock.co.jp.xpiファイルが作成されれば事象を解決させることができる。

###2.FireFoxをUpdate後、起動すると、FireFox内の画面が白くなったままハングアウトする

####対処法
1. Firefoxを終了させる
2. c:\users\[対象ユーザ]\AppData\Roaming\Mozilla\Firefox\Profiles\[プロファイルID]　配下の places.sqlite ファイル(places.sqlite-journal このファイルも存在する場合はこれも含む。)に末尾.oldを付ける。
3. Firefoxを起動する。

####関連資料
[【Mozilla Support】Firefox がハングアップまたは応答なしになる](https://support.mozilla.org/ja/kb/firefox-hangs-or-not-respondin)

#NUROの一般ONUにまつわる話
https://qiita.com/hellfire/items/e10bf16bfffdf21d30f6
実務3ヶ月で得たコーディング以外の技術

はじめまして、こんにちは。

名前はsohと申します。
年齢は23歳、
現在東南アジア発のスタートアップスタジオ「GAOGAO」に所属し、主にサーバーサイドを担当させて頂いています。
このQiitaは[GAOGAO Advent Calendar 2019](https://qiita.com/advent-calendar/2019/gaogao)の10日目の記事です。

大学卒業後、[GAOGAOゲート](https://gate.gaogao.asia/)にてベトナムのホーチミンでプログラミングを学び、9月からタイのバンコクにて仕事をしています。
よって12月現在、実務経験は3ヶ月となっております。

**そんな僕が3ヶ月で得た経験を書いていこうと思います。**

「こんなこと当たり前だろ...」
と思う方がいるとは思いますが、温かい目で最後まで見守ってください。

#目次
1. ドキドキワクワクの初案件
2. 0からのスタート
3. この経験から得たもの
4. まとめ


#ドキドキワクワクの初案件

9月の上旬にタイに引っ越し、すぐに案件に入らせてもらいました。

この案件は他のメンバーがすでに進めていて僕は途中から参加する形でした。

初めての実務ということで戸惑いながらも自分の振られたタスクを必死にこなしていく毎日。

やっとの思いで完成させたコードをレビューしてもらうと、驚きの言葉が。

**「これ悪いけど、丸々書き直しだね〜。」**

まじ、、、？
僕は耳を疑った。

確かにコードは見直すと汚い。しかし本質はそこではなかった。

機能要件を満たしていない。

普通に考えて当たり前だ。

####仕様の理解が足りないままコーディングに入ってしまった。

いきなりやってしまったが、落ち込んでいる暇は無くすぐに遅れを取り戻さないといけない状態になりとても焦ってしまった。

そう思い取り組んでは見るものの次の問題が。

####圧倒的にコーディングのスキルが足りない。

こればっかりはもう自主的に勉強してスキルをつけるしかないが、結局最後まで自分のタスクを終わらせる事ができずに同じチームのメンバーに負担をかけてしまった。

個人的には勉強しているつもりであったが全くできなかった。
チームのメンバーには申し訳ない気持ちがいっぱいで悔しくてたまらなかった。

そんなこんなで僕の苦い初案件が終わった。


#次の案件は0からのスタート
次の案件は業務基幹システムを作るというものだった。

実際にプロジェクトの始めから終わりまでの流れを経験したことがない僕はすべてが初めてで
学ぶことだらけであった。

要件定義、お客様へのヒアリング、画面遷移図の作成、ER図作成などいわゆる上流工程？と呼ばれるコーディング以外の部分は時間をかけておこなった。

正直な感想は、、、
###コーディングまでの道のり長くね？

でした。

業務時間以外でもやることが山積みで楽しいかと言われるとあまり楽しくはなかった時期でした。

しかし、この時点で細かく相手の要望を聞くことが後々の認識のズレを減らし、
結果計画どおりにプロジェクトを進めることに繋がるので、前回と同じ失敗をしないように丁寧に行いました。

これからコーディングに入っていくのですがこれを丁寧にやることにより自分のすべき正しい作業が明確になった気がします。




他にも書ききれないぐらいたくさんありますが、、、

#この経験から得たもの
業務を進めていく上で、、、

###1. 何よりもまず仕様の理解を第一に。

せっかく時間をかけて作ったものでも、相手の求めている物でなければ納品できない。


これから作るものを自分がイメージできていることが前提としてあり、
それが果たして相手の欲しい物なのかを確認しなければならない。

当たり前のように聞こえるが実際にやるとできていない人が多いのではないだろうか。
少しの認識のズレが大幅な修正の時間を作ってしまう。

ならば最初から多めに時間を取ってでも丁寧に作り込む方が長期的に見てスムーズに進むことがわかった。

もちろんスピーディーにできるに越したことは無いがそこは経験を積んでレベルアップしていきたいと考えている。


###2.事前に足りないスキルを明確化する

今の時点ではできることが少ないが、そんなことは関係なく振ってもらったタスクは責任を持ってやらなければならない。

しかし丁寧にやるべきことを決めておくことで、
今の時点で自分は何ができて、何ができないのかを把握して事前に学習ができるのだ。

こうやって一つ一つできることを増やして行くのがスキルアップに繋がる。


###3.報告、連絡、相談は細かく、そしてわかりやすく。
今自分は半リモートのような働き方をしているので、チームのメンバーと顔を合わせない日も多々あります。
そこでPMの負担を減らすために自分が行うようになったこと。

- 今どんな作業をしているか
- 進捗はどうなっているのか
- 遅れている場合→改善策

最低でもこの３つは意識しています。

この改善策というのがキモで「遅れています。」という報告は誰でもできる。

しかし本当に大事なのは
最終的にどのくらいの時間で終わるのか？その後取り組む予定だったタスクはどうするのか？を明示すること。
これが大事です。
報告を怠ったせいで、他のメンバーの作業が止まってしまう可能性だってあります。

スケジュールを引き直すのにも時間がかかるし、はっきり言って手間ですがそういう所をめんどくさがらずにやりましょう。

この報告、連絡、相談はプロジェクトを進めるメンバーにだけではありません。

お客様への報告が少ないと不信感を持たれてしまいます。
フリーランスだから、リモートだからこそ相手の自分へ印象は人一倍敏感でなくてはなりません。

なのでどんなに些細なことでもわからないことがあれば質問、
特別想定外なことがあっても、作業が順調に進んでいても細かく報告、
というのはこれからも意識して仕事をしていきます。



#まとめ
自分と同じような経験、スキルの方は

- まずはプロジェクト全体のイメージを掴む
- いつでも相手のことを考えて細かく報告
- 何ができないのかを把握し、足りない部分の勉強を空いている時間にする。

これさえやっていれば大きなミスはしないと思うし、仮にミスをしてしまってもすぐに取り戻せるレベルだと思います。

**「先のことを常に考えつつ、目の前の作業に精一杯取り組む。」**

これを心に留めながら日々精進していきます。

以上が「実務3ヶ月で得たコーディング以外の技術」でした。


#終わりに
実際にエンジニアとして働いてみると想像していた業務とは少し違う部分も出てきて、学びの多い日々を過ごすと思います。
結局どの仕事も根本的な所は変わらず、信用、信頼を重ねることが大事だと感じました。

この記事が同じような境遇の方のプラスになればいいなと思います。

最後まで読んでいただきありがとうございました。
次回は技術的なことを書きます。
Ubuntu 18.04でVNCサーバを立てる

#VNCのインストールと設定の手順
ターミナルだけ開いた状態になる。サーバ機で直接ログインしている状態だとうまくいかない。
あと、gnome-flashbackを使わないとVNCでアクセスすると白黒？灰色？しましま？の画面になってしまうので注意。

```bash
$ sudo apt-get install vnc4server
$ vncpasswd
パスワードを入れる
$ vncserver :1 
ポート5901の意味
~/.vnc/xstartupが生成される
$ vi .vnc/xstartup
下記のように編集する
#!/bin/sh

# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc
export XKL_XMODMAP_DISABLE=1
export XDG_CURRENT_DESKTOP="GNOME-Flashback:GNOME"
export XDG_MENU_PREFIX="gnome-flashback-"

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &
#x-terminal-emulator -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
#x-window-manager &
gnome-terminal &

$ vncserver -kill :1
$ vncserver :1 
VNCサーバを再立ち上げ
$ vi ~/.bashrc
下記を追加して、起動時にvncserverが立ち上がるようにする
vncserver :1
```

#おまけ
ノートPCを閉じてもサスペンドしないために

```bash
$ sudo vi /etc/system/login.conf
下記を追記する
HandleLidSwitch=ignore
$ sudo reboot
```
Basic認証とDigest認証

# Basic認証
- Webサイトの閲覧に使うプロトコル「HTTP」が備える、簡易的なユーザ認証方式。
- アクセスの制限されたWebページにアクセスしようとすると、Webブラウザでユーザ名とパスワードの入力を求める。
- Basic認証はファイルやディレクトリ単位でしか制御できない。
- Base64でエンコードして送信する。
- 平文でのやり取りなのでセキュアでない。SSLにすれば別。内部システムの認証によく使われる。

>HTTP/1.0", includes the specification for a Basic Access
Authentication scheme. This scheme is not considered to be a secure
method of user authentication (unless used in conjunction with some
external secure system such as SSL [5]), as the user name and
password are passed over the network as cleartext.

https://tools.ietf.org/html/rfc2617

## 実装方法
###.htaccessを作成

```
AuthUserFile /app/.htpasswd
AuthType Basic
AuthName "Restricted Access"
Require valid-user

//IPアドレスによる制限もかける場合は以下を記載
Satisfy Any

Order Allow,Deny
Allow from 192.168.36.0/24
```
- AuthType : ユーザー認証の方法を指定します。
- AuthName : ダイアログに表示される文字列を指定します。
- Require valid-user : 入力したIDとパスワードが合っていれば誰でもアクセスできるという意味。
- Satisfy Any：IPアドレス制限とBASIC認証のどちらか一方を満たせばアクセスが許可される。
- Satisfy All：IPアドレス制限とBASIC認証の両方を満たす場合にアクセスが許可される。

###.htpasswdを作成
- ID:username,PASS:hogeとしたいとすると、username:Mtyb8PH67BU9Yというコードを書く。
- パスワードは以下サイト等を使って作成する。
- https://www.luft.co.jp/cgi/htpasswd.php

###サーバー再起動
```
sudo service httpd restart
```


# Digest認証
- クライアントでユーザー名, パスワード, ランダムな文字列をMD5でハッシュ化し, サーバーに送信する. サーバー側でもハッシュ値を計算し, クライアントから送信されたハッシュ値と合致するかを検証する。
- ハッシュ値を送信するため, パスワードを平文で送らないという点でBasic認証に比べて盗聴に耐性がある。
- MD5なので強度はそこまで強くない。

## 実装方法
### パスワードファイルの作成

```
htdigest -c passwdfile realm username
```
 - -c : パスワードファイルを作成する。

 - passwordfile : 任意のパスワードファイル名

 - realm : httpd.conf内の「Auth Name」と同様のものを指定する。
 
 - username : ユーザー名

### パスワードの設定

```
Adding password for user in realm Digest Auth.
New password: 
```

### httpd.confに追記
```
<Directory  "/var/www/html/">
    AuthType Digest
    AuthName "XXX Zone"
    AuthUserFile "/path/to/documentroot/.htdigest"
    Require valid-user
</Directory>
```

 - AuthName: 領域名を定義する。領域名が同じならば、画面が変わっても認証されたことになり認証画面は表示されない。
 - AuthName : httpd.conf内の「Auth Name」と同様のものを指定する。
 - AuthUserFile: パスワードファイルの場所をフルパスで指定する。
 - Require valid-user: 入力したIDとパスワードが合っていれば誰でもアクセスできるという意味。

###サーバー再起動
```
sudo service httpd restart
```
メモ： Railsで全テーブルと全カラムのテーブル定義書風のTSVを出力

この記事は
===

* 完全なる個人メモです
* RailsのDBからテーブル定義書ちっくなtsvを出力するスニペットを書いたので貼っておきます

スニペット
===

```lib/tasks/export_table_tsv.rake
namespace :export_table_tsv do
  task run: :environment do
    ActiveRecord::Base.connection.tables.each do |table|
      table_ja = I18n.t("activerecord.models.#{table.singularize}")
      eval(table.singularize.camelize + ".column_names").each do |column|
        column_ja = I18n.t("activerecord.attributes.#{table.singularize}.#{column}") 
        puts "#{table}\t#{table_ja}\t#{column}\t#{column_ja}"
      end rescue nil
    end
  end
end
```

出力イメージ
===

* これをExcelにはって活用します（雑）

```
users	ユーザー	id	translation missing: ja.activerecord.attributes.user.id
users	ユーザー	email	メールアドレス
users	ユーザー	first_name	名
users	ユーザー	last_name	姓
```

終わりに
===

* one offスクリプトなのでtranslation missingは放置してしまったけど許してください
Markdownエディタの"SimpleMDE"の代替、"EasyMDE"を使ってみる

今回、新たにサービス（[Chroco](https://chroco.ooo/)）の開発をしていて、Markdownエディタの"[SimpleMDE](https://github.com/sparksuite/simplemde-markdown-editor)"を利用しようと設定してみたのですが、CSSフレームワークの"Bulma"と競合を起こして、利用できない。。。
"Font Awesome"のバージョンでおかしくなっている。。。

調べていると、あれ？2016年から更新されていない、、、
![FireShot Capture 015 - GitHub - sparksuite_simplemde-markdown-editor_ A simple, beautiful, a_ - github.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/ea24b0c3-790e-ec8e-a288-2a8078a666bc.png)

なるほど、もうバージョンアップされていないのですね。
だから、他のモジュール達との差分が発生して、うまく動かなくなっているのですね。。。

そこで、新しい代替えの"**[EasyMDE](https://github.com/Ionaru/easy-markdown-editor)**"を見つけました。

# [EasyMDE](https://github.com/Ionaru/easy-markdown-editor)とは
EasyMDEは以下にも記載がある通り、SimpleMDEをフォークして作られた後継機のMarkdownエディタみたいですね。現在でもちょくちょくバージョンアップは続いているみたいです。

> This repository is a fork of [SimpleMDE, made by Sparksuite](https://github.com/sparksuite/simplemde-markdown-editor/).
Go to the [dedicated section](#simplemde-fork) for more information.

## EasyMDEをインストールする

[npm](https://www.npmjs.com/package/easymde)を使う場合

```
npm install easymde --save
```

*UNPKG* CDNを使う場合

```html
<link rel="stylesheet" href="https://unpkg.com/easymde/dist/easymde.min.css">
<script src="https://unpkg.com/easymde/dist/easymde.min.js"></script>
```


## 使い方

### エディターの読み込み

モジュールをインストールまたはインポートした後、EasyMDEを最初のTextAreaにロードします。

```html
<textarea></textarea>
<script>
var easyMDE = new EasyMDE();
</script>
```

または、Javascriptを使用して特定のTextAreaを選択できます。

```html
<textarea id="my-text-area"></textarea>
<script>
var easyMDE = new EasyMDE({element: document.getElementById('my-text-area')});
</script>
```

jQueryの場合

```html
<textarea id="my-text-area"></textarea>
<script>
var easyMDE = new EasyMDE({element: $('#my-text-area')[0]});
</script>
```

# まとめ
今回は簡単な設定方法だけを紹介しましたが、"**[EasyMDE](https://github.com/Ionaru/easy-markdown-editor)**"は"SimpleMDE"の機能をそのまま使えます。（そりゃフォークしたモジュールですからね）
なので、最新の環境でMarkdownエディタを利用する場合はこちらを利用するほうがおススメかと思います。

最後になりますが、今回の"EasyMDE"を利用したサービスの[CHROCO](https://chroco.ooo/)はこちらになります。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ポートフォリオをストーリーで語るサービスを作ったよ。 <a href="https://twitter.com/blue_islands/status/1207533526469365761?ref_src=twsrc%5Etfw">2019年12月19日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
A2019で「デスクトップファイル」を参照できない時

Automation Anywhere Community Edition A2019 ビルド1598で、
昨日まで機嫌よく操作できていたのに、今日「デスクトップファイル」を参照しようとすると、よく分からないエラーメッセージ
「Device is disconnected 問題が解決しない場合は、システム管理者にお問い合わせください。」
が出た。

![3ローカルファイル操作.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417890/800c0085-15e7-dbf9-8026-1777f28f1351.png)

Chromeの拡張機能を削除してもダメ。

[デバイス]-[マイデバイス]を選択し、一旦デバイスを削除する事で元に戻った。（Chromeの拡張機能は再インストールした。）
どこかに何か困った情報が残っているみたい。
原因はよくわからないけれど、私の環境ではとりあえず動くようになった。

![2ローカルファイル操作.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417890/f4339438-d261-a794-d41c-842fdc0a8b29.png)
Mac用のチートシート表示アプリ

# 何ができるか

チートシートを表示することでショートカットキーが確認できる。

# 環境

macOS Catalina 10.15.2

# CheatSheatをインストール

https://www.cheatsheetapp.com/CheatSheet/

# 使い方

- アプリがアクティブになっている状態でコマンドキー(⌘)を長押し。
- おまけ
    - チートシートが表示されている状態で特定のショートカットキーをクリックすると実行される。

# 設定、印刷

チートシートが表示されている状態で右下の歯車アイコンをクリック。

![スクリーンショット 2019-12-26 15.57.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72156/4f9621fd-1267-f2f1-dfdb-c03405889ab7.png)

## 印刷

「Print」をクリック。

## 長押し時間の設定

「Delay」の欄を変更。※秒数指定不可

# 他

- メニューでアクティブになっているものを整形して表示している様子。  
  グレーアウトされている項目はチートシートに表示されないので注意。
- テキストとしてコピーしたい場合はPDF化すれば可能。

## 参考URL

https://qiita.com/MMMMMonchi/items/dd72e32fc8e69bcdf411
macOS CatalinaにAnacondaを入れてもVSCodeで動かない

#問題
VSCodeでAnacondaのpythonでなく、macOS標準のpythonが動いてしまいました。

#解決手段
.zshenvに"export PATH="を追加することで、VSCodeでもAnacondaのpythonを動かせるようになりました。

```zsh
% vi ~/.zshenv
>> export PATH=
```

#原因究明
##参照先を確認
まずは参照先が違うんだろうなーと思ったところ、

```zsh
% which python
terminal -> /Users/yuya/opt/anaconda3/bin/python
VSCode   -> /usr/bin/python
```

案の定、それぞれ異なるPATHのpythonを参照していました。なんで？

##PATHを確認
PATHが貼られていないのかと思ったので、見てみると、

```zsh:terminal
% echo $PATH
/Users/user/opt/anaconda3/bin:/Users/user/opt/anaconda3/condabin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```

```zsh:VScode
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/user/opt/anaconda3/bin:/Users/user/opt/anaconda3/condabin
```

PATHは貼られているが、優先順位が違う。なんで？

##なぜ優先順位が変わったか
いろいろググってみると、[この記事](http://what-alnk.hatenablog.com/entry/2017/08/07/230000)でVSCodeの統合ターミナルで再度設定を読み直していることがわかりました。
さらに、[この記事](https://qiita.com/t-takaai/items/8574ff312f2caa5177c2)の参考記事で優先順位が変わることを言及していました。

どうも/etc/zprofileで動作するpath_helperの影響のようです。
path_helperはデフォルトPATH(/etc/pathsと/etc/paths.d/*で規定)を重複なく、PATHを再設定しているようです。その際、PATHのデフォルトPATHと重複した箇所は削除し、PATHの頭にデフォルトPATHを追記するため、PATHの順番が変わったのだと思われます。

###メカニズム(というほどでもないけど)
まずzshの設定が一通り読み込まれる。ここでは想定したPATHとなっています。
PATH=/Users/user/opt/anaconda3/bin:/Users/user/opt/anaconda3/condabin:**/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin**

次にVSCodeの統合ターミナルでもう一度、zshの設定が読み込まれるが、path_helperの動作により、デフォルトPATHが頭に移動しています。
PATH=**/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin**:/Users/user/opt/anaconda3/bin:/Users/user/opt/anaconda3/condabin

#zshのログイン時の設定
ここでログイン時にどのような設定ファイルが、どのような順番で読み込まれているのかが気になってきます。この情報は[この記事](https://qiita.com/yuku_t/items/40bcc63bb8ad94f083f1)でわかりました。
/etc/zprofileより前の設定ファイル(.zshenv)でPATHを初期化してやれば

```zsh
% vi ~/.zshenv
export PATH=
```

何度読み直しても同じPATHになるはずです。

##同じPATHになりませんでした
anaconda3/condabinが設定されなくなってしまいました...
多分、condaコマンドになにかしら値が設定されると、anaconda3/condabinのPATHは読まれないような設定になっているのでしょう、多分。
でも、やりたいことはできてるので、とりあえずよしとします。
IT技術系　新人研修講師　座談会2019 での話（１）

クリスマス前　１２月２３日に開催して「IT技術系 新人研修講師 座談会2019」を振返り文字として残しておきたいと思います。

まずは、集まって頂いた方々１１名は、研修講師を経験したことがある方が９割り程度、残りはこれから研修講師を担当する方。自社の新人研修を担当する方やサービスとして他社の新人研修を請け負っている方と様々です。

受付後、新人研修を担当していて　又は　これから担当するにあたり　悩んでいる事や聞きたい事をお一人３個程度だして頂きホワイトボードに貼り出しました。

![P_20191223_185853_vHDR_Auto.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100559/24a1e587-ee8c-9280-8359-f3c12313381b.jpeg)

悩み系では

「研修で教える内容やレベル感」「教え方」
「新人のスキル差・二極化」
「講師側や講師の体制」

などがあがりました。

![P_20191223_185901_vHDR_Auto.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100559/5047d5c2-7691-bbe3-d89c-4d29aff75916.jpeg)

聞きたい系では

「セルフ・コントロール型研修実施してます？」
「テキストなに使ってます？作ってます？」
「研修講師になるためのスキルは？」
「効果測定どうしてますか？」

などがあがりました。

座談会では、１テーブル３、４名の方に座ってもらいました。その後、それぞれのテーブル毎にホワイトボードから話してみたいテーマを持っていってもらい、３０分程度話してもらいました。

３０分後、各テーブルで何が話されたのかを共有いたしました。

共有した内容はまた後ほど。
EC2からECSに移行した際の障害事例

# はじめに
[本番環境でやらかしちゃった人 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yarakashi-production
)  が面白かったので書いてみた

# なにが起きたか
EC2からECSに移行した際、ローカルに出力していたファイルが消滅した
~~一言で書くと初歩的すぎる。~~

# 対応
ファイル出力バッチ、定期実行するように機能を追加した。

# 時系列
1. 前任者が主担当となるシステムを、プロトタイピング方式で開発し、EC2上に構築した。  
→この際の運用作業で、手動でファイルをローカルに出力していた。
2. 自分が着任。
3. 前任者が部署移動のため引継ぎを行う。  
→ 引継ぎ作業を予定していたところ、クリティカルな不具合が発覚し、修正を優先度をあげて対応した。結果、十分なヒアリング期間を設けることができなかった。
4. 自分がEC2からECSに移行するプロジェクトを担当。  
→ 別プロジェクトで成功経験より、引き継いだプロジェクトにも適用する。
5. リリース後に問題がなかったため終了報告。一般ユーザの利用にも影響がなかった。  
しかし、月次定期運用の管理機能でエラーがでるとの連絡があり、調査の結果判明。

# 何をすれば防げたか
## ファイルの出力先の意識
やることは出力先をローカルからS3に変更するだけ。
コンテナの特性上、ローカルに出力したファイルが消えることは明白なので、早いタイミングで実装しておくべきだった。
## 調査をしっかり
類似構成で移行できたからといって、慢心しない。
システム単位でファイル出力の有無などを確認する。
## アーキテクチャの特徴の把握
ECS + fargate 構成のため、EC2サーバ上の既存ファイル転送による復旧作業できなかった。
ECS + EC2構成なら被害が減ったかも。
## 運用作業の認識不足
ファイル再出力機能やバッチがなかった。
月次運用業務についてのヒアリングができていれば気が付けた可能性がある。
管理画面での月次運用のデグレテストができていれば防げた。
また、DDD的な取り組みができていれば防げたかも。
## 引継ぎしてほしい内容を分かりやすくつたえる。
「引継ぎしてください」というオープンクエスチョンだと、「ありません」という返答がかえっきてしまうことがある。経験が浅いと特に。
「運用手順や再実行手順など、自動化されていないような作業に焦点をあてて、引継ぎしてください」といっておくとわかりやすかったのかなとおもう。

# まとめ
コンテナを使うときは、出力するファイルを別途設定しようね！
アーキテクチャや運用作業をちゃんと理解しようね！
【MySQL】テーブルの値を含む正規表現/パターンをCONCATで動的に生成する

# やること
以下のクエリのコメントの通りのことを実現します。

```sql
SELECT *
FROM table_a ta
  INNER JOIN table_b tb
    ON ta.text_value REGEXP ??? -- ???にtable_bの値を使った正規表現を入れたい
 -- ON ta.text_value LIKE ??? -- ???にtable_bの値を使ったパターンを入れたい
WHERE ...
```

# やり方
`CONCAT`で文字列と値を結合することで、任意の正規表現/パターンを動的に生成し、条件に加えることができます。

以下の例は、`ta.text_value`をカンマ区切り・`"`無しで`id`を記録しているカラムとして、その中に`tb.id`が含まれるかどうかを見ています。
`(^|,)`は「`tb.id`の左は行頭または`,`」、`(,|$)`は「`tb.id`の右は`,`または行末」を表す正規表現です。

```sql
SELECT *
FROM table_a ta
  INNER JOIN table_b tb
    ON ta.text_value REGEXP CONCAT('(^|,)', tb.id, '(,|$)')
WHERE tb.id IN (:ids)
```

例では正規表現を使っていますが、`LIKE`でも同様の方法で動的生成が可能です。

# 参考にさせていただいた記事
- [【MySQL】カンマ区切りのIDのリストを、マスタテーブルで定義されている日本語名にフォーマットして出力する不思議なSQL \| バシャログ。](http://bashalog.c-brains.jp/15/09/29-100000.php)
思いつきでやったデータ分析で気づきを得られた話

突如始まった、[いえらぶ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ielove) 19日目の記事です。

発売日から愛用している[AirPods Proとマダツボミ](https://www.itmedia.co.jp/news/articles/1910/29/news060.html)をARでコラボさせるネタを考えてたらいつの間にか投稿日が過ぎ去っていました。

つい最近、思いつきで簡易的なデータ分析をしてみたらUIUXの改善につながる気づきを得られたので、それについて書いてみようと思います。

##いえらぶCLOUDとは
不動産会社をターゲットとしたクラウドサービス。物件管理はもちろん追客支援からホームページ運用、ポータルサイト掲載など、あらゆる不動産業務を効率化することができる。

##賃貸管理機能とは
いえらぶCLOUDの一機能で、各種台帳管理から帳票出力、入出金管理など賃貸管理業務をサポートするシステム。

##修繕/点検/クレーム対応
そんな賃貸管理機能には、日々の対応内容を記録・管理する機能があります。
管理会社にとってのステークホルダーは、家主や入居者、リフォーム業者など数多く、当然対応内容も様々です。
そこで、選択式の必須項目である「種別」について、どの項目が多く選ばれているのかを分析してみました。


| 種別|割合|
|:--|--:|
|クレーム|10.8%|
|修繕|25.0%|
|点検|1.5%|
|巡回|0.4%|
|その他|62.3%|

##結果から
新規登録時に**初期選択状態**として設定している項目は「クレーム」なのですが、実際に登録される割合としては**10%程度に留まる**事がわかりました。
つまり、ほとんどの場合、セレクトボックスをクリックし、値を変更するという手間を強いていることになります。 
開発当初からさほど気にかけてこなかったことですが、初期選択としている項目が実は**不適切（不親切）**であったことがわかりました。

また、「その他」が6割以上の割合で選択されており、「点検」や「巡回」は逆にほとんど選択されていません。
**必要であるべき項目が用意できていない**可能性が高いです。

##追加項目の検討

では一体、どんな項目が必要なのでしょうか。
実は、種別に「その他」を選択した場合、その概要を自由入力することができます。
そこで、この入力値データの一部をランダムにピックアップ、MeCabで単語単位に分割し、どんな単語が多く使われているのかを調べてみました。

参考：[python 日本語の単語分割と出現頻度をカウントする](https://ameblo.jp/javaee7/entry-12419180587.html)

```python:python
import csv
import MeCab
from collections import Counter

data = open('claim.csv', 'r')
tagger = MeCab.Tagger('-Ochasen')
counter = Counter()
for text in data:
    nodes = tagger.parseToNode(text)
    while nodes:
        if nodes.feature.split(',')[0] == '名詞':
            word = nodes.surface #.decode('utf-8')
            counter[word] += 1
        nodes = nodes.next

for word, cnt in counter.most_common():
    with open('result.csv', 'a', newline='') as f:
        writer = csv.writer(f)
        writer.writerow([word, cnt])
```

結果がこちら（上位のみ抜粋）

|単語|出現回数|
|:--|--:|
|交換|157|
|清掃|107|
|日常|75|
|管理|74|
|連絡|68|
|入居|62|
|解約|53|

最も多かったのは「交換」。
エアコンや浴室乾燥機から鍵、扉、雨戸、クロスに消化器などその対象は様々です。
「点検」は管理会社が定期的に行うものですが、家主や入居者からの連絡を受けて行う交換作業は別物としてタグ付けしたいという需要が見て取れます。

20行にも満たないコードですが、追加すべき項目の候補が容易に浮かび上がりました。

##最後に
- 開発側の思い込みや浅はかな予想が、思いがけずユーザビリティを低下させていることもあるので、こういった実データを使った分析は積極的に行っていくべし
- PDCAを迅速・フレキシブルに回すことができるのは完全内製サービスの強み
macOS CatalinaにAnacondaをインストールする

#Anacondaのインストール
Anacondaをインストールするには、[ここ](https://www.anaconda.com/distribution/)からダウンロードして実行すれば良いです。

#PATHの設定
Anacondaのインストール時にbash向けの設定ファイルを自動生成しますが、
macOS Catalinaからデフォルトシェルがbashからzshに変更になったので、

```zsh
% mv ~/.bash_profile ~/.zprofile
```

と、zshの設定ファイル(.zprofile)を生成する必要があります。
コピーでOKです。
NervesでShellに落ちた

NervesにsshするとElixirのiexが起動している状態になっています。
いや、本来Linuxで動きゆきシェル使いたいやんって思ったので調べてみました。


# やり方
`mix.exs`に以下の設定を追記します。

```elixir:mix.exs
 defp deps do
    [
      （略）
      # 追加
      {:nerves_network, "~> 0.5", targets: @all_targets},
      {:nerves_firmware_ssh, "~> 0.3"}, targets: @all_targets},
      {:nerves_runtime_shell, "~> 0.1.0", targets: @all_targets}　←今回追記した行
    ]
  end
```

この状態でファームウェアを更新します。

```shell:bash
$ mix deps.get
$ mix firmaware
$ ./upload.sh NervesのIPアドレス
```

Nervesにログインして確認します。
Ctrl+Gする（コントロールキーを押しながら`g`を押す）と`iex`に戻ることができます。

```shell:iex
$ ssh NervesのIPアドレス

iex> Nerves.Runtime.Shell.start
#PID<0.1069.0>
Nerves Interactive Command Shell

Type Ctrl+G to exit the shell and return to Erlang job control.
This is not a normal shell, so try not to type Ctrl+C.

/srv/erlang[1]>　※Ctrl+Gを押す

iex>
```

なんだかうまく抜けられない時は、いろいろ試しているなかで成功率が高かったので以下を試してみてください。

```shell:iex
/srv/erlang[1]>　※Ctrl+Gを押す→でも何もおこならい

/srv/erlang[2]> exit
Interactive shell port exited with status 0
/srv/erlang[3]>　※Ctrl+Gを押す

iex>
```

#まとめ
Shellに落ちることができました＼(^o^)／
でも、フツーのLinuxなどで利用できるコマンドはほとんどなく何ができて何ができないのかも分かりません。
【Django】 POSTでファイルを送信して返ってくるcontextを確認するテスト 【TDD】

# Formのあるページのテストがしたいんだ！
[前のほうの記事](https://qiita.com/NomuK3/items/245b2070cd290d911294)でも言ってるんですが、Formのあるページのテストを作りたいです。
でもなぜだかエラーが出て、テストできませんでした。
とりあえずFormのテストをすればいいやと思って落ち着いたのですが、よく考えたらそのあとの処理のテストができないので、何の解決にもなってなかったです。
やっぱりPOSTしてcontextを確認しないといけません。
# 解決方法と、何で詰まってたのか。
解決方法は以下の通りです。
openで開いてファイルハンドラを入れ込んで、POSTしてあげれば良いというだけでした。

```python:tests.py
from django.test import TestCase, Client
from django.contrib.auth.models import User

class Test_Model_Create(TestCase):
    def setUp(self):
        # テストユーザー作成
        self.user = User.objects.create_user(username='tdd', email='test_account@aaa.bbb', password='test_pass') 
        self.client=Client()
        # ログイン
        self.client.login(username='tdd', password="test_pass")  

    def test_step1(self):      
        with open('./path/test.csv') as f:  
            data = {
                "nametest" : "test",
                "file_data" : f
            }
            response = self.client.post('/model_create/form/', data)
        self.assertIn('file_data', response.context, "contextにfile_dataが含まれていません")
```
こんな感じでオッケーです。

詰まってた点は、
    
- ファイルをクローズした後にPOSTをしてた。
- ファイルハンドラじゃなくてファイルを渡してた(`File(open('./path/excel_test.csv'))`)

こんな理由でした。ほかにもClientじゃなくてRequestFactoryを使ったりしてみてましたが、こんな理由だったとは…

# 複数ファイルのアップロードテスト
ちなみに今回2つファイルをあげなくちゃいけないのですが、こういう時は下のように書けるみたいです。

```python:tests.py
        with open('./path/testA.csv') as fa,open('./path/testB.csv') as fb:    
            data = {
                "name" : "test",
                "file_dataA" : fa,
                "file_dataB" : fb
            }
            response = self.client.post('/model_create/form/', data)
```
with文ってめっちゃ簡単ですね。すごいっす。

# まとめ
なんでこんなに悩んでたのかほんと不思議です。
ポカミスは減らしていきたいです。
AndroidでSQLCipherを利用する際に、ERROR: couldn't find "libsqlcipher.so" の対処法

[SQLCipher for Android](https://www.zetetic.net/sqlcipher/open-source/)を利用した時に`couldn't find "libsqlcipher.so" `というエラーが発生してしまいました、対処手順としては：

1.この[ページ](https://search.maven.org/artifact/net.zetetic/android-database-sqlcipher/4.2.0/aar)からaarファイルをダウンロードする：
![スクリーンショット (6)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269934/85e26310-901f-e6e5-818f-918379b735a6.jpeg)

2.aarファイルを解凍する。

3.soファイルをandroid端末にpushする:

```shell
push \sqlcipher\android-database-sqlcipher-4.2.0\jni\arm64-v8a\libsqlcipher.so /system/lib64/

push \sqlcipher\android-database-sqlcipher-4.2.0\jni\armeabi-v7a\libsqlcipher.so /system/lib/
```
gitメモ

#commitメッセージを変更
##１つ前にもどしたい
####git commit --amend コマンド
```
git commit --amend -m "メッセージ"
```
ちなみに`-m`は`--message`の略です！
**コメントじゃああない！(怒り)**
あと`-a`は`--all`の略ね。覚えとけよ!（戒め)

##２つ以上前にもどしたい
####戻りたいcommitまで戻る
```
git rebase -i HEAD~<戻りたいコミットまでの数>
```

####エディタが起動するからpickをeditへ変更
変更したいcommitコメントの行にある`pick→edit`に変更して保存

####commitメッセージ変更
```
git commit --amend -m "メッセージ"
```

####rebaseを終了させる
終了させないとお母さん泣くよ！

```
git rebase --continue
```
これにてcommitメッセージの変更の巻おしまいです！めでたしめでたし！


#gitで起動するエディタをVimに変更
```
git config --global core.editor vim
```
Cognitoのログイン、認可エンドポイント役割、挙動の説明

## 概要

Cognitoのログイン、認可エンドポイント役割、挙動の説明。

* OAuth2.0、OpenIDConnectのフローに乗せる際に使用する。
* 単純に認証してトークン（ID、アクセス、更新）が欲しいだけの場合、CognitoのAPIを普通に叩いても（Amplify等を使って）取得できる。

## cognitoログインエンドポイントの説明

https://yyyyyyyyy.auth.ap-northeast-1.amazoncognito.com/login?client_id=xxxxxxxxxxxxxxxxxxxxxxxxxx&response_type=token&scope=openid&redirect_uri=https://www.google.com

* 認可エンドポイント+認証機能がログインエンドポイント。
* ログインという名前がついているが感覚としては、"認可する"ことを許可するための認証が正しい。
* ログイン後、set-cookie: cognito="XXXX" され、認証のやり取りはこのクッキーを使って行う。  
※１度認証済みになった後（認証情報用のクッキー有りの状態）、ログインエンドポイントへアクセスした場合、認証済みの画面が表示される。
* 認証後、指定したリダイレクトエンドポイント（redirect_uri）へ認可情報付きでリダイレクトされる。


## cognito認可エンドポイントの説明

https://yyyyyyyyy.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?client_id=xxxxxxxxxxxxxxxxxxxxxxxxxx&response_type=token&scope=openid&redirect_uri=https://www.google.com

* ログインエンドポイントで認証した結果設定される認証情報用のクッキー（set-cookie: cognito="XXXX"）が存在しない場合、ログインエンドポイントにリダイレクトされる。  
* ログインエンドポイントにて認証後（認証情報用のクッキー有りの状態）、cognito認可エンドポイントをコールすると指定したリダイレクトエンドポイント（redirect_uri）へ認可情報付きでリダイレクトされる。

## ユーザプール認証フローで使われているモノと何が違うのか

* ユーザプール認証フローはCognitoのAPIを叩いて実際に認証するための手順。  
※APIを叩く方法はAmplifyやAmazon Cognito ユーザープールSDK等（自分実装で叩いても勿論OK）
* ログイン、認可エンドポイントは、OAuth2.0、OpenIDConnectにCogtnitoを使う用。  
 * エンドポイントが内部処理として認証を行う際、ユーザー認証フローのどれかを使ってるのだと思われる
 * エンドポイントの内部処理なのでユーザからは実際に認証をどうやってるのか見えない

## 参考

* [公式＠認可エンドポイント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authorization-endpoint.html)
* [公式＠ログインエンドポイント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/login-endpoint.html)
* [公式＠ユーザープール認証フロー](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html)
使っていない会議予約を撲滅する

# はじめに
オプトテクノロジーズの伊藤です。
完全なるアドカレドリブンな投稿。そしてクリスマスを跨ぐというもはやアドカレですらありません。
さて、今回は[前回の投稿](https://qiita.com/yorishigeito/items/0aaeb2783038583e89e4)で試作したIoTデバイスを社内の全会議室に導入するにあたり、使っていない会議予約を撲滅する技術をご紹介します。

# 前提
弊社には会議室の利用ルールとして「ミーティング開始時間を5分経過した後に在室していなければキャンセル扱いで他の利用者が使って良い」があり、そのルールに則り、15分間隔で[この仕組み](https://qiita.com/yorishigeito/items/0aaeb2783038583e89e4)によって在室チェックし、不在判定ならGoogleカレンダーをこねくり回します。

# 準備
1.Google Calendar APIの設定
ここについては私が解説するよりも、Googleさんが用意された各言語に対応した[ガイド](https://developers.google.com/calendar/overview)をご参照いただく方が良さげなので割愛します。

2.ライブラリの準備（以降はPythonです）
これだけです。

```
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
```

# 実装
エラーハンドリングや通知系の実装を省いたコードを抜粋します。

```qcalendar.py
from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

def main():
    creds = None
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('credentials.json', ['https://www.googleapis.com/auth/calendar'])
            creds = flow.run_local_server()
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token, protocol=2)

    service = build('calendar', 'v3', credentials=creds)

    # カレンダー取得日時の範囲を指定
    now = datetime.datetime.today()
    fromDt = now - datetime.timedelta(minutes=5) - datetime.timedelta(hours=9)
    toDt = now - datetime.timedelta(hours=9)

    # カレンダー取得
    resourceId = カレンダーにリソース登録されているID
    events_result = service.events().list(calendarId=resourceId, timeMin=fromDt.isoformat() + 'Z', timeMax=toDt.isoformat() + 'Z', maxResults=1, singleEvents=True).execute()
    events = events_result.get('items', [])

    # カレンダー削除
    deleted_event = service.events().delete(calendarId=resourceId, eventId=events[0]['id']).execute()

if __name__ == '__main__':
    main()
```
1.で用意した `credentials.json` と同じディレクトリに配置し、

```
python qcalendar.py
```
で実行する（初回実行時のみブラウザが起動するのでカレンダー編集可能なアカウントで諸々の操作を許可する）。

実行した結果、問題なければ15分間隔で起動するようにCron登録して完成。

```
5,20,35,50 * * * * /usr/bin/python /hoge/qcalendar.py
```
# 感想
* 時刻の取り扱いには注意。全てUTCで扱えばいいのかもしれないが、そうでないと時差やサマータイムでズレが生じる。
* カレンダーの繰り返し予約全てを削除するのは試していない（singleEvents=True を取り除くとおかしな挙動がある）
Selenium, Firefox, GeckoDriver のバージョン組み合わせ

## Selenium/Firefox/GeckoDriver のバージョン互換性表
https://firefox-source-docs.mozilla.org/testing/geckodriver/Support.html

| geckodriver | Selenium | Firefox(min) | Firefox(max) |
|:-----------:|:--------:|:------------:|:------------:|
| 0.26.0 | ≥ 3.11 (3.14 Python) | 60 | n/a |
| 0.25.0 | ≥ 3.11 (3.14 Python) | 57 | n/a |
| 0.24.0 | ≥ 3.11 (3.14 Python) | 57 | n/a |
| 0.23.0 | ≥ 3.11 (3.14 Python) | 57 | n/a |
| 0.22.0 | ≥ 3.11 (3.14 Python) | 57 | n/a |
| 0.21.0 | ≥ 3.11 (3.14 Python) | 57 | n/a |
| 0.20.1 | ≥ 3.5                | 55 | 62  |
| 0.20.0 | ≥ 3.5                | 55 | 62  |
| 0.19.1 | ≥ 3.5                | 55 | 62  |
| 0.19.0 | ≥ 3.5                | 55 | 62  |
| 0.18.0 | ≥ 3.4                | 53 | 62  |
| 0.17.0 | ≥ 3.4                | 52 | 62  |
[Rails][file_field]画像のアップロードのボタンデザインを変更する

Railsのアプリを作っている際に画像アップロードボタンのデザインを変えたかったが、
クラス名をつけるだけでは上手くいかなかったのでメモ。

# 初めの状態

普通にコードを書いた場合はこのようなデザインになるかと思います

```ruby
        = p.file_field :image_file
```

![ 2019-12-26 11.40.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532695/18a39c9d-8585-e07b-a8e1-7642c5d75251.png)


もっとかっこいいデザインにしたいので、変更していきます。

## 変更するとき

file_field以外にlabelを追加します。

```ruby
= p.label :image_file, "画像をアップロード", class: "image_input_btn"
        = p.file_field :image_file, class: "image_form"
```
どちらにもクラス名を追記します。

そして、CSSで

```
　　　.image_input_btn{
        display: block;
        background-color: #228B22;
        width: 180px;
        height: 30px;
        margin-top: 10px;
        margin-left: 10px;
        border-radius: 5px;
        padding-top: 2px;
        color: white;
        text-decoration: none;
        text-align: center;
      }

      .image_form{
        display: none;
      }
```
file_fieldのクラス名は非表示にし、labelに装飾していきます。

このように書くと

![ 2019-12-26 15.13.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532695/04abf2b1-c6f7-3202-786a-3449b6a4c0fd.png)

このようなボタンが出来上がります。

少しかっこよくなりました！！
Jupyter Nootbookの活用

# Jupyterのファイルをgithubにアップロード
Jupyter notebookの.ipynbファイルを
GitHub上にアップロードすると、
自動的にグラフや式などがレンダリングされる。
[Jupyter NotebookをGitHub上で公開する](http://pppurple.hatenablog.com/entry/2016/06/11/194713)

# 数式を入力する
Jupyter notebookで数式を入力したい場合は、
Markdownモードで、２つの$$で前後を囲った間に、
Latex記法で数式を書くと、数式が表示されます。

```
$$ \sqrt{a+b} $$
```

しかし、上記の方法で複数行の数式などを書いた時は、
なぜかGitHub上で数式が上手く表示されないので、
Github上でちゃんと表示させたい場合は

```
\begin{equation*}
# math
\end{equation*}
```

と正しくtex記法で入力すると表示されます。


# 数式の計算をする
Pythonの数式演算ライブラリsympyを使用すれば、
Jupyter上で数式の計算も出来る。
数式をprintすると、自動で数式表示される。

## インストール

```
pip install sympy
```

# 画像をドロップ＆ドラッグで挿入
画像をセルへドラッグ＆ドラッグすると挿入される。

# 目次を見出し情報から自動生成する
Nbextensionsの機能を使うことで、
Markdownの見出し情報から自動で目次を生成することができる。

(1) nbextensionsの設定画面で "Table Contents(2)" にチェック
(2) 同じ設定画面で "Add a Table of Contents at the top of the notebook" にチェック

# レポートタイトル
JupyterのMarkdownモードのセルに以下のHTMLを入力。

``` *.html
<br />

<div style="text-align: center;">
<font size="7">Jupyter Report</font>
</div>
<br />
<div style="text-align: right;">
<font size="4">oyan 29</font>
</div>

<br />
```

# Jupyterでコードの処理時間を計測する
%timeit
の後にコードを記述すると、そのコードの計算時間を計測してくれます。

# Jupyterでモジュールや関数のdocstringを確認する
モジュールや関数の後ろに"?"をつけると、docstringを表示してくれる。

# Jupyter Notebookでプレゼンをする
[Jupyter Notebookでプレゼンをするとっても便利な方法](https://qiita.com/cvusk/items/d425751ba663dc8c6517)

```
pip install RISE
jupyter-nbextension install rise --py --sys-prefix
jupyter-nbextension enable rise --py --sys-prefix
```
CSSで文字などをクルッと回転させる方法

#CSSで文字などをクルッと回転させるやり方
ポートフォリオサイトを作成中、ホバー時に文字をクルッと回転させる動作を実装したので、備忘的に投稿させて頂きます。
※javascriptは使用しておらずcssのみで実装しています。
##実際方法
実装方法といっても、とてもシンプルです。
例として、回転させたい方向で３通り記載しております。
X軸起点　＝　鉄棒で前回りするイメージ
Y軸起点　＝　フィギュアスケートでのスピンのイメージ
Z軸起点　＝　側転するイメージ

```html:example.html
<ul>
  <a href="#" class="example-X">X軸起点</a>
  <a href="#" class="example-Y">Y軸起点</a>
  <a href="#" class="example-Z">Z軸起点</a>
</ul>
```

```css:example.css
ul a{
  text-decoration:none;
  display: inline-block;
  color: black;
  font-size: 30px;
}
.example-X {
  transition: 1s ;
  transform: rotateX( 0deg ) ;
}
.example-X:hover {
  transform: rotateX( 360deg ) ;
}
.example-Y {
  transition: 1s ;
  transform: rotateY( 0deg ) ;
}
.example-Y:hover {
  transform: rotateY( 360deg ) ;
}
.example-Z {
  transition: 1s ;
  transform: rotateZ( 0deg ) ;
}
.example-Z:hover {
  transform: rotateZ( 360deg ) ;
}

```
大事なのは、transform: rotateX( 360deg )　この記述です。
このように記述する事で、ホバー時に、360度=１回転します。

また、transition: 1s の値を大きくすればゆっくり回転し、小さくすれば速く回転しますので、
お好みで調整してください。

少しでも、皆様の参考になれば幸いです。
閲覧頂きありがとうございました！