OmniAICreator/Qiita-1.07M huggingface dataset:
puppeteerでSPAのページ表示速度を計測してみた


普段はテスト自動化、ちょっとだけ開発を行なっています。
自動化繋がり(?)で、手動で行なっているページ表示速度の取得を自動化をすることなりました。

# やりたいこと
・手動でやっているページ表示速度(ボタンクリック→画面が全て表示されるまで)を定期的に自動で取得したい
・対象はSPA(シングルページアプリケーション)
・リロードの時間も計測したい
・毎日数時間おきに計測して比較したい
・Puppeteer使いたい (Node.jsのライブラリでプログラムからAPIでChromeの操作ができる。詳しくは[こちら](https://pptr.dev/))

# 計測方法の検討

## Puppeteer + Navigation Timing API
Puppeteer + Navigation Timing API で簡単にできそうと思ったけど、SPAだと計測できなかった(計測できたのは初期表示時だけ。。)

## Puppeteer + speedline
手動で計測する場合は、Chrome DevToolsのPerformanceパネルでのパフォーマンス計測を行なっているという情報を得たため、同じようにできないか調査。(左上のRecordボタンで計測できき、左下のような結果が得られる)
![スクリーンショット1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399598/2149db23-e31d-2abe-343a-b0fd33f6f1ca.png)

Puppeteerを使用することで、Chrome DevToolsのPerformanceパネルでのパフォーマンス計測でexportできるprofileが取れることがわかったため、これを利用することとした

ただし、profileの解析は別でやる必要があるため、別ライブラリ(speedline)を使用することとした

ボタンのクリック後、ページが表示されたタイミングが取れなかったため、
対象画面が表示される直前までマスクがかかっていることを利用し、マスクが外れたタイミング=表示されたとした

# 具体的な計測方法
　1. Puppeteerで遷移元のページを表示する
　2. recordを開始
　3. ボタンのクリックを行う
　4. マスクが表示されるまで待つ(ボタンクリック後マスクがかかるまでタイムラグがあるため)
　5. マスクが表示されなくなるまで待つ
　6. recordを終了
　７. recoed結果をspeedlineで解析して、結果を取得する

```javascript:SpeedMeasure.js
const puppeteer = require('puppeteer');
const speedline = require('speedline');
const filename = 'trace.json';

(async () => {

    const browser = await puppeteer.launch({
        devtools: true,
    });

    try {

        let page = (await browser.pages())[0];
        // 1. puppterで遷移元のページを表示する
        await page.goto("http://xxxxxx.com", { timeout: 300000, waitUntil: 'networkidle0' });

        // 2. recordを開始
        await page.tracing.start({ path: filename , screenshots: true});
        // 3. ボタンのクリックを行う
        // 4. マスクが表示されるまで待つ(ボタンクリック後マスクがかかるまでタイムラグがあるため)
        await Promise.all([
            page.waitForXPath("マスクが表示状態時のxpath", 300000),
            (await page.$x("ボタンのxpath"))[0].click()
        ]);
        // 5. マスクが表示されなくなるまで待つ
        await page.waitForXPath("マスクが非表示状態のxpath", 300000);
        // 6. recordを終了
        await page.tracing.stop();

        // ７. 結果をspeedlineで解析して、結果を取得する
        const result = await speedline(filename);
        console.log(result.duration)

    } catch (e) {
        console.error(e);
    } finally {
        browser.close();
    }

})();
```



# 最後に
手動と比べたら若干の誤差はありますが、毎日同じ時間に計測し、比較するという目的は満たせているのでよしとしました。
実際にはJenkinsで数時間ごとに実行、結果をスプレットシートに自動で書き込みまでやっています。
GitHub ActionsでGradleのユニットテストの結果を取得する

#GitHub ActionsでGradleのユニットテストの結果を取得する

GitHub ActionsでAndroidのユニットテストを実行し、結果をダウンロードできるようにしてみます。
ワークフローの実行中に生成された物をアーティファクトというそうです。
今回は以下を参考にしてトライしてみます。

https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts

### ymlファイルを編集する

```ruby:*.yml
name: branch2_CI

on:
  push:
    branches:
      - branch2         # Push events on master branch
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # Checking out
      - uses: actions/checkout@v1

      # Setting up JDK
      - name: set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      
      # Gradle build
      - name: Build with Gradle
        run: ./gradlew build
      
      # Archiving code coverage results ←このセクションがテスト結果を保存している
      - name: Archive code coverage results
        if: always()
        uses: actions/upload-artifact@v1
        with:
          name: code-coverage-report
          path: app/build/reports/tests/testDebugUnitTest
```

### 実行結果
左上に[Artifact]というのがあって、そこからcode-coverage-report.zipがダウンロードできます。

<img width="984" alt="スクリーンショット 2019-12-12 11.08.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351880/c9d86d89-7396-79ba-9d87-fb8378088557.png">

解凍したフォルダのindex.htmlをブラウザで開くとテスト結果が確認できました。
<img width="1074" alt="スクリーンショット 2019-12-12 11.17.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351880/30d9523f-549f-e23d-7daa-12a18170d688.png">


### まとめ
apkファイルなども残してもいいかもしれません。
社内でJenkinsを運用するより、よっぽど楽チンですね。
ただし、Actionsの無料枠のストレージ容量にも上限があるので、節約したい人は大きなファイルはAWSなどに送ってしまう方がいいかもしれません。
MacOSで[スペルを追加]した単語はどこに登録されるのか

<img width="200" alt="image" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165931/299369a4-791f-9af8-e353-8e1b39899848.png">
MacOSで[スペルを追加]した場合の単語登録先について調べた。

## 登録先
Mac OSにおけるスペルチェック機能でスペルを追加(Learn Spelling)した単語は

```console
~/Library/Spelling/LocalDictionary
```

に保存されている。
誤って追加したスペルなどがあれば、上記ファイル内から単語を消去すれば削除することができる。

## 変更の反映
変更を反映するには、下記いずれかの操作を行う。
・Activity MonitorでAppleSpellを終了
・Mac再起動

## 補足
AppKitの [NSSpellChecker](https://developer.apple.com/documentation/appkit/nsspellchecker) でも本ファイルを参照している。
このAPIを利用したライブラリで本来検出されるべきものがされない場合には本ファイルに登録されている可能性がある。

## 補足2
Macの環境移行時にこのファイルも移行すれば学習結果をそのまま新環境に持っていくことが可能。

## 調査環境
macOS Catalina(10.15.1)
【備忘録】ポートを掴んでるプロセスを特定して殺す

# そのポート掴んでんの誰じゃい
Eclipseからサーバ立ち上げてデバッグしようとしたら、ポートが使用中で起動できない状態に。
誰？誰が掴んでんの？？そんなん立ち上げた記憶ないんだけど？
そんなときのための備忘録です。

**※殺すときに管理者権限を要求されたら**
「管理者として実行」でコマンドプロンプトを起動しておくのがオススメ。
PID特定するだけで殺さないとかなら(多分)権限は不要なので、そのまま起動で。

# ポート番号からPIDを特定する
netstatで一覧取得してfindでポート番号指定して絞り込み。

```shell-session
>netstat -ano | find ":9080"
  TCP         127.0.0.1:9080         0.0.0.0:0              LISTENING       10428
```

# PIDから逆引きしてサービスを確認
まあPIDわかったらGUIのタスクマネージャから確認してもいいんだけど。
java.exeなんて場合によってはいろんなところに置いてあるわけで、タスクマネージャから「ファイルの場所を開く」でパス見て確認したりもします。

```shell-session
>tasklist /fi "PID eq 10428"

イメージ名                     PID セッション名     セッション# メモリ使用量
========================= ======== ================ =========== ============
java.exe                     10428 Console                    1  1,073,312 K
```

オプション「/svc」で各プロセスに紐づいている「サービス」の名前が表示されます。

```shell-session
>tasklist /svc /fi "PID eq 10428"

イメージ名                     PID サービス
========================= ======== ============================================
java.exe                     10428 N/A
```

オプション「/v」だと詳細に見れるけど、状況によっては取得に時間がかかる場合も。
「セッション名」「セッション#」「メモリ使用量」「状態」「ユーザー名」「CPU時間」「ウィンドウタイトル」が確認できます。

# PIDを指定して該当プロセスをkill
これもPIDわかったらGUIから殺せることもあるよね。管理者権限要求されるとかはあるけど。

```shell-session
>taskkill /F /PID 10428
成功: PID 10428 のプロセスは強制終了されました。
```

# おまけ
`taskkill`コマンドの各種オプションとか。

|パラメーター|説明|
|:--|:--|
|**/s** <コンピュータ>|名前またはリモートのIPを指定(`\`は使用しない)する。省略した場合はローカル。|
|**/u** <ドメイン>\\<ユーザ名>|`ユーザ`または`ドメイン￥ユーザ`として、指定ユーザの権限で実行する。省略した場合は実行ユーザ。|
|**/p** <パスワード>|`/u パラメータ`で指定したユーザアカウントのパスワードを指定する。省略した場合はパスワードなし扱い、指定したがパラメータなしだとプロンプトで入力。|
|**/fi** <フィルター>|対象または除外するプロセスの種類を指定する。フィルタはワイルドカード(`\*`)や複数(の場合はAND条件で絞り込み)指定が可能。詳細は公式ドキュメントを参照。|
|**/pid** <PID>|終了するプロセスのPIDを指定する。|
|**/im** <イメージ名>|終了するプロセスのイメージ名を指定する。通常は拡張子を含むファイル名(`hoge.exe`)となる。ワイルドカード(`\*`)を使用する場合は、`/fi`が必須(`/fi`なしはエラー)。複数指定した場合はOR条件で絞り込み。|
|**/t**|親プロセスを指定する。指定した親プロセスのすべての子プロセスも一括終了させられる。子プロセスに対しての絞り込みは不可。|
|**/f**|強制終了するプロセスを指定する。リモートプロセスの場合、省略してもすべて強制終了となる。|

最初からイメージ名がわかっている場合は、PIDなくても殺せるね！

Microsoftの公式ドキュメントはこちら→　[taskkill | Microsoft Docs](https://docs.microsoft.com/ja-jp/windows-server/administration/windows-commands/taskkill)
javascriptで配列にオブジェクトをぶち込む方法はこれ以外にないのか

ReactNativeでアプリを作っている最中に思わぬところで詰まりました。
タイトルの通りです。

目標は、

```js
[ { num1: 1, num2: 1 }, { num1: 2, num2: 2 } ... { num1: 100, num2: 100}]
```
という配列を作ることです。

まずは手打ちでやってみる。

```js
box = [{},{},...,{}] //100個用意
for(i = 1; i <= 100; i++){
   box[i].num1 = i;
   box[i].num2 = i;
}
```

できるが、100個も用意するのはめんどくさい。

ならば、こうしてみよう。

```js
box = [{}*100]
```
しかし、上のコードは``NaN``というエラーが出てしまう。

仕方ない、for文を使おう。

```js
box = []
for(i = 1; i <= 100; i++){
    box.push({});
    box[i].num1 = i;
    box[i].num2 = i;
}
```

できた！！！

が、なんだかスタイリッシュではない気がする。

他に方法を知っている方、ご教授ください。
ChatworkのAPIトークンの発行と実行の仕方まとめ

## API Tokenの準備
チャットワーク右上のプロフィール画像のメニューから「API設定」を選びます。

<img width="217" alt="スクリーンショット 2019-12-12 10.37.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452135/3a3e3769-6d8e-73d2-e815-24551fb2de94.png">

API Tokenのページが開くのでチャットワークのパスワードを入力して認証します。
するとトークンの文字列が表示されます。

<img width="619" alt="スクリーンショット 2019-12-12 10.38.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452135/d4880a5c-b319-2145-35f1-702c29038824.png">

※非常に大切な文字列なので扱いにご注意ください。
※トークンの発行には管理者の許可が必要な場合もあります。

## postmanの準備
[Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=ja)
今回はGUIの実行で分かりやすいようにpostmanというツールを使用します。
postやgetのhttpの実行を簡単に行えます。
もちろんコマンドから実行でも全然問題ありません。

## いざ実行

### エンドポイントの設定

``https://api.chatwork.com/v2``
現在（2019/12時点）ではAPIのバージョンがversion2なのでv2となっています。

今回は例として自分のアカウントのトークンを使用してメッセージを投げるAPIを実行したいと思います。

こちらを下図の箇所に設定します。
`` https://api.chatwork.com/v2/rooms/{マイチャットのroomID}/messages ``

![スクリーンショット 2019-12-12 10.49.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452135/f340206f-c971-9440-4941-e9e54f17e7bd.png)


### http headerの設定

HTTPリクエストヘッダに ``X-ChatWorkToken`` というキーでvalueに発行したトークンを設定します。
そして``Content-Type``は``application/x-www-form-urlencoded``を設定します。


### bodyの設定

ここはAPIのインタフェースに合わせて値を指定するだけです。
今回のメッセージ通知は``body``というkeyでvalueに文字列でメッセージを入れます。

![スクリーンショット 2019-12-12 10.50.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452135/db060e4b-5e5e-62d9-c49b-b36d80cd54c8.png)

### 実行

ここまで設定してあとは``Send``で実行するだけです。
実行するとレスポンスとして``message_id``が返ってきます。
自分のチャットにメッセージが飛んできていたら成功です。
エラーになったりした場合は設定やトークンを見直してみてください。

思ったより簡単ではなかったでしょうか？
APIを活用してシステム連携などさせると便利なのでぜひご活用ください。


## APIドキュメント
http://developer.chatwork.com/ja/
CreateJSの勉強をはじめました②

#前置き
公式に従い黒背景に青い丸を浮かばせました。
次は、アニメーションに入ります。

#tweenの作成
stage.update();をコメントアウトして言われた通りのコードを追加します。

```test.js
$(window).on("load", function() {
    var stage = new createjs.Stage("canvas_01");

    var background = new createjs.Shape();
    background.graphics.beginFill("0, 0, 0").rect(0, 0, stage.canvas.width, stage.canvas.height);
    stage.addChild(background);

    var circle = new createjs.Shape();
    circle.graphics.beginFill("DeepSkyBlue").drawCircle(100, 100, 50);
    stage.addChild(circle);

    createjs.Tween.get(circle, { loop: true })
    .to({ x: 400 }, 1000, createjs.Ease.getPowInOut(4))
    .to({ alpha: 0, y: 175 }, 500, createjs.Ease.getPowInOut(2))
    .to({ alpha: 0, y: 225 }, 100)
    .to({ alpha: 1, y: 200 }, 500, createjs.Ease.getPowInOut(2))
    .to({ x: 100 }, 800, createjs.Ease.getPowInOut(2));
 
    //stage.update();
});
```

この時点では真っ白。

stage.update();の位置に次のコードを追加します。

```test.js
    createjs.Ticker.setFPS(60);
    createjs.Ticker.addEventListener("tick", stage);
```

この二つのコードは、上がFPSの設定。FPSは1秒間での更新回数です。
下が、stageが更新されるためのイベントリスナーのようです。
これで、円が動くようになりました。

せっかく背景を黒くしているので、円を黄色にしてみました。
これは最初の横移動中の状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491400/7b430e4a-15af-2a65-71d6-a74bbbf91adb.png)

#改造
そして、追加の改造を行いたかったのでやってみます。
まずcanvasを追加します。

```test.html
  <canvas id="canvas_01" width="800" height="600"></canvas>
  <canvas id="canvas_02" width="800" height="600"></canvas>
```

次に、同じものを描写したいので、一連のコードをfunction化します。
ただ同じものだと楽しくないので、stageのscaleを変更できるように追加の記述を行い、変数を設定。

```test.js
    var init = function(canvasID, scale) {
        var stage = new createjs.Stage(canvasID);
        stage.scaleX = scale;
        stage.scaleY = scale;

        var background = new createjs.Shape();
        background.graphics.beginFill("0, 0, 0").rect(0, 0, stage.canvas.width, stage.canvas.height);
        stage.addChild(background);

        var circle = new createjs.Shape();
        circle.graphics.beginFill("yellow").drawCircle(100, 100, 50);
        stage.addChild(circle);

        createjs.Tween.get(circle, { loop: true })
        .to({ x: 600 }, 1000, createjs.Ease.getPowInOut(4))
        .to({ alpha: 0, y: 175 }, 500, createjs.Ease.getPowInOut(2))
        .to({ alpha: 0, y: 225 }, 100)
        .to({ alpha: 1, y: 100 }, 500, createjs.Ease.getPowInOut(2))
        .to({ x: 100 }, 800, createjs.Ease.getPowInOut(2));
    
        createjs.Ticker.setFPS(60);
        createjs.Ticker.addEventListener("tick", stage);
    }
```

そして、init関数をそれぞれ呼び出します！

```test.js
    init("canvas_01", 1);
    init("canvas_02", 0.5);
```

ちょっと下がスクショの関係で切れていますが、同じ動きをする違うサイズのstageの完成ですー！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491400/fe9c02fc-a7cf-3426-3ec9-7f89c7e2734f.png)


今回はここまでです！
次回はチュートリアルから離れて、カラーパレットを作ろうかなぁとか、無謀なことを考えています。


#参考URL
https://createjs.com/
【AngularJS】ネストしたデータをng-repeatで表示させる

三階層ぐらいにネストしたデータをng-repeatで表示させてみた。

#実装
~~~JavaScript
// データ構造はこんな感じです
data:[
	{"id":1,"name":"最初の項目","status":1,"created_date":"2019-01-01 00:00:00","updated_date":"2019-01-01 00:00:00",
	"nest1":[
		{"id":2,"name":"説明","status":1,"created_date":"2019-01-01 00:00:00","updated_date":"2019-01-01 00:00:00",
		"nest2":[
			{"id":3,"name":"使い方","status":1,"created_date":"2019-01-01 00:00:00","updated_date":"2019-01-01 00:00:00"
			}]
		}]
	},
	{...},
	{...}
]
~~~

~~~index.html
<div id="menu">
    <ul class="contents">
        <li ng-repeat="(i, first) in data">
            <a href ng-click="getInfo(first.id, first.status)">{{first.name}}</a>
            <ul>
                <li ng-repeat="(j, second) in first.nest1">
                    <a href ng-click="getInfo(second.id, second.status)">{{second.name}}</a>
                    <ul>
                        <li ng-repeat="(k, third) in third.nest2">
                            <a href ng-click="getInfo(third.id, third.status)">{{third.name}}</a>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</div>
~~~

#補足
・多分、ng-repeatで表示させる手段より、データ構造について考える方が面倒な気がする…
AdobeCC ver 5.0.0でExtendscript Toolkit CCをインストールする方法

## 三行で
1. AdobeCCの設定`⚙`を開く
2. アプリタブを開く
3. 設定の項目の`古いアプリを表示`をONにする。

これでアプリのインストール画面にExtendscript Toolkit CCが表示されるようになります。
インストールすれば使えるようになります。

## くわしく
仕事中なので後でゆっくり画像付きで手順を書こうと思います。
QCDからみる会社でのサービス開発の違い

現在バカンにて１ヶ月ほど前からPMを行っているものです。
前職ではSIerにてオンプレミスのサービスやMicrosoftで公開されているソフトウェアでの設計・構築を行っていました。ソフトウェアの開発やSaasビジネスに携わってみたいと思い、現在はSaaSビジネスを展開しているスタートアップに所属し、PMを行っています。
前職のSIerではお客様の意見や発注を元にBtoBのサービス開発（受託開発）をしていました。現職のスタートアップでは世の中に必要なサービス開発しています。

現時点で経験した範囲での個人的意見ではありますが、前職SIerで行っていた「受託開発」と現職のスタートアップで携わっている「サービス開発」の違いをご紹介したいと思います。

#一番の違い
個人的にはQCDが大きく異なるように感じます。

Q（Quality）・・・質
C（Cost）・・・コスト、料金
D（Delivery)・・・納期、時間

特にD（Delivery)に違いがあるような気がしております。
上記の観点から記載していきます。

#Q（Quality）
##受託開発
前職では基本的にBtoBの基幹システムに導入・開発をしていたため、セキュリティやSLA（どれくらいサービスを継続できるかを数値的に担保したもの）を大事にし、使いづらさより今まで利用していた機能と差がないように利用したいことや新しいサービスがあったとしてもセキュリティ的に利用したくないという「お客様の意見」を元に「Quality」を設定します。

##サービス開発
自らより良いサービスを会社もしくは一般の方に提供するため、自分たちがどういうサービスを必要としているかお客様の立場として何が必要か、自分たちの強みがどこにあるかを重視してサービスとしての「Quality」を決めることができると感じます。
自分たちの考えがサービスとして提供し、利用して頂くことがあるため、喜びと自分のサービスである責任感を感じることがあります。
QualityはセキュリティやSLAも考えつつ、「お客様として使いやすいか、利用しやすいか」を基準とし設定しています。

#C(Cost)
##受託開発
RFP（お客様が何をしたいかを記述されている書類）からどれくらい設計に対して時間がかかるかを元に費用をお客様に提示します。工数という時間の概念と設計の時にリスクを見積もります。
お客様から求められていることから、何をやるべきかでことで「Cost」を決めています。

##サービス開発
今世の中としてどのようなサービスを必要としているかを検討し、提供するサービスに落とし込んだ「Quality」から「Cost」に落とし込みます。「Cost」によってサービスの価値やお客様が利用して頂けるかが決まります。

#D（Delivery)
##受託開発
RFPからどの時期にサービスを展開するか、サービスを構築する時にで「Delivery」です。
保守や改善はあるものの、最初にプロジェクトとして立ち上げ、納期まで遂行してくこととなります。
PMとしては遅延がないか、どこが遅れているかをマネージメントする必要があります。
「Delivery」はよっぽどのことがない限り、お客様の必要な時期から見積もられます。

##サービス開発
今作ろうとしているサービスもしくは利用中のサービスが現在何が必要であり、必要なことをいつまでに作成する必要があるかによって「Delivery」は決まります。
「Delivery」を決めないとサービスを提供することが後退りになってしまうので、PMとしてはどの時期に何をするかを選定して、社内のエンジニアの方々や全体を動かすことが重要です。

#まとめ
あくまでも現時点で経験した範囲での個人的意見ではありますが、受託開発ではQCDをお客様を元に決めること、サービス開発では全てを会社と自ら考えお客様に提供することだと思っています。
自己責任や日々考えることは苦悩であると同時に、楽しさにも直結しているので精進して行きたいです。
🍰【CakePHP2】複雑なデータ取得結果を自作配列にしてページネートさせる

## 環境

PHP 7.2.21
CakePHP 2.10.18
MySQL 5.7.27

## 内容

ページネイトは便利機能であるが単純なfindのデータのみでしか出来ないっぽい
別DBに接続して得たデータやPHP側で修正をかけた自作の配列でもページネイトできればうれしい

ページネイトは内部でfindを実行しているのでControllerで用意した配列をセットしてそれをfindとして返すのみのページネイト専用Modelを作成してそれを利用した


## やったこと

Paginate専用のModelを新規作成し、Controllerでそれをuse
Controllerでデータを加工した後にPaginate専用Modelにデータをセットしてからpaginate関数にModel名を渡す

```Model/CustomArrayPaginator.php
<?php

class CustomArrayPaginator extends AppModel {
    public $useTable = false;

    // Paginatorに渡す配列データ
    public $data = [];

    /**
     * 受け取ったデータをpagingする為にfindをoverride
     *
     * @return array
     */
    public function find($type = 'first', $query = []): array {
        if (is_null($this->data)) {
            $this->data = [];
        }

        $page = Hash::get($query, 'page', 1);
        $limit = Hash::get($query, 'limit', 10);
        if ($page === 1) {
            // 1ページ目はoffset無し
            $offset = 0;
        } else {
            // 2ページ目以降はページ数-1に分割数をかけたものをoffsetとする
            $offset = ($page - 1) * $limit;
        }
        $result = array_slice($this->data, $offset, $limit);
        return $result;
    }

    /**
     * 配列数の独自計算の為にpaginateCountをoverride
     *
     * @param array|null   $conditions
     * @param integer|null $recursive
     * @param array|null   $extra
     * @return integer カウント数
     */
    public function paginateCount($conditions = null, $recursive = 0, $extra = []) {
        $this->recursive = $recursive;
        return count($this->data);
    }
}

```

```Controller/HogeController.php
<?php

// 追加Modelをuseに追加
App::uses('CustomArrayPaginator', 'Model');
App::uses('Fuga', 'Model');

class HogeController extends AppController {
    // 追加Modelを$uses配列に追加
    public $uses = [
        'CustomArrayPaginator',
        'Fuga',
    ];

    public $paginate = [
        'limit' => 10,
    ];

    public function index() {
        // 加工前のデータを取得
        // ※limitは設定しない！！！
        $tmpData = $this->Fuga->find('all', [
            'fields'     => ['*'],
            'conditions' => ['del_flg' => '0'],
            'order'      => [
                'Fuga.id' => 'asc',
            ]
        ]);

        // 関数で取得したデータを色々加工
        $dispData = $this->modDispData($tmpData);

        // 加工したデータをModelの$dataにセットし、overrideしたfindでページネイトすることでセットした値をそのまま使用
        $this->CustomArrayPaginator->data = $dispData;
        // Model名を渡す
        $result = $this->paginate('CustomArrayPaginator');
    }
}

```

## 結果

自分で任意に加工したデータをpaginateすることができた！！
今回の例ではFugaテーブルから単純なfindしかしていないげどJOINなども思いのままです
BlenderでDriver


11日目が空いていたので（またか

説明画像はありません。
Auto Run Python Scriptを設定からONにしておかないと動きません。

BlenderにおけるDriverとは、1行で書き表されるpython scriptです。
どんなものか試してみたい人はまず
Emptyをシーンに追加し、Rotationのどれかに 

```
#radians(frame)
```
と打ってEnterを押してみましょう、現在のフレームの角度に回転するアニメーションになるはずです。
やっていること
    - frameはBlender Driverの機能として、現在のフレームの値が入っている変数です。
    - blenderはスクリプトだとラジアン角度なので、frameをradians()　で囲むことによって、frameを角度として扱うようにしています。
    - 最初の#はDriverを書いているということをBlenderに示すというショートカットです。欄が紫のときは必要ありません。
   
```
#sin(radians(frame))
```
 - 数学のsinです。locationに入れるとsin波みたいになります。ゆらゆらします

```
#round(sin(radians(frame))*5)
```
 - さらに値を四捨五入してみました（元の値を5倍にしています。０－１の範囲だと分かりにくいので）
 　カクカクな感じのsinな動きになりました
mathはインポートされているようなので使ってみると良いでしょう

##ヘンなことをしてみる

###自己責任で…

```
#len(bpy.data.objects)
```

オブジェクトデータの数を値に入力します。Driver Editorには遅いだのうまく動かないなどと怒られますが、どうやら動くようです。

スザンヌを作成し、フレームENDを507にします。

Emptyのlocation のXに

```
bpy.data.meshes[0].vertices[int(frame)].co[0]
```
location のYに

```
bpy.data.meshes[0].vertices[int(frame)].co[1]
```
locationの　Zに

```
bpy.data.meshes[0].vertices[int(frame)].co[2]
```

でフレーム数に対応した頂点のローカル座標にEmptyが移動します。CNCとか３Dプリンタっぽい動きに見えなくもない･･･

one more thing...
ワンライナーがキツイという方はテキストエディタ->templates->python->Driver Functions
で出来たテンプレを読んでみると良いかもしれません（Driverに機能追加できる
fast_json_apiでErrorSerializerを作った

今やっているバイトで[fast_json_api](https://github.com/Netflix/fast_jsonapi)というgemを使ってRailsAPI serverから返すresponseをmodel serializerに記述しています。
fast_json_apiを使うことで
- responseが早くなる
- 統一されたresponseを開発者間で共有できる
- table columnの追加があった時に、変更が楽

などのメリットがあります。

## 使うタイミング

```ruby
def create
  @book = Book.new(create_params)

  if @book.save
    render ...
  else
    render エラーレスポンスを返したい
  end
end
```

RailsAPI serverでCRUD処理を書いていると、上のような条件分岐をすることがあります。
その時にエラーの内容をjson_apiの仕様に基づいて返したいという動機が生まれました。

## 作ったコード

```ruby
class ErrorSerializer
  def initialize(model)
    @model = model
  end

  def serialized_json
    error_json = @model.errors.messages.map do |field, err_msgs|
      err_msgs.map do |err_msg|
        {
          source: { pointer: "/data/attributes/#{field}" },
          detail: err_msg
        }
      end
    end
    { erros: error_json.flatten }
  end
end

```

[json_apiのエラーの仕様](https://jsonapi.org/examples/#error-objects)によると、

```json
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json

{
  "errors": [
    {
      "status": "422",
      "source": { "pointer": "/data/attributes/firstName" },
      "title":  "Invalid Attribute",
      "detail": "First name must contain at least three characters."
    }
  ]
}
```

のように

```yaml

{
    "errors": ...
}
```
の形になっている必要があります。
【Unity】バッチビルド時に、PlayServicesResolverを強制的にResolveしたい

# 前提

- Androidでプッシュ通知をしようと思ったら、Firebase Cloud Messaging を使う必要がある。
- UnityでFirebase Cloud Messagingを使おうと思ったら、PlayServicesResolverと上手に付き合う必要がある。
- PlayServicesResolverはapplicationIdentifierが変わるとResolveし直す必要がある。
- バッチビルドで開発版/ストア版を切り替える時、applicationIdentifierを書き換えた直後に、明示的にResolveしたい。

# 解決法
``` BatchBuild.cs
// 強制的にResolve
GooglePlayServices.PlayServicesResolver.ResolveSync(true);
```
- これで強制的にResolveされる模様。
- どこにもドキュメントはないが、試しに使ってみたところ問題なさそう。(Unity2017.4.34とFirebase Unity SDK 5.7.0の場合)
- 使う場合は自己責任で！

# おまけ(Resolveを走らせたくない)

- iOSの通知は従来の方法を使いたい。
- 特にFirebaseの何かを使うわけじゃない。
- Firebase Unity SDKはAndroid版だけ入れる手段がない。
- iOSのビルド時、（大抵）ビルドが終わったタイミングでResolveが走ってしまう。
- 大きな実害はないが、ログを汚してエラーを探しにくくなるし、ビルド時間がもったいない。

``` BatchBuild.cs
// iOSでは使わないので、自動Resolveを抑止
GooglePlayServices.PlayServicesResolver.UnlinkAutoResolution();
```
- これもドキュメントされてないが、余計なResolveが走らなくなったことは確認。
- もっとも、こちらは普段からAutoResolve設定をオフにしておくだけでいいのかもしれない。
homesteadのxdebugが通らなくなった場合


```
vagrant destroy -f && vagrant up
```

```
sudo apt-get install php-xdebug
```
```
sudo service php7.4-fpm restart

vi /etc/php/7.4/fpm/conf.d/20-xdebug.ini
vi /etc/php/7.3/cli/conf.d/20-xdebug.ini
```

```
[xdebug]
zend_extension=xdebug.so
xdebug.remote_enable = 1
xdebug.remote_connect_back = 1
xdebug.remote_port = 9000
xdebug.max_nesting_level = 512
xdebug.remote_host=10.0.2.2
xdebug.remote_log=/tmp/xdebug.log
```
【rails c】pluckしたデータをCSVで保存するコマンド

# pluckしたデータをCSVで保存する処理
便利

```ruby:4行目のみ書き換えれば良いです
require 'csv'
File.open('backup.csv', 'w') do |f|
  csv_string = CSV.generate do |csv|
    User.where(hoge: nil).pluck(:id, :name).each{|data|csv << data}
  end
  f.puts csv_string
end
```
プロジェクトマネジメントをやってみた

# 目的
これからプロジェクトマネジメントを始める人は不安だらけだと思うので、こんな感じでもなんとかなっているよ。という意味で、少しでも参考にしてもらえれば。

## 前置き
aucfanに転職して早2年、前職は零細スタートアップでスマホアプリのSNSを運営。
python + mongoDB on AWS的な環境にて、サーバーサイドエンジニアとして従事。

**プロジェクトマネジメントらしい事は一切経験せず。**

## プロジェクトマネジメントとは？
- プロジェクトを成功裏に完了させることを目指して行われる活動のことらしい
- どうやらQCDを管理するらしい
    - Q…品質（Quality）
    - C…コスト（Cost）
    - D…納期（Delivery）

### やってみて分かったこと
それっぽい事はできます(得意です)と嘯いて入社をした経緯もあり、入社した翌週には炎上案件の火消しをするための管理を任されました。
サービスを理解していないのと、ソースコードを1行も見ていないので、まずは関係メンバーそれぞれにヒアリングを行いました。ヒアリング内容は下記3つ
 - **なにが起こっているのか**
 - **いつまでにやらないといけないのか**
 - **今の体制でそれは叶うのか**

その結果、分かった事が1つ

***誰も全体像を把握していない***

でした。。

そこで火を消すために、以下を実施
- **ヒアリングした内容から課題をピックアップ**
- **課題管理票の作成**
- **毎朝、毎夕に進捗確認と残課題確認**

課題管理票によって課題の可視化をした事で、自分もメンバーも、残作業のボリューム感や自分以外で発生している問題が見えるようになったと思います。


この経験が今思うととても大事だったと思います。
その時は言葉すら知らなかった**QCD**の大切さがそこにありました。

そして、スタートアップのようなむき出しのオレオレエンジニアの集まりでない限り、管理がされていないプロジェクトは思うようなスピードや品質で動かないのは(良くも悪くも)当たり前なのだと経験できました。

自社サービスを運営している会社では、納期や品質、目的に対する意識が緩くなりがちで、本当に良くある事なので、1つ上のレベルにステップアップするためには意識すると良いと思います。

### まだまだ勉強不足なこと
プロジェクトは生物です。
1つのプロジェクトが終了すると、新たな未経験のプロジェクトを推進しないといけません。常に未経験の新しいプロジェクトを完遂するのが役割です。
それもあって、プロジェクトマネジメントをする中で一番難しいと感じているのが、
**工数見積の査定**

実際に開発をするメンバーに工数を見積もってもらいますが、そこに積まれるバッファーが適正なのか、そもそも見積もりが適正なのかを判断するのがまだまだ経験不足だと感じている（ので誰か教えて下さい）

# 最後に
プロジェクトマネジメントをしていると、MTGに呼ばれる頻度が多くなります。
それも工数なので、5W1Hを毎回のMTGで確認するようにし、無駄をなくし、定時に帰れるようにしましょう。
CircleCIをDocker+Rails(RSpec+Rubocop)で動かす方法

## 設定ファイル
```yaml:config.yml
version: 2
jobs:
  build:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - run:
          name: docker-compose build
          command: docker-compose build
      - run:
          name: bundle install
          command: docker-compose run <rails_container_name> bundle install
      - run:
          name: setup db
          command: docker-compose run <rails_container_name> rails db:create db:migrate
      - run:
          name: seed db
          command: docker-compose run <rails_container_name> rails db:seed_fu
      - run:
          name: docker-compose up
          command: docker-compose up -d
      - run:
          name: rubocop
          command: docker-compose run <rails_container_name> rubocop
      - run:
          name: rspec
          command: docker-compose run <rails_container_name> rspec
      - run:
          name: docker-compose down
          command: docker-compose down
```

重要なのは

```yaml
    machine:
      image: circleci/classic:edge
```
の部分です。
これを使うことで、`docker-compose`を使うことができます。

[docker-compose のインストールと使用](https://circleci.com/docs/ja/2.0/docker-compose/)

>docker-compose ユーティリティは、Machine Executors と [CircleCI コンビニエンスイメージにプリインストール][pre-installed]されています。
CombineのPublisherにignoreNilを実装する

SwiftのCombineのPublisherにはOutputがOptionalの場合にreplaceNil(with:)メソッドが存在します。これはOutputがOptional.noneの場合にreplaceNilの引数に置き換え、OutputをOptional.Wrappedに変換するメソッドです。

この場合replaceNilの引数、つまりOptional.Wrappedのデフォルト値が必要になりますし、私はnilを無視するメソッドが欲しいのです。なのでignoreNilを作成してみました。

```swift
public protocol OptionalType {
    associatedtype Wrapped
    
    var optional: Wrapped? { get }
}

extension Optional: OptionalType {
    public var optional: Wrapped? { self }
}

extension Publisher where Output: OptionalType {
    public func ignoreNil() -> AnyPublisher<Output.Wrapped, Failure> {
        flatMap { output -> AnyPublisher<Output.Wrapped, Failure> in
            guard let output = output.optional
                else { return Empty<Output.Wrapped, Failure>(completeImmediately: false).eraseToAnyPublisher() }
            return Just(output).setFailureType(to: Failure.self).eraseToAnyPublisher()
        }.eraseToAnyPublisher()
    }
}
```

Publisher.OutputがOptionalの場合にignoreNilを実装したいのですがOptionalをそのままwhereで判定することができません。ですのでOptionalTypeプロトコルを作成してOptionalに適合させ、Publisher.OutputがOptionalTypeである場合に実装しています。

実装の内容は、flatMapでnilの場合はEmptyを、実体がある場合はJustを返しています。
EmptyはcompleteImmediatelyをfalseにしてすぐにFinishしないようにしています。が、trueにしても動作が変わったようにならないので、この引数の効果はよく分かっていません。誰か教えてください。

以下のように利用すると

```swift
let c = ["first", nil, "second", nil, "third"]
    .publisher
    .ignoreNil()
    .sink { string in print(string) }
```

```
first
second
third
```

と表示され、nilが無視されているのが分かります。

まだCombineに精通していないのでこれが正しい実装か分かりませんが、とりあえずignoreNilメソッドができました。
Docker を利用して Splunk を複数バージョン起動する

#はじめに
Splunk の新しいバージョンが出たので試したいと考えたときに、既存環境を壊さず試そうと思うと
専用のサーバ、もしくはPCを用意する必要があります。
※実際には、conf ファイルいじれば、同居もできなくはないですが。。。結構面倒です。
そんなとき、Docker 環境があれば、複数のバージョン、異なる環境を同時に起動させることも可能です。

##Docker Hub
Docker Hub (https://hub.docker.com/r/splunk/splunk ) に、Splunk の Docker Container Image がありますので、そこからイメージを取り込みます。

``` 
$ docker pull splunk/splunk:latest
```
ただ、これだとそのときにある、最新版のイメージが取り込まれます。（2019/12/12 現在では、バージョン8.0です）

取り込める Container Image は、[ここ](https://hub.docker.com/r/splunk/splunk/tags)から確認できます。

例えば、バージョン７．３．３ を取り込みたい場合、下記のように実行します。

```
$ docker pull splunk/splunk:7.3.3
```

## Splunk Docker Image の起動
イメージがダウンロードできたら、Docker Image を確認します。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
splunk/splunk       7.3.3               24213f4de1b4        3 weeks ago         1.3GB
splunk/splunk       latest              53b3518333e7        7 weeks ago         1.65GB
```

これらの Container Image を利用して、 Splunk Container を起動します。
まず、最新版を起動します。

```
$ docker run -d -p 8000:8000 -e "SPLUNK_START_ARGS=--accept-license" -e "SPLUNK_PASSWORD=Changeme1" --name splunk splunk/splunk:latest
```
※パスワードは任意の値に変更ください。

ステータスを確認します。

```
$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS                        PORTS                                                                           NAMES
0170e7177fac        splunk/splunk:latest   "/sbin/entrypoint.sh…"   About a minute ago   Up About a minute (healthy)   8065/tcp, 8088-8089/tcp, 8191/tcp, 9887/tcp, 0.0.0.0:8000->8000/tcp, 9997/tcp   splunk
```

次に、バージョン７．３．３ を起動します。
※ Port ８０００ を ８００１ にマッピングし、Container 名を Splunk733 に変更します。

```
$ docker run -d -p 8001:8000 -e "SPLUNK_START_ARGS=--accept-license" -e "SPLUNK_PASSWORD=Changeme1" --name splunk733 splunk/splunk:7.3.3
```
※パスワードは任意の値に変更ください。

ステータスを確認します。

```
$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS                            PORTS                                                                           NAMES
6e007165fbe2        splunk/splunk:7.3.3    "/sbin/entrypoint.sh…"   6 seconds ago       Up 5 seconds (healthy)   8065/tcp, 8088-8089/tcp, 8191/tcp, 9887/tcp, 9997/tcp, 0.0.0.0:8001->8000/tcp   splunk733
0170e7177fac        splunk/splunk:latest   "/sbin/entrypoint.sh…"   7 minutes ago       Up 7 minutes (healthy)            8065/tcp, 8088-8089/tcp, 8191/tcp, 9887/tcp, 0.0.0.0:8000->8000/tcp, 9997/tcp   splunk
```

起動が確認できたら、Web ブラウザを起動し、Splunk を利用します。
http://localhost:8000
http://localhost:8001

Splunk をシングルインスタンスで利用するのであれば、これで同一ホスト上で、複数バージョンを同時に確認できます。
Port Mapping を応用すると、Indexer Cluster 構成なども組むことも可能です。（リソースの課題はおいておいて）

## conf ファイルを直接修正したい場合
docker コマンドで直接コンテナ内に入ります。（splunk コンテナーに入る場合）

```
docker exec -it splunk bash
```

vi 等で直接 conf ファイルを変更できます。
また、７．x は、Ubuntuベース、８．０ ベースは、Redhat ベースになるようなので、若干利用できるコマンドが異なりますのでご注意を。

##最後に
バージョンアップ前に今までの環境を壊さずに、技術検証するときや、新しい機能をとりあえず試してみたい（例えば、新しい機械学習の App を利用したい等）ときに、簡単に環境作るくことができると思います。
データサイエンティストへの道のり

この記事は、[データ分析人材のキャリア（データラーニングギルド）アドベントカレンダー](https://qiita.com/advent-calendar/2019/data-learning-guild)の記事です。
初めてQiitaを記事を書くので不安でいっぱいですが、最初のデータ分析の仕事を始めてから約１年ちょいが経ち、いろいろ学んだことがあるので記事にしようと思います。
身バレを防ぐために、ちょっとアバウトな表現だと感じる箇所があるかと思いますがご了承ください、、、

## 経歴
・大学：　海外の大学でコンピュータサイエンスを専攻
・大学卒業後～新卒入社直前：　フリーランスのITなんでも屋さん
・新卒入社後：　データサイエンティスト

ざっくりしていて、いろいろツッコミどころがあるかもしれません。
しかし、大学卒業までは話が非常に長くなるため割愛します、、、
今回は、大学卒業後のフリーランスバイトの経験～新卒入社後までのことを書きます。
（次回があるか分からないです、すんません）

## ある日突然、データ分析の業務に携わることに
大学在学中は、バリバリのSEではなく、ITを使って問題解決できるような仕事をやりたいと思っていました。
そこで、データサイエンティストというポジションに興味を持ち、卒業後はデータサイエンティストとして働くことができる会社を探すことに。
無事にデータサイエンティストのポジションの内定をもらい、会社に入社するまでは~~ニート~~アルバイトをしようとしました。
家から出るのが面倒だと思い、在宅ワークを探していると、データ分析とは別のポジションの求人を見つけました。
在宅ワークができるなら何でもいいやと思いながら面接に行き、経歴と新卒入社後はデータサイエンティストとして働くことを伝えると、データ分析の仕事をやってみない？とのこと。
全く予想もしていなかった展開に驚いたと同時に、データサイエンティストとしての修業が積めそうだと考え、データ分析の仕事を始めました。
しかし、大変だったのはここからでした。

## 修業の連続
在宅フリーランスに採用されたあとは、データ分析の基礎や流れを学ぶための研修課題を行いました。
研修課題は問題なくクリアしましたが、実際の業務に取り掛かるとまだまだ知らないことばかり。
特に、データサイエンティスト協会が定める必要スキルセットうちの１つである、ビジネス力。
解決したい課題は明示されていたものの、問題の背景やクライアントの業界をふまえて最適解を考え出すのが大変でした。
そして、まだ実務を積んでいなかった私にとって、ビジネス本を読んだりビジネスの専門用語をリサーチして覚えることも重要。
その上、Pythonでデータ分析はそこまで慣れていなかったので、Qiita様に頼りながらコードを書いて分析する日々。
基本的にデータ分析を行っていましたが、時にはWebスクレイピングの業務なども担当し、外部の勉強会にも積極的に参加して新しい知識を吸収していました。

フリーランスバイトは大変でしたが、自分の成長が感じられた非常に貴重な経験であり、データサイエンティストになるためのスキルの重要さを新卒入社前という早い段階で知ることができました。

## 新卒入社後
フリーランスバイトは卒業し、某ＩＴ企業のデータサイエンティストとして入社しました。
会社の研修では使用するデータだけ与えられ、自ら解決したい課題を決めることに。
データを眺めて課題決定をした直後の上司からのフィードバックとして、何のためにその課題を解決したい、または解決する必要があるのか、ということを意識するべきと教わりました。
自社が保有しているデータの業界が特徴的なため、これ以上詳しい研修を書いてしまうと身バレしそうなので割愛します。

## データサイエンティストとして必要なスキル（持論）
フリーランスバイトと今の会社を通して、データサイエンティストとして必要なスキルは、クライアントに寄り添って適切なデータ分析をするという心意気と、常に成長したいという気持ちなのではと思います。
もちろん、統計学・ビジネス力・コーディングのスキルも大事です。
しかし、データサイエンティストになる第一歩としては、気持ちとやる気が重要なのではと考えています。
また、データサイエンティストを含め、ＩＴエンジニアは新しい技術に追いつくために常に勉強が必要であり、自分のスキルの低さに落ち込む必要はないと思います。
~~落ち込んでいる場合があったら、勉強しましょう。~~


私自身もまだまだ成長途中なので、一緒に頑張りましょう！！
FreeBSDのnewsyslogでローテーション後に指定したコマンドを実行する方法

FreeBSDで構築されたWebサーバのログを別サーバに一定期間保管する必要があるため、logrotateを使用してローテートの後に別サーバへ転送していました。

newsyslogで同じこと出来ないのかなとnewsyslog.conf(5)を見てみるとflagsにRをつける事で、
ローテート後にコマンドを実行できそうなので、newsyslogで同じことができるかどうかやってみました。

FreeBSDのバージョンは12.1です。

```/etc/newsyslog.conf.d/nginx
# logfilename          [owner:group]    mode count size when  flags [/path_to_pid_cmd_file] [sig_num]
/var/log/nginx/*.log   root:wheel       644  7     *    $D05  BGXR  /usr/local/etc/nginx_rotate.sh 30
```

nginxのnewsyslogの設定に関しては[こちらも参照](https://qiita.com/psaxuww/items/709636448603f1f5af7a)

[/path_to_pid_cmd_file]の部分はRフラグによって実行コマンドになったので、シグナル発行をスクリプトの最初に入れています。
シグナルを投げたらローテーションされたファイルを転送するだけです。

```/usr/local/etc/nginx_rotate.sh
#!/bin/sh

SIGNUM=$1
kill -$SIGNUM `/bin/cat /var/run/nginx.pid`

YMD=`date '+%Y%m%d'`
scp /var/log/nginx/access.log.0.xz server:"/savelog/access.log.${YMD}.xz"
scp /var/log/nginx/error.log.0.xz  server:"/savelog/error.log.${YMD}.xz"
```

これでlogrotateが無くてもログのローテーション＆ファイル転送ができました。

と思って後日確認してみたらファイルは転送されていませんでした。原因はnewsyslogの処理が

1. ファイル名変更
1. シグナル送信（コマンド実行）
1. 圧縮

の順番で行われているためでした。考えてみたら圧縮後にシグナル送っても遅いですよね。

圧縮前のファイルを転送するようにすれば終わりですが、logrotateの時のように圧縮後の転送でないとlogrotateの代わりにはなれないと思い結局下記のようにしてみました。


```/etc/newsyslog.conf.d/nginx
# logfilename          [owner:group]    mode count size when  flags [/path_to_pid_cmd_file] [sig_num]
/var/log/nginx/*.log   root:wheel       644  7     *    $D05  BGR  /usr/local/etc/nginx_rotate.sh 30
```

logroateに圧縮させるのもやめてスクリプトで圧縮させてから転送するようにしました。

```/usr/local/etc/nginx_rotate.sh
#!/bin/sh

SIGNUM=$1
kill -$SIGNUM `/bin/cat /var/run/nginx.pid`

/usr/bin/xz /var/log/nginx/access.log.0
/usr/bin/xz /var/log/nginx/error.log.0
YMD=`date '+%Y%m%d'`
scp /var/log/nginx/access.log.0.xz server:"/savelog/access.log.${YMD}.xz"
scp /var/log/nginx/error.log.0.xz  server:"/savelog/error.log.${YMD}.xz"
```

これで今度こそlogrotateを使わずにログのローテーション＆ファイル転送ができました。
RxSwift のクラスの整理

RxSwift は便利だがいっぱいクラスがあって圧倒されてしまう。 (たぶん RxJava/RxRelay など他の言語でも共通の話だと思います)

* PublishSubject
* BehaviorSubject
* ReplaySubject
* AsyncSubject
* PublishRelay
* BehaviorRelay
* ReplayRelay
* AsyncRelay

これらは すべて Observable の特殊系である。そして、[`Publish`|`Behavior`|`Replay`|`Async`]と[`Subject`|`Relay`]の組み合わせである。

## Observable

前置き。

Observable は以下のような特徴をもつ。

1. 値が emit されるたびに onNext が呼ばれる
2. 自身の onNext, onCompleted, onError を自分で呼び出すことはできない
3. 終了時、エラー時に onCompleted, onError が呼ばれる

この特徴の 2 を開放したものが `Subject` 、 3を制限したものが `Relay` とみなすことができる。

前置きここまで。各要素について説明していく。

## Subject

* ほぼ Observable
* ＋ Hot/Shared
* ＋ 自身の onNext, onCompleted, onError を自分で呼び出して発生する機能開放がされている
  * ””自分で"" 送れる＝主語＝Subject 的な由来…？

## Relay

* ほぼ Observable
* ＋ Hot/Shared
* ＋ Completed, Error を送出する機能がもぎ取られている
  * 値を送るだけ＝リレー的な由来…？

## Publish

  * 購読したら購読したタイミングからの値が流れてくる
  * （これがまあ当然っぽく感じる一番素朴なやつ）

## Behavior

  * ほぼPublish
  * ＋購読したらとりあえず最新の値が流れてきて、その後もemitされた値が流れてくる

## Replay

  * ほぼBehavior
  * ＋最新の値1個だけでなく、n個とりあえず流すことができる
 
## Async

  * onCompletedのときに、購読した側は「一番最後値→onCompleted」になる（説明が難しい　珍しくマーブルダイアグラムを見たほうがわかりやすいと思った例）
  * Relay 版はない（onCompletedをもぎ取られてるから）

## まとめ

[Publish|Behavior|Replay|Async]＋[Subject|Relay]は、すべてObservableであるが、若干違う部分があり、それぞれの要素の特徴の組み合わせで説明できる。

### PublishSubject

*  Hot/Shared な Observable である
* ＋ 自身の onNext, onCompleted, onError を自分で呼び出して発生する機能開放がされている (Subject)
* 購読したら購読したタイミングからの値がふつうに流れてくる (Publish)

### BehaviorSubject

* Hot/Shared な Observable である
* ＋ 自身の onNext, onCompleted, onError を自分で呼び出して発生する機能開放がされている (Subject)
* ＋ 購読したらとりあえず最新の値が`1個`流れてきて、その後もemitされた値が流れてくる (Behavior)

### ReplaySubject

* Hot/Shared な Observable である
* ＋ 自身の onNext, onCompleted, onError を自分で呼び出して発生する機能開放がされている (Subject)
* ＋ 購読したらとりあえず最新の値が`n個`流れてきて、その後もemitされた値が流れてくる (Replay)

### AsyncSubject

* Hot/Shared な Observable である
* ＋ 自身の onNext, onCompleted, onError を自分で呼び出して発生する機能開放がされている (Subject)
* ＋ 購読しても値は流れてこず、onCompletedがemitされたときに一番最後の値だけが流れてくる (Async)

### PublishRelay

* Hot/Shared な Observable である
* ＋ Completed, Error を送出する機能がもぎ取られている (Relay)
* 購読したら購読したタイミングからの値がふつうに流れてくる (Publish)

### BehaviorRelay

* Hot/Shared な Observable である
* ＋ Completed, Error を送出する機能がもぎ取られている (Relay)
* ＋ 購読したらとりあえず最新の値が`1個`流れてきて、その後もemitされた値が流れてくる (Behavior)

### ReplayRelay

* Hot/Shared な Observable である
* ＋ Completed, Error を送出する機能がもぎ取られている (Relay)
* ＋ 購読したらとりあえず最新の値が`n個`流れてきて、その後もemitされた値が流れてくる (Replay)

### AsyncRelay

* そんなものはない
* Async は onCompleted のときにごにょごにょするという意味だが、Relay からはそもそもCompletedを送出する機能がもぎ取られているので
自己中な謝り方


先日、お客様先へサイトリニューアルの定例会があったため訪問したのですが、その定例で先方様を少し不機嫌にさせてしまった。

しかも社長様・・・

やっちまったか？ｗ

![tumblr_inline_n0esw6EQvR1sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/3014761a-966c-5500-99f9-977248420a46.jpeg)


結果としては大事にはならなかったのでよかったのですが・・反省ですね。

簡単にいうと原因としては、ボクの勉強不足からの理解力の遅さに苛立ったってしまい、不機嫌にさせてしまった。

コトの発端はサイトで使用する情報のデータの有無を確認しているところからだった。

サイトで使用する情報が法律上で少し引っかかる内容で、注意してサイトに明記しないと行けないものです。

社長さんは細かく説明してくれたと思うんです・・

「これは相手の会社に使用権があってね？使う場合はなんちゃらかんちゃら〜・・」みたいな感じで、合わせて頂いた感じ満載ですねｗ

それを聞いた私は噛み砕いたつもりで

自分「なるほど、ではここはこのデータをお持ちってことですよね？」

社長「？いやさっきの話きいてた？ここのデータは使用権があるからつかえないわけでね？」

正直、その社長さんは全部自分の話口調で話し、内容もけっこう要約する人だから、ボクじゃまだそこまですぐ読み込めない・・

でも食い違いは関係なく発生します。

自分「え、あ失礼しました。えっと、先ほどのここはぁ〜・・・」

社長「だからね？ここで使えないってことはグループであるここの会社では使用権の関係でうんちゃらかんちゃらぁ〜」

というようなやりとりがかなり長く続きました。

![tumblr_inline_n0esvqTKM61sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/5a307b70-9a5a-af07-db72-a727b1d5aed4.jpeg)


その結果、話がよくわからない感じで進んでしまい、社長になにか指摘されても

話がわからないので「失礼しました」のみ

で、言われたとおり修正内容や今後の進行のさせ方を鵜呑みで聞いてしまっていました。

もう空気でわかっていましたが、かなり社長さんが不機嫌になっていることがわかりました。

根本な原因はボクの勉強不足からくるものですが、じゃあ分からないなりにできることがあった気がします。

いまこのブログを書いている今だからこそわかったんですがｗ

定例の後半は指摘されても謝るだけでしたが、もちろん勉強不足でよくわからないという理由ですが、他に理由としては

「この人は話を要約する人ですべて分かるていで話すし、とりあえず聞いた内容を聞いてあとでかみ砕いて、定例終了後にメールで聞こうかな・・怒らすとやだし、早く終わらせよう」

のようなメンタルブロックが働いていました・・
こうなるとこのブロックを外すのは大変ですね・・

その謝り方の言い方や気持ちのこめ方もあるが、やっぱ分かる人は分かるんでしょうね・・

たぶんわかってねーなコイツみたいな感じで見抜かれたんだと思います。

じゃあ、まったくわからない自分としてどのような謝り方がよかったのか？
人によりけりですが、例えば

「失礼いたしました。私としてはここの部分でユーザーが欲しがる情報かと思ったので、使用権のことを考えずユーザー目線で言ってしまいました」

いいわけっぽいですねｗ

でもサイトを作るときは、ユーザー目線でまず考えるので言われた社長さんも「ユーザーがほしがる情報か。。ちゃんとユーザー目線で考えてくれるんだ・・でも出せないんだよな」

と、、思ってくれるはずｗ
若干怒られそうにはなる気がしますがｗｗ

謝るのって、「許してもらう」ってことです。

この時は、はやく終わらせよう・・どうせ要約しすぎてわからんし・・・

と自己中な謝り方になっていましたね。

![tumblr_inline_n0esvaa1K01sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/56ee35df-c397-df01-2744-b3b54d193d19.jpeg)


許してもらうのに自己中はダメですねｗ

人によって謝り方はあると思いますし、絶対的な答えもない気がします。

謝り方一つでも少し考えてちょっとしたことを付け足せば、良い関係を築けるんではないでしょうか？
ミャンマー語(ビルマ語)の文字コード問題で困ったら

#ミャンマー語の文字コードについて
ミャンマーは、歴史的な背景からインターネット技術の発展が世界から取り残された地域の１つ。
従来はZawgyi文字コードが主流でしたが、市場開放され国際化対応が進んでいく中、
UNICODEへ変わりつつあります。

日本の例では、EUCやSJISなどのWebがあった時代から、今ではUTF8に変わってきた歴史に似ています。
https://enjoy-yangon.com/ja/enyanblog/351-change-myanmar-font-zawgyi-to-unicode

#文字コード混在による文字化け対処の基本方針

現地人でなければ、文字自体が文字化けしているような文字なので、何が問題あるのかは我々エンジニア・プロラマーにすればわからないというのが本音。ただし、エンジニアなら、その問題解決に向き合う必要があります。

つまり、問題解決に必要な要件を決めて、ソフトウェアで解決する必要があります。

要件１　ZawgyiかUNICODEが文章で判定できる事
要件２　ZawgyiからUNICODEへ文字コード変換できる事

その2点が必須要件です。

#実際に実装するにあたって
Githubなどを検索するしてみました。
Google Myanmar Toolがヒットしてきました。
https://github.com/google/myanmar-tools

この内容を確認していると、ZawgyiかUNICODEを判定してくれるという機能を持つ事が書かれています。
これを使います。

さらにヒントが隠されていて、ZawgyiからUNICODEへ文字コード変換するには、Rabitを使えと

Rabbit-Converter
https://github.com/Rabbit-Converter

２つライブラリが見つかりました。

PHPならば、composerでライブラリーをインストールし、クラスを読み込みして、通すだけです。
使い方は簡単ですね。

１．文字コードを判定する。
２．変換する

```php:
$ZawgyiDetector = new ZawgyiDetector();
$Rabbit = new Rabbit();
$text = 'ミャンマーのテキスト';
$check = $ZawgyiDetector->getZawgyiProbability($text);

if($check >= 0.95){
  $newtext = $Rabbit->zg2uni($text);
}
```
こんな感じで文字コードを直してやればUNICODEで正しく表示されます。
UNICODEのミャンマーフォントは、WebフォントのUNICODE版をCSSにあてておく必要があります。


#入り口と出口で対処を
CMSなどを使う場合、データベースに入れる時、出す時それぞれか、どちらかで、このコードを入れた対応すれば、文字化けは解決するでしょう。私はデータベースに入れる時にチェックする機能を付ける方がよいと考える派です。
毎回、このロジックを走らせると、文字数次第ではレンダリングが遅くなるから。


レアな話ですが、ミャンマー関係のWebのお仕事される方は、ご参考ください。


それでは、また。
【最新版】discord.pyでbotに一定時間ごとに発言させる

# 注意
既にアナウンスされている通り、discord.pyは2021年8月下旬ころ、以降のサポートを停止する旨発表されております。
もし本記事を参考にして不具合が生じた場合、ライブラリのサポート終了による影響である可能性もあります。
詳しくは [the_future_of_dpy.md](https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1) をご覧ください。

## 概要
チャットツールDiscordにおいてbotを簡潔に記述できる、PythonのDiscordAPIラッパーである`discord.py`を利用して、botに定期的な処理を行わせる手法を解説する。

### 注意
コード例が実際には動作しないものになっておりました。2020年4月10日にコード例を修正しております。

## 前提
* discord.py 1.3.0a
* Python 3.6

### バージョンに関する注意
`discord.py`は長らく `ver0.16.12`(いわゆるasync版)が提供されてきましたが、2019年4月にv1.0.0(いわゆるrewrite版)がリリースされ、v0系はもはや使用は推奨されていません。
実際、v0系はPython3.7以上には対応しておらず今後のアップデート・メンテナンスは見込めません。
可能な限り、最新版への対応をお勧めします。
v0系での定期的な処理については、以下の記事を参考にしてください。
>[discord.pyでbotに一定時間ごとに発言させる【async版】](https://qiita.com/rareshana/items/76d9c9d0fa68ec242d13)

## 手法
'discord.py'の拡張である`tasks`を使用します。
これはbot開発でよく用いられる繰り返し実行に主眼を置いたライブラリで、
繰り返し処理を非常に簡潔に記載することができます。
[公式のドキュメント](https://discordpy.readthedocs.io/ja/latest/ext/tasks/index.html)

基本的には定期実行を行いたいメソッドの1行上に
`@tasks.loop([実行間隔])`を付け加えた上で、定期実行を開始したいタイミングで`start()`するというものです。

次の節で具体的な例を見てみましょう。

## 具体例
以下は、予め指定したIDを持つチャンネルにbot起動直後から定期実行を10秒ごとに行う例となります。

```python
from discord.ext import tasks
import discord

client = discord.Client()

channel_sent = None
"""
10秒ごとに発言するメソッドを定義している部分。
async def の1行上が定期実行を示すもので、()内で間隔を指定します。
例えば5分ごとなら(minutes=5)です。
"""
@tasks.loop(seconds=10)
async def send_message_every_10sec():
    await channel_sent.send("10秒経ったよ")

"""
今回はbotの起動直後に定期実行を開始したいので、
botの準備ができた段階で定期実行をstart()します
"""
@client.event
async def on_ready():
    global channel_sent 
    channel_sent = client.get_channel(any_channel_id)
    send_message_every_10sec.start() #定期実行するメソッドの後ろに.start()をつける


client.run("hogehogetoken")

```

これで、指定したチャンネルに10秒ごとに「10秒経ったよ」と発言させることができます。

応用すれば、指定したコマンドを受け取ったら、そのタイミングから実行することもできます。
`start()`のタイミングを変更するだけですね。


## 終わりに
[discord.pyでbotに一定時間ごとに発言させる【async版】](https://qiita.com/rareshana/items/76d9c9d0fa68ec242d13)
この記事の閲覧数が未だに増え続けていて申し訳なくなったので、ひとまず最新版への動線を作成。必ずアップデートします……。

質問があればコメントください。discord.pyはある程度使いこなせてきました。

## コード例の補足
上記のコード例は、いくつかの不具合の報告を受け数回の修正を行ったコードです。もともと記載していたコード例からかけ離れたものにならないよう修正をしているため、少し不自然な部分もあります。
特に`global`を用いて外側の変数の値を書き換えるのは、必要性がなければ避けるべきです(可読性が落ち、意図せず変数の値を書き換えてしまうこともありえます)。

例えば以下の記事を参考に、ループする関数自体が送信先チャンネルを引数に取るようにすれば、globalで書き換えずとも`start()`の引数として渡すだけで済みます。
https://qiita.com/rareshana/items/b84bec58acd48cb14118

あるいは、Cogというコマンドやイベント、タスクをひとまとめにする機能を使えば、インスタンス変数にチャンネルを格納することができるため、やはりglobal変数を使う必要はなくなります。状況に応じて適切に書き換えてください。
先入観による完結な物言い

先入観による完結な物言い
先日、パートナーさんに制作のお願いをしてきました。

うちの上司の昔からの知り合いで、けっこうザックバランに話しながらでも引き継ぎが出来ました。

まぁほぼ上司が引き継ぎをしてくれたんですがｗ

その時にそのパートナーさんが興味深い話をしてくれました。

「ヤマザキパンのカビない話って知ってる？」

って形で話が始まりました。

その言葉だけ聞くとヤマザキパンが画期的なパンを開発したんかな？って感じでしたが、言いたいところは違いました。

![tumblr_inline_n18wdeKIJx1sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/f982289c-7bee-1ffb-ec39-a99ef5810215.jpeg)


ヤマザキパンはなぜかびないか？↓

記事：http://www.foocom.net/fs/takou_old/1013/

>「市販の食パンには保存料がたくさん入っているからカビが生えない」という意見に「市販のパンには保存料は入っていません。保存料を使用しなくても工業的に無菌的な環境で製造されたパンは、数日位の日持ちは当然です」と答えたところ、「私は市販のパンは買わなくて、自分の家で食べるパンは全部家庭で作っていますが、3日も持ちません」と答えられた。そこで、「申しにくいことですが、あなたの台所がパン工場より汚いからです」と答えてその方を激怒させたことがある。

上記の文章は一部引用した内容ですがここに着目していました。

なぜパンがカビないかというと、結論をいうとヤマザキパンが独自に使用している臭素酸カリウムのおかげでカビが生えにくいというものだったのです。

その結論がでたときに、色々な方が「臭素酸カリウムって毒じゃない！あぶないわよ！」と騒いだらしいですが、厚生労働省がだしている基準値を下回るもので人体に問題はないというものであった。

それにたいして、色々な例を記事のほうであげてるので後で見てみてください。

そこでぐるっと回ってさっきの引用した文章の激怒させた内容に戻るんですが、色々な先入観で完結し、何も調べずに物言いをする人が今の世の中多いのではないか？ということを言ってました。

その記事に他の例をあげておりますが、厚生労働省がだしている臭素酸カリウムの基準値の数値はあくまで食品のみの基準値で、水道水などでいったら数値だけみたらかなりの臭素酸カリウムが入っているそうです。

でも水道水のことはなにも問題になってませんよね？

再度、言います。

色々考えずに物言いしている人多くないですか？

ボクもですがｗ

![tumblr_inline_n18xjhbspQ1sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/e07f3bb8-b8ef-af81-3406-a0697b4dbeb9.jpeg)


たしかに自分の日常のなかでも、なぁんとなく友達に言っている言葉とか話とかも、自分の中で勝手に解釈して発言してケンカになったりしていることがあったりしますｗ

内容はケンカではないですが、この前も些細なことでありました。

コンビニ行ったときにコンビニの横にあった、木の枝が折れてプランってなってコンビニの駐車場にストンって落ちてきそうになってました。

危ないなぁって思って、コンビニの定員に

「あの木危なくないですか？大丈夫ですか？」

と聞いたら

「あれは雪が積もって折れたら危ないんで折ったんです」

いやいや、折りきれよ！って思ったんですが、ここでボクの中では業務外のことかもしれないけど、それくらい気づけるんじゃね？っていう先入観の上での物言いでした。

だから若干、高圧的に言ってしまったと思います。。

でも現実問題、もう一押ししてほしいにせよ立派な考えのもとでの行動でした。

それも知らず勝手な先入観で完結し、残念な言い方をしてしまいました。

だから今では少し反省してます・・

「先入観での完結な物言い」は年をとればとるほど、増えていきこのような発言が多くなると思います。。

ここでおおきな例でいうと「これだから、ゆとりは」

言われたことありますｗｗ

がっつりゆとりなんでｗｗ

あまちゃんな考えだと「これだからゆとりは」

ミスすると「これだからゆとりは」

ネットとかでもけっこう話題になったりしますね。

今度言われたら言った本人に聞いて見ればいいんじゃないでしょうか？

あなたはミスをしないんですか？って

![tumblr_inline_n18ycumeyD1sy5s64.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540409/257dc703-6011-e072-8cbf-31f20f700afd.jpeg)


おっと、、愚痴っぽくなってしまいましたねｗｗ

このような先入観というのは完全に切り離すことはできないですが、私がしていることは単純に疑問に思ったことを言葉に出すことです。

そのフレーズを言葉で発すると、そのことについて考えるようになります。

よく企画書とか書いてる時とかもそうだと思います。

けっこうブツブツ独り言いいません？

私だけですか？ｗ

あれって集中してるし、しっかりモノコトに向き合ってるからだと思います。

知らないことに関して聞いてしまうのはいいですが、先入観で完結し自分の答えがすべて正しいという態勢で聞くのは何も聞かれた方もなんか気持ちよくないでしょう。

ただ日常でなかなか気づけないところではありますが、そこに着目して考えれるかが重要な気がします。

物事をしっかりとかみ砕き、理解する気持ちを前提としてもって物言いをすることを忘れないようにしなければならないとボクは思います。
homebrewでインストールしたgitをアップグレードしてみた

# はじめに

[Git」に複数の脆弱性、Windowsユーザーはとくに注意](https://forest.watch.impress.co.jp/docs/news/1223826.html)

というニュースを見かけました
Windowsユーザーではありませんが、脆弱性があるまま開発するのはちょっと。。。と思いまして、アップグレードしてみました

# homebrew経由でインストールしたgitをバージョンアップする

コマンドは以下でOK

```
$ brew upgrade git
```


しばらく待っていると、gitの新旧バージョンが表示されます

```
・
・
==> Upgrading 1 outdated package:
git 2.17.0 -> 2.24.1
・
・
```

アップグレードが終わった後、バージョン確認します

```
$ git --version
git version 2.24.1
```

無事にアップグレードすることができました

# 最後に
アップグレードは定期的にしましょう（自戒の念を込めて）
Raspberry PiとNoodlでデジタルサイネージを作る～Noodlのデプロイ機能～

#概要
　Noodl Advent Calendar 13日目！

※Noodlって何？という方は[こちら](https://tensorx.co.jp/noodl-jp/)が公式サイトです。

これまでにNoodlハンズオンやオンラインアーカイブなどあり、Noodlを触った方が増えてきたのではないでしょうか。

　私がNoodlを触っていて思ったのが「UI作ったけどここからどうするのか」という事です。Noodlはプロトタイピングツールなので、保守や運用の面を考えると本番の運用には不向きだそうです。（筆者は組込みソフトウェアエンジニアなのでこの辺り疎いです...）

　しかしながら、デモ展示や社内プレゼン用のプロトタイプ、趣味で利用する用途ではかなり便利なツールです！（個人的所感）

　今回はNoodlのUIをデプロイする方法について触れていきます。デプロイするとhtmlファイルとjsファイルが出力されるので、Webサーバに置いてブラウザからWebサーバにアクセスするとNoodlで作成したUIを利用できます。

#作ったもの
　Noodlのデプロイ機能を利用して**デジタルサイネージ**を作ってみました！
　巷で流行っているRaspberry PiにLCDキットを組んでサイネージ化しています。
【気象情報を表示】
![20191114_083533.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/6bb1e920-17e5-d824-abe8-bbca10f5b77a.jpeg)

【自宅にあるアクアリウムの状態表示】
※IoTつくるよ２に展示した際の写真です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/0bc4f006-4b92-28aa-3194-f13223b04ad5.png)

# 技術解説
　今回はNood Advent CalenderということでNoodlのデプロイ機能について触れます。Noodlのエディタ画面右上にボタンが羅列されています。その中の「丸に↑」のアイコンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/01932565-cb0e-3c6e-22c1-98dfd111fd26.png)

　すると、次のように「デプロイますか？」というメッセージが表示されるので、「PICK FOLDER」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/50287720-14e8-3cf4-8ad6-8ef9f3d9fa14.png)

　デプロイ先のフォルダを選択してデプロイが成功すると、次のようにhtmlファイルやjsファイルが出力されます。私の場合、文字フォント（.ttf）や画像（.jpg）をプロジェクトに読み込んで利用しているので、そのファイルも出力されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/504c06c8-23b6-ebdc-29e7-c6bde7486a14.png)

　この出力されたファイル達をWebサーバに配置すればOKです！

#おわりに
　最近、Noodlのオンラインハンズオン開催されたので気になる方は[こちら](https://www.youtube.com/channel/UCUPtEr3W0BwFKxzhX-CL4ww)をチェックしてみてください。
 デジタルサイネージ化についてもっと詳しく知りたいという方はこちらをご参照ください。
https://kmaepu.booth.pm/items/1726666
kintoneの技術書籍を調べてみる

kintoneを題材にした技術同人誌には、どのような種類があるのか気になったのでまとめてみました。参考にしたサイトは一番下に記載しています。


- 技術同人誌、購入者特典誌、冊子の3種類がある
- 有料と無料がある
- 入手経路はイベントなど

※入手経路、価格、技術同人誌種類は、本記事の執筆時点の情報です。


## 費用
有料（1冊200円）と無料がある。

## 入手経路
- 技術者の集まる物販可能なイベント（技術書典、オープンソースカンファレンスなど）
- Cybozu Daysやkintone hiveなどのサイボウズ主催のイベント

## kintone技術同人誌 
一部抜粋ですが、このような技術同人誌がある。
![](https://developer.cybozu.io/hc/article_attachments/360026046751/s____________.png)

- [きんとーん・らぼ](https://developer.cybozu.io/hc/ja/articles/115003505983) 「kintoneを題材にした技術同人誌を企画、制作、販売している団体」が発行
- 有料 200円

## 購入者特典誌

**kintoneちゅーとりある**
![](https://developer.cybozu.io/hc/article_attachments/360026048612/s_vol1__.png)

- [きんとーん・らぼ](https://developer.cybozu.io/hc/ja/articles/115003505983) が発行
- 配布条件は、有料の技術同人誌を購入の際

## 冊子

**地味にいいカスタマイズ実例集**
<img src='https://pbs.twimg.com/media/EAYtyosVAAAgvx6.jpg' width='30%'>


- イベントでの限定配布、費用は0円のよう


## 参考
- [きんとーん・らぼ](https://developer.cybozu.io/hc/ja/articles/115003505983)
- [kintone技術同人誌を作ってみた](https://kintone-blog.cybozu.co.jp/developer/000325.html)
- [cybozu developer networkの1年を振り返る 2019](https://qiita.com/yukaffy/items/ebf1eb72e1d9e62f0fb3)

サイボウズ商店での販売の様子
![](https://kintone-blog.cybozu.co.jp/2S0A5069.jpg)