もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

久々にnuxt入門した #01

はじめに

こんばんは。 結構前に本を見ながら nuxtをやってみたのですが、もう完全に忘れてるのと、6月から別案件で使用する可能性があるので再度入門しようとしてみます。

今回は最初なのでインストールとかもろもろをやってみます。

本題

1. インストール

$ npx create-nuxt-app example01

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project in example01
? Project name example01
? Project description My world-class Nuxt.js project
? Author name kojirock
? Choose programming language JavaScript
? Choose the package manager Npm
? Choose UI framework Tailwind CSS
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint, Prettier, Lint staged files, StyleLint
? Choose test framework Jest
? Choose rendering mode Single Page App
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert s

以前の案件で他のエンジニアさんがおすすめしていた tailwind cssを選択して、Eslint, Prettier, Lint staged files, StyleLintもいれてみました。

TSは今回使わずに行きます。

自分のレポジトリにアップしようと思ったら、 eslintでエラー出ました ...

$ npm run lint                

> example01@1.0.0 lint /Users/kojirock/projects/study/nuxt/example01
> eslint --ext .js,.vue --ignore-path .gitignore .


/Users/kojirock/projects/study/nuxt/example01/tailwind.config.js
  2:1  error  Insert `·`  prettier/prettier
  3:1  error  Insert `·`  prettier/prettier
  4:1  error  Insert `·`  prettier/prettier
  5:1  error  Insert `·`  prettier/prettier
  6:1  error  Insert `·`  prettier/prettier

✖ 5 problems (5 errors, 0 warnings)
  5 errors and 0 warnings potentially fixable with the `--fix` option.

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! example01@1.0.0 lint: `eslint --ext .js,.vue --ignore-path .gitignore .`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the example01@1.0.0 lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/kojirock/.npm/_logs/2020-05-22T21_55_17_781Z-debug.log

tailwind.confg.jsでエラってるみたいです。 本来このファイルは特にlinterチェックする必要なさそうだったので、 .eslintrc.jsignorePatterns に追加したらうまくいきました。

pushしたので、いったんnetlifyにあげてみます。

f:id:kojirooooocks:20200523075427p:plain

f:id:kojirooooocks:20200523075442p:plain

とりあえず出来ました。

後の変更点としては、ローカルで立ち上げようとすると、デフォルトで立ち上がる ポート 3000番が別のもので使用中だったので、ポート番号を変更してます。

"dev": "nuxt -p 9998",

1. trelloっぽいの作ってみる

trelloっぽいサイトを作ってみます。

<template>
  <div class="bg-blue-100">
    <h1 class="text-2xl">Todo List</h1>

    <div class="flex">
      <div class="flex-1 bg-gray-100 m-2">
        <p class="text-xl">todo</p>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card1
        </div>
      </div>

      <div class="flex-1 bg-gray-100 m-2">
        <p class="text-xl">doing</p>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card1
        </div>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card2
        </div>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card3
        </div>
      </div>

      <div class="flex-1 bg-gray-100 m-2">
        <p class="text-xl">done</p>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card1
        </div>
        <div class="z-10 flex-auto bg-white m-2 p-3 shadow">
          card2
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  components: {}
}
</script>

<style></style>

f:id:kojirooooocks:20200523075509p:plain

tailwindのおかげで、自分的にcssの障壁がかなり低くなりました。

おわりに

nuxtである必要があまりないかも...w

まぁ一旦trelloぽいの作った後、次はまたページ数増やした何かを作るみたいな感じでステップアップしようと思います。

という感じで、一旦今回はここまで。

次はカードの追加と、カードの移動とかやってみます。

zengin-codeで銀行名・支店名を自動で取得

はじめに

こんばんは。

今回も作業でやった簡単な備忘録です。

以下のライブラリで銀行コード・支店コードから 銀行名支店名を自動で取得するようなことをしました。

github.com

本題

html

<div>
    <div>
        <p>銀行コード</p>
        <input type="text" name="bank_code">
    </div>
    <div>
        <p>銀行名</p>
        <input type="text" name="bank_name" readonly>
    </div>
    <div>
        <p>支店コード</p>
        <input type="text" name="branch_code">
    </div>
    <div>
        <p>支店名</p>
        <input type="text" name="branch_name" readonly>
    </div>
</div>

js

import $ from 'jquery';
const zenginCode = require('zengin-code');

$(function() {
  // 銀行コードの入力イベント
  $("input[name='bank_code']").on('input', function() {
    $("input[name='bank_name']").val('');
    const bankCode = $(this).val();

    if (zenginCode[bankCode] !== undefined) {
      // 銀行データがあれば銀行名をセットする
      $("input[name='bank_name']").val(zenginCode[bankCode]['name']);
      $("input[name='branch_code']").val('');
      $("input[name='branch_name']").val('');
    }
  });

  // 支店コードの入力イベント
  $("input[name='branch_code']").on('input', function() {
    $("input[name='branch_name']").val('');
    const bankCode = $("input[name='bank_code']").val();
    const branchCode = $(this).val();

    if (zenginCode[bankCode] !== undefined && zenginCode[bankCode]['branches'][branchCode] !== undefined) {
      // 支店データがあれば支店名をセットする
      $("input[name='branch_name']").val(zenginCode[bankCode]['branches'][branchCode]['name']);
    }
  });
})

この感じで、銀行コード・支店コードを入力すると、銀行名・支店名が自動で表示されるようになります。

ただ、このライブラリの欠点としては、信用金庫などは信金などに略されているので、そのあたり正確に欲しい場合は泥臭いコードを書く必要があります。

おわりに

今週死ぬほど忙しくて、雑なブログになってしまいました。。。

MicroCMSためした

はじめに

こんばんは。

今回は存在は知っていたけど、使ったことなかったヘッドレスCMSの MicroCMSを試してみました。

会員登録を先程終わらせたバリバリ初心者です。

f:id:kojirooooocks:20200503040158p:plain

ではやっていきます。

本題

とりあえずAPIのURLを登録して、早速テスト用のAPIを作成します。

f:id:kojirooooocks:20200503041529p:plain

今回は お知らせ の情報のみをもらう、リスト形式のAPIとして作成します。

f:id:kojirooooocks:20200503041649p:plain

APIスキーマはこんな感じで、ほぼほぼ placeholderに出てきた内容そのままです。

f:id:kojirooooocks:20200503041754p:plain

各項目の詳細設定で、必須にするなどの選択もできます。

f:id:kojirooooocks:20200503041840p:plain

実際に登録すると、サイドバーにコンテンツとして、登録したAPIが作成されて、そこに遷移すると、スキーマとして登録した状態で、入力フィールドが生成されてます。こんな感じで登録してみました。

f:id:kojirooooocks:20200503042014p:plain

今回はわかりやすく、コンテンツIDもわかりやすいものに変更しておきます。

f:id:kojirooooocks:20200503042131p:plain

これで公開することで、実際に登録した情報がAPIを実行することで取得できます。

今回は以下の通り、2個のお知らせを登録しました。

f:id:kojirooooocks:20200503043859p:plain

ちなみにAPIリファレンスで実際に叩くAPIや、サポートされているクエリなども確認できます。

f:id:kojirooooocks:20200503043249p:plain

ちょっとやってみます。

ちょうど前回の記事で落としてきたCakePHPのプロジェクトがあるのでそれで試してみます。

src/Controller/ExampleController.php
<?php
namespace App\Controller;

class ExampleController extends AppController
{
    public function index()
    {
        $url = 'https://********.microcms.io/api/v1/news';
        $http = new Client();
        $response = $http->get($url, [], [
            'headers' => [
                'X-API-KEY' => '******************************'
            ]
        ]);

        $newsData = json_decode($response->getStringBody(), true);
dd($newsData);die();
    }
}

f:id:kojirooooocks:20200503044917p:plain

テンプレートで表示するとこんな感じ?

<div>
    <p>こんにちは!</p>
    <p>CakePHPのテンプレートですよ!</p>
</div>

<div>
    <?php foreach ($newsData['contents'] as $newsDatum): ?>
        <div>
            <?php echo $newsDatum['title']; ?><br />
            <?php echo $newsDatum['body']; ?><br />
            <?php echo $newsDatum['publishedAt']; ?>
        </div>
    <?php endforeach; ?>
</div>

Image from Gyazo

リンクも画像も問題ないですね。

こんな感じで、簡単でした。

終わりに

流行るのわかりますね。 すげー簡単。

wordpressで頑張るよりは、遥かに使いやすい感じだと思いました。

現場からは以上です。

CakePHP4で非推奨となる File and Folderを推奨されている書き方で置き換えてみた

はじめに

こんばんは。

最近CakePHPの案件に携わっていて、CakePHPのドキュメントとか結構見たりしているのですが、以下のページでこんな文言を見つけました。

book.cakephp.org

Deprecated since version 4.0: The File and Folder classes will be removed in 5.0. Use SPL classes like SplFileInfo or SplFileObject and iterator classes like RecursiveDirectoryIterator, RecursiveRegexIterator etc. instead.

どうやら File クラスや Folder クラスが4系から非推奨になり、5系で廃止されるようです。

正直僕どちらも使った経験がなかったので、便利かどうかも知らないのですが、今携わっているCakePHP3の案件でも使われているようなので、この FileFolder を、推奨されているものを使用して書き換えてみます。

本題

step1

まず、そもそも File クラス、 Folder クラスがどんなもんかがわかってないので、それを調べます。

ドキュメントどおりに進めて、対象フォルダ内にある対象ファイルを調べてみます。

実装
<?php
require dirname(__DIR__) . '/vendor/autoload.php';

use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

$dir = new Folder('../src/Controller');
$files = $dir->find('.*\.php');
var_dump($files);
結果
$ php file_and_folder_test.php 
array(3) {
  [0]=>
  string(19) "ErrorController.php"
  [1]=>
  string(17) "AppController.php"
  [2]=>
  string(19) "PagesController.php"
}

他にもフォルダ作ったりできる感じです。

実装
<?php
require dirname(__DIR__) . '/vendor/autoload.php';

use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

$dir = new Folder('../src/Controller');
$dir->create(date('Ymd'));
結果
$ php file_and_folder_test.php
$ ls -l ../src/Controller
total 24
drwxr-xr-x  2 kojirock  staff    64  5  3 02:27 20200502/  ← 出来てる
-rw-r--r--  1 kojirock  staff  1595  6 27  2019 AppController.php
drwxr-xr-x  3 kojirock  staff    96  6 27  2019 Component/
-rw-r--r--  1 kojirock  staff  1731  6 27  2019 ErrorController.php
-rw-r--r--  1 kojirock  staff  2152  6 27  2019 PagesController.php

Fileに関しては、こんな感じでファイルサイズ貰ったり、最終更新日貰ったり出来ます。

実装
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

$dir = new Folder('../src/Controller');
$fileNames = $dir->find('.*\.php');
foreach ($fileNames as $fileName) {
    $file = new File($dir->pwd() . '/' . $fileName);
    var_dump("{$fileName} サイズ: {$file->size()}, 最終更新日: {$file->lastChange()}");
    $file->close();
}
結果
$ php file_and_folder_test.php
string(64) "ErrorController.php サイズ: 1731, 最終更新日: 1561602647"
string(62) "AppController.php サイズ: 1595, 最終更新日: 1561602647"
string(64) "PagesController.php サイズ: 2152, 最終更新日: 1561602647"

他にもファイル操作・フォルダ操作を扱うための標準機能や便利機能があります。

だいたい理解できました。

次は、4以降から推奨されているやり方でこれを置き換えてみます。

step2

step1でやった、対象フォルダ内にある対象ファイルを調べる ことをやってみます。

実装
<?php                                                                                                                          

$directoryIterator = new RecursiveDirectoryIterator('../src/Controller', FilesystemIterator::SKIP_DOTS);                       
$files = new RecursiveIteratorIterator(new RecursiveRegexIterator($directoryIterator, '/.*\.php/'));                           
foreach ($files as $fileInfo) {                                                                                                
    var_dump($fileInfo->getFilename());                                                                                          
}
結果
$ php spl_test.php
string(19) "ErrorController.php"
string(17) "AppController.php"
string(19) "PagesController.php"

ファイル情報の取得です。これは RecursiveIteratorIterator が SplFileInfo返してるので、簡単です。

実装
<?php
$directoryIterator = new RecursiveDirectoryIterator('../src/Controller', FilesystemIterator::SKIP_DOTS);                       
$files = new RecursiveIteratorIterator(new RecursiveRegexIterator($directoryIterator, '/.*\.php/'));                           
foreach ($files as $fileInfo) {                                                                                                
    var_dump("{$fileInfo->getFilename()} サイズ: {$fileInfo->getSize()}, 最終更新日: {$fileInfo->getMTime()}");
} 
結果
$ php spl_test.php
string(64) "ErrorController.php サイズ: 1731, 最終更新日: 1561602647"
string(62) "AppController.php サイズ: 1595, 最終更新日: 1561602647"
string(64) "PagesController.php サイズ: 2152, 最終更新日: 1561602647"

こんな感じです。

終わりに

簡単なので、相当難しい使い方していない限りは簡単に置換可能かなーと思っています。

とりあえず、現場からは以上です。

CakephpのbuildRules:existsInの条件を緩和する

はじめに

こんばんは。

今回は今の案件で少しハマったことを記録しておきます。

CakephpのbuildRulesでexistsInがあるとおもいます。

FOREIGN KEYを貼っているか、決められた命名カラム名を作ると、bakeしたときに対象先のテーブルにデータがあることを調べてくれるルールを作ってくれます。

便利なのは、FOREIGN KEY貼ってないような、tableの成約が緩めのテーブルに関しても、そのルールを書くことで不正データの混入を阻止してくれます。

ただ、 基本的には管理者テーブルのIDが入るんだけど、0の場合は、バッチが追加したデータとして追加したいんだよね〜 みたいなことがよくあります。

そのさい、 existsInのルールを外してしまうと、関連データ以外もどんどん入ってしまいます。

0または、対象先テーブルのIDの値しか許可しない というようなルールをかければ一番です。

やり方調べてみるとやっぱりありました。

やってみた

<?php

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules): \Cake\ORM\RulesChecker
    {
        $rules->add(static function (Summary $entity, array $options) {
            $rule = new ExistsIn(['admin_id'], 'AdminUsers');

            return $entity->admin_id === 0 || $rule($entity, $options);
        }, '_existsInOrZero', ['errorField' => 'admin_id', 'message' => '0を設定するか、存在するadmin_idを入力してください']);

        return $rules;
    }

終わりに

今週は仕事忙しすぎて、勉強できてないですが、来週からは少し楽になるので、溜まってる本読んでいこうと思います!

cakephpのtestをもっとやりやすく

はじめに

こんばんは。

簡単な記事ですが、今回やった対応をブログにします。

現在cakephp3の案件をお手伝いしています。

今回はcakephpで使用する testとfixtureについてです。

cakephpのfixtureファイルは以下のような感じで、table構造と テストデータを一緒に記述できます。

<?php
namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

class UsersFixture extends TestFixture
{
    public $import = ['table' => 'users'];

    public $records = [
        [
            'id' => 1,
            'name' => 'kojirock',
            'email' => 'kojikoji@example.com',
        ], [
            'id' => 2,
            'name' => 'kojirock2',
            'email' => 'kojikoji2@example.com',
        ], [
            'id' => 3,
            'name' => 'kojirock3',
            'email' => 'kojikoji3@example.com',
        ],
    ];
}

そして、testcaseでfixtureを使用したい場合以下のように記述します。

<?php
namespace App\Test\TestCase\Repository;

use App\Repository\UsersRepository;
use Cake\TestSuite\TestCase;

class UsersRepositoryTest extends TestCase
{
    public $fixtures = [
        'app.users',
    ];

    /**
     * @test
     */
    public function findUsers()
    {
        $results = (new UsersRepository(
            $this->getTableLocator()->get('Users'),
        ))->findUsers();

        $this->assertCount(3, $results);
    }
}

これはこれで便利なのですが、問題があります。

あまりほかから参照されない tableに関しては良いのですが、いろんな場所から参照されている tableだと、この fixtureデータのテストデータがどんどん膨れ上がっていきます。

例えばこの UsersRepositoryで Userの友達情報も取るようなメソッドを作った場合、 UserFriendFixture 的なfixtureができ、そのテストデータレコードも膨れていきます。

また、 testケースの $fixtures に設定した段階でテストデータが入ってしまうので、本来テストしたいデータ のみ使いたいのですが、この形ではそれはかないません。

そして、testケース毎に truncate insertを繰り返すので、大量のfixtureを使用する & テストケースが多い場合、テストの実行時間がどんどん伸びていってしまいます。

更にやっかいなのが、大量なfixtureデータになりそのfixtureに依存するテストケースが増えてきた場合、fixtureデータを少し修正するだけで、大量のテストがコケる可能性があります。

これをなんとか解決したいと思い、色々調べていると、似たようなことを考えている人がいました。

qiita.com

autoFixtures = false にすることで、テストデータを入れるタイミングをずらせるようです。

ただし、これだけだと、すでに大量のテストデータをfixtureに登録している場合、loadFixtureした段階でドンと不要なテストデータが登録されます。

これを解決するためには、 Fixtureデータは以下のようにテーブルを作成するまでにとどめることが大事だと思いました。

<?php
namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

class UsersFixture extends TestFixture
{
    public $import = ['table' => 'users'];
}

そして、テストデータは以下のように、各テストケース毎に愚直に入れていきます。

<?php
namespace App\Test\TestCase\Repository;

use App\Repository\UsersRepository;
use Cake\TestSuite\TestCase;

class UsersRepositoryTest extends TestCase
{
    public $fixtures = [
        'app.users',
    ];

    public $autoFixtures = false;

    public function setUp()
    {
        $this->loadFixtures('Users');
        parent::setUp();
    }

    /**
     * @test
     */
    public function findUsers()
    {
        $testDatum = [
            'name' => 'kojirock',
            'email' => 'kojikoji@example.com',   
        ];

        $table = $this->getTableLocator()->get('Users')
        $table->save($table->newEntity($testDatum));

        $results = (new UsersRepository(
            $this->getTableLocator()->get('Users'),
        ))->findUsers();

        $this->assertCount(1, $results);
    }
}

すこし骨が折れますが、これで各テスト毎に独立したデータが作れ、かつ、テストをしたいデータのみを考えるだけで良くなります。

これ以外の対策として、今回のテストケース用のfixtureを別で用意するという対応もあります。

ただ、今回はこれは使いませんでした。

fixtureはtable作成に留めるべき という設計で統一したかったからが理由です。

テストケース毎にfixtureを用意する手間も大変そう...というのも理由の一つです。

終わりに

余談ですが save() を使ってると ruresや単一トランザクションの処理が入り、テストデータ挿入で無駄なsqlが流れてしまいます。 第2引数を指定すればOKですが、それも毎回はめんどくさいです。

というわけで自分は以下みたいな traitを用意して対応してます。

<?php
namespace App\Test\TestCase;

use Cake\Datasource\EntityInterface;
use Cake\ORM\Table;

trait TestDataSaveTrait
{
    /**
     * @param Table $table
     * @param array $testDatum
     * @return EntityInterface
     */
    private function save(Table $table, array $testDatum): EntityInterface
    {
        $entityOption = ['validate' => false];
        $saveOption = ['checkRules' => false, 'atomic' => false];

        return $table->save($table->newEntity($testDatum, $entityOption), $saveOption);
    }

    /**
     * @param Table $table
     * @param array $testData
     * @return EntityInterface[]
     * @throws \Exception
     */
    private function saveMany(Table $table, array $testData): array
    {
        $entityOption = ['validate' => false];
        $saveOption = ['checkRules' => false];

        return $table->saveMany($table->newEntities($testData, $entityOption), $saveOption);
    }
}

あくまでテストデータを挿入するためだけに使うものなので、validationとかは必要ないかなと思い、これを使うようにしました。

現場からは以上です。

Mysql8系のユーザー作成

はじめに

こんばんは。

完全な備忘録です。

環境構築する際に間違いなく、一からググっているので、備忘録のため残しておきます。

本題

1. ユーザー作成

現状のphpmysqlライブラリがmysql8系デフォルト認証方式の caching_sha2_password に対応していないので、必ずmysql_native_password の認証プラグインを指定する

CREATE USER 'ユーザー名'@'ホスト名' IDENTIFIED WITH mysql_native_password BY 'パスワード';

2. 権限設定

① データベースの全体制御

GRANT ALL PRIVILEGES ON データベース名.* TO 'ユーザー名'@'ホスト名';

② 参照のみ

GRANT SELECT ON データベース名.* TO 'ユーザー名'@'ホスト名';

③ テーブル単位での制御

GRANT ALL ON データベース名.テーブル名 TO 'ユーザー名'@'ホスト名';

3. 確認

① DBの権限設定

mysql> select * from mysql.db\G
*************************** 1. row ***************************
                 Host: localhost
                   Db: example_db
                 User: kojirock
          Select_priv: Y
          Insert_priv: Y
          Update_priv: Y
          Delete_priv: Y
          Create_priv: Y
            Drop_priv: Y
           Grant_priv: N
      References_priv: Y
           Index_priv: Y
           Alter_priv: Y
Create_tmp_table_priv: Y
     Lock_tables_priv: Y
     Create_view_priv: Y
       Show_view_priv: Y
  Create_routine_priv: Y
   Alter_routine_priv: Y
         Execute_priv: Y
           Event_priv: Y
         Trigger_priv: Y
*************************** 2. row ***************************
                 Host: localhost
                   Db: example_db
                 User: select_user
          Select_priv: Y
          Insert_priv: N
          Update_priv: N
          Delete_priv: N
          Create_priv: N
            Drop_priv: N
           Grant_priv: N
      References_priv: N
           Index_priv: N
           Alter_priv: N
Create_tmp_table_priv: N
     Lock_tables_priv: N
     Create_view_priv: N
       Show_view_priv: N
  Create_routine_priv: N
   Alter_routine_priv: N
         Execute_priv: N
           Event_priv: N
         Trigger_priv: N
2 rows in set (0.00 sec)

② テーブルの権限設定

mysql> select * from tables_priv\G
*************************** 1. row ***************************
       Host: localhost
         Db: example_db
       User: payment_user
 Table_name: payment_logs
    Grantor: root@localhost
  Timestamp: 0000-00-00 00:00:00
 Table_priv: Select,Insert,Update,Delete,Create,Drop,References,Index,Alter,Create View,Show view,Trigger
Column_priv: 

終わりに

自分がよく使う権限設定をまとめました。

まじで毎回設定するたびにググってるんで、覚えないと...

現場からは以上です。