In my head.

感想と考え事とメモ

カスタムプロパティにSassの変数使うときは#{}つける

タイトル通りだけど、カスタムプロパティにSassの変数使うときは#{}つける。
つけないとおかしなことになる。

Sassの変数適用される書き方:

$common-text-color: #6e6e6e;
--button-text-color: #{$common-text-color};

.button {
  color: var(--button-text-color);
}.button {
  color: #6e6e6e;
}

#6e6e6eが出力される。

Sassの変数適用されない書き方:

$common-text-color: #6e6e6e;
--button-text-color: $common-text-color;

.button {
  color: var(--button-text-color);
}.button {
  color: $common-text-color;
}

$common-text-colorという文字列が出力される。

以前は#{}なくても大丈夫だったのが、Sass・LibSass・node-sassの変更に伴って、Sassの変数がコンパイルされなくなったみたい。
(英語力低いから解釈不安)

Assigning SASS variables to CSS Variables (Custom Properties) no longer works

Laravelで外部キー制約があるテーブルデータを削除したい

結論

2つ方法があります。

前提

テーブル構成

例えば下記のようなテーブルがあったとします。

  • teamsテーブルがあり、それに紐づく形でmembersテーブルがある(親:teams 子:members)
  • membersteam_idteamsidを参照している外部キー
  • fk__team_id__members_idは外部キーの制約名

f:id:smspring0426:20190323195117p:plain

したいこと

teamsテーブルのデータを削除する。とにかく削除ができればいい。

書いたこと

TeamsController.php

 ...略
 public function deleteTeam($id) {
   $this->teams->deleteTeam($id);
 }

TeamsService.php

 ...略
 public function deleteTeam($id){
   Teams::where('id', '=', $id)->delete();
 }

これでいけるだろって思ってました。

実行結果

エラー

Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`DB名`.`teams`, CONSTRAINT `外部キー名` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`)) (SQL: delete from `DB名`.`` where `id` = xxx)

なんか外部キーが問題で削除できないと。えぇ.....
(この時は外部キー設定されてたことと、その制約内容知らなかった)

原因

実はこのテーブル、こうなっていました。

f:id:smspring0426:20190323195950p:plain

fk__team_id__members_idの制約内容がON DELETE RESTRICT
テーブルの参照整合性を保つために、親テーブルの削除ができないようになっていたんですね。

補足

RESTRICTは下記の場合に設定されます。

  • ON DELETE RESTRICTと明示的にRESTRICTを設定した時
  • ON DELETE NO ACTIONと明示的にNO ACTIONを設定した時
  • ON DELETEを明示的に何も設定しなかった時

設定した覚えがないのにRESTRICTになっているのは、そういう仕様だからみたいですね。

対処法

マイグレーションでDBスキーマを変更

方法

問題のfk__team_id__members_idの設定を変えればOK。

f:id:smspring0426:20190323200458p:plain

ON DELETE RESTRICTON DELETE CASCADEに変えることで、親テーブルの削除に合わせて子テーブルも削除されます。

では、具体的にどうするか。順序としては下記の通りです。

  1. 既存の外部キー制約削除のマイグレーションを生成
  2. 新しい外部キー制約追加のマイグレーションを生成
  3. マイグレーション実行

1. 既存の外部キー制約削除のマイグレーションを生成する

下記のコマンドを流します。
$ php artisan make:migration 生成するファイル名

そうすると、日付_生成するファイル名.phpdatabase/migrationsに作成されます。

生成された日付_生成するファイル名.phpを下記のように編集します。

 ...略
 class 生成するファイル名(←すでに記述されている) extends Migration
 {
   /**
   * Run the migrations.
   *
   * @return void
   */
   public function up()
   {
     Schema::table('members', function (Blueprint $table) {
       $table->dropForeign('fk__team_id__members_id');
     });
   }
 }

これで既存の外部キーfk__team_id__members_idを削除します。

2. 新しい外部キー制約追加のマイグレーションを生成

もう一度下記のコマンドを流します。
$ php artisan make:migration 生成するファイル名2

そうすると、1.のように日付_生成するファイル名2.phpdatabase/migrationsに作成されます。

生成された日付_生成するファイル名2.phpを下記のように編集します。

 ...略
 class 生成するファイル名2 extends Migration
 {
   /**
   * Run the migrations.
   *
   * @return void
   */
   public function up()
   {
     DB::statement(
       'ALTER TABLE members ADD CONSTRAINT 新しい外部キー名
        FOREIGN KEY (team_id) REFERENCES teams (id)
        ON DELETE CASCADE ON APDATE CASCADE'
     );
   }
 }

これで新しい外部キーを作り、その外部キーに対して削除時の挙動を明示します。

補足

さりげなく書いているON APDATEは更新時の挙動を指定する句です。

ON DELETE CASCADEと同じように、ON UPDATE CASCADEは親テーブルの更新に合わせて子テーブルも更新されます。

3. マイグレーション実行

下記のコマンドでマイグレーションを実行します。
$ php artisan migrate

これでOKです。

マイグレーションは生成されたファイル順で実行されます。
なので、既存の外部キー削除→新しい外部キー作成の順で生成しましょう。
順序逆だとエラーになると思います。(試してないですが)

メリット/デメリット

2つ方法を紹介しているので、各方法のメリット・デメリットを一応書きます。
あくまで私が感じたことですが・・・。

○ 削除実行部分をスマートに記述できる
× スキーマ変更で何か不具合が起きる可能性ある

親テーブルデータ削除前に子テーブルデータを削除

方法

親テーブルに紐づいている子テーブルのデータを先に削除してから親テーブルを削除するだけです。

こんな感じのメソッド作って、
MembersService.php

 ...略
 public function deleteMemberByTeamId($teamId)
 {
   Members::where('team_id', '=', $teamId)->delete();
 }

teams削除の記述前に追加。
TeamsController.php

  ...略
  public function deleteTeam($id) {
    $this->members->deleteMemberByTeamId($id);
    $this->teams->deleteTeam($id);
  }

メリット/デメリット

これも主観です。

スキーマ変更する必要ない
× 親テーブルに紐づく子テーブルが複数ある場合、全テーブル書く必要があるので冗長
× なんかかっこ悪い

余談

  • 2つ紹介しましたが、とりあえず上手くいった方法です。これでもできたけどなんか冗長な気もするから、多分もっとスマートで正しい書き方あるんだろうな。Laravelかじりたてなので許してください。むしろ正しい方法あったらぜひ教えてください。
  • controllerとserviceで定義するメソッド名って同じでいいのかな。よくわかんない。
  • なんとなんと、laravel5.1です!(古くてすみません。)まあ今のバージョンでもきっと同じエラー起こると思うから・・・。

参考

これはただのぼやき

今日でエンジニア歴半年になった。わーい。

 

ぱっと思いつく、成長したなと思うこと

 

・技術力

色んなフレームワーク扱えるになった。 JavaScript系がメイン。

Node.js、Nuxt.js、Vue.js

ららべるもちょっとだけわかる。

webpackもだいぶわかるようになった。あいつだいぶ難しいな・・・。

 

・綺麗なコードを書く意識

コメントがいらないくらいわかりやすく をいつも意識するようになった。

前書いてたコード見返すと、ちょっと恥ずかしい気持ちになるから多分ちょっとは養われた。

 

・英語ドキュメントへの許容感

前は日本語で解説してるやつを漁りまくって英語を敬遠してたけど、今では当然かのようにふつーに読めるようになった。

ただたまに言ってる意味がピンとこなくてイライラする。笑

 

ぱっと思いつく、これから成長したいなと思うこと

 

デバッグ

エラーや想定通りに進まない原因を解明する時間、もっと短縮する。

 

・検証力

例えば技術を採択する時、色んな視点からメリット・デメリットを見つけられるようにする。

あと、自分がわかってる部分とわからない部分もハッキリさせる検証力もつけたい。

 

・先を見据える力

目の前のゴールだけを見るんじゃなくて、ゴールの先には何があるかを想定する習慣をつける。

 

・楽する方法を探す習慣

どうしても技術に頼りがちアンサーを出しがちなので、技術に頼らずいかに楽に、いかにスピーディーに物事を解決できる方法がないか模索する。

 

 

技術力も高めたいけど、それはまじめに仕事なり勉強なりしてたら自然に身につくと思うんですよ。実際そうだし。

だからそれより、仕事の進め方的なところを改善させたい。

 

 

がんばろー。

 

酔った頭で書いてるから今度編集しなおすかも。

 

 

DockerやKubernetesが学べるサイト教えてもらった、そんで使ってみた

今日の内容

この前Kubernetesの勉強会参加した。
参加した理由は、会社でKubernetes利用しているから。
私はインフラ担当ではないと思っているけど、
状況によっては自分がyaml書いてなんやかんやする必要が私にはあると感じてるし、何より頼ってばっかりは嫌だからなんでも知りたくなっちゃうんだ。
(ただどの分野においても浅い知識しか持ってないエンジニアになる危険性が高いけどw)

んでその勉強会で教えてもらったのが、Katacoda

英語だけど、DockerやKubernetesが無料で学べるっぽい。
私Dockerすら曖昧な理解しかしてないから、無料で使えるんだとしたらとてもありがたいなー。

ということで、触ってみました。

結論から先に言うと

え、めっちゃいいじゃん・・・。と思いました。笑

英語だけどそんな難しい英単語で説明されているわけじゃないし、
ただ英語のドキュメント読んで概念とかやり方理解するよりも
ずっとわかりやすいなって感じました。

多分その理由が、コースの進め方にあるのかな。
一つひとつのコース(Katacoda風にいうとシナリオ)が物語形式なんですよ。
ある人物を想定した上で、課題とか解説を進めていくような。
だから、こういう状況の時にはこのコマンドを入力すればいいのかーって理解が深まる。

Katacodaのコースの流れをまとめてみたので気になる方は以下ご覧ください。

Katacoda触ってみたよ

会員登録必要ですが、FacebookTwitterのアカウントでログインできるのでTwitterのアカウントでログイン。

コース一覧どんな感じかな。

f:id:smspring0426:20180826153506p:plain
f:id:smspring0426:20180826153754p:plain

他にもまだまだあった。
とりあえずDockerの勉強したいので、1番上にあるDocker&Containersを見てみる。

見てみると、Docker&Containersコースの中にも複数勉強シナリオがあるみたい。

f:id:smspring0426:20180826154221p:plain f:id:smspring0426:20180826154237p:plain

色んなパターンあるのいいねー。
とりあえず初回だし、1番上のDeployng Your First Docker Containerのシナリオを選択してみる。

f:id:smspring0426:20180826155025p:plain

難易度は初心者レベルで、目安10分ぐらいでこのシナリオがクリアできるっぽい。
んでシナリオシナリオって何のことかと思ってたけど、ある人物の状況を想定した上で(?)、勉強を進めていくみたいなのかな。

今回の例でいうと、ジェーンという開発者がいて、その人はアプリケーション内でKey-Valueストアを使いたい。
だからKey-Valueストアとして有名なRedisを使おうと決めたけど、導入方法いまいちわからん。
ただDockerがそういうサービスデプロイするのに便利って聞いたことがあるから使ってみよ。的なシナリオかな。

進めてみましょう。

f:id:smspring0426:20180826160444p:plain

左にTaskとDockerの解説、右に入力できる画面。
シナリオといってるだけあって、物語を読んでいるかのようにDockerの解説が進められていく感じ。
今回の課題は、RedisのDockerイメージ探してみて、latestバージョンをrunしましょうというもの。

f:id:smspring0426:20180826161302p:plain

入力した感じ、ちょっと文字の反映が遅いかもって思うけど、そんな不満じゃない。
Taskをクリアしてなくても、CONTINUEクリックしたら先に進めるみたい(?)
あと正しい答え(?)を入力しても、よくあるようなSuccess!的な表示はなく、コマンドラインに実行結果が表示される。
答えがわからなくてもSHOW SOLUTIONで答え確認できるから安心。

f:id:smspring0426:20180826163346p:plain

オプションとか、割り当てられたポートがわからなければこのコマンドでわかるよ的な例もあるから、わかりやすいしタメになる。ありがたすぎる。

f:id:smspring0426:20180826164837p:plain

初めてDocker扱うことになった時、
コンテナ消してもデータ残しておけるようにボリュームをマウントしておくっていう概念もやり方もわからなくて泣きそうだったなあ・・・。
英語だけど丁寧に簡潔に解説されてて、けっこう感動している。
実際この解説見て初めて知ったけどコンテナってステートレスなんだね。

シナリオクリアしたらこうなる。

f:id:smspring0426:20180826165954p:plain

f:id:smspring0426:20180826170016p:plain


結局ねえ、英語とは切っても切れない関係にあるわけだし
英語力もつくんじゃないかなーって思うからおすすめ。

あ、あと英単語いちいち調べるの面倒だと思うんですけど そんな時はこのグーグルの拡張機能が便利ですよ。

Weblioエクステンション
英単語選択して、ctrlキー押したらWeblio辞書が出てきてくれるやつ。

Katacoda - Interactive Learning Platform for Software Engineers

誰かやったことない人やってみて〜〜(^◯^)♪

MySQLの予約語なんて知らなかった

今日の内容

大したことではありません。
タイトルの通り、MySQL予約語なんて知らなかった故に
とてもツマラナイエラーにどハマりしていたということです。
情けないし時間の無駄〜と思ったのでメモ。

予約語について

MySQL :: MySQL 5.6 リファレンスマニュアル :: 9.3 予約語によると、SELECTやDELETEが予約語に該当するそう。
もし予約語をテーブル名などに使うんだとしたら、引用符で囲ってあげる必要がある。

ex) interval

//引用符で囲わないとエラーになる
mysql> CREATE TABLE interval (begin INT, end INT);
ERROR 1064 (42000): You have an error in your SQL syntax ...
near 'interval (begin INT, end INT)'

//これは囲ってあるからおっけー
mysql> CREATE TABLE `interval` (begin INT, end INT);
Query OK, 0 rows affected (0.01 sec)

予約語の一覧はMySQL :: MySQL Server Version Reference :: 2.2 Keywords and Reserved Words in MySQL 5.6から確認できる。
単語の横に(R)がついてるものが予約語みたい。
パッと見た感じ、予約語だって知らずについつい使っちゃいそうだな〜って感じたものはall, case, keyかな。
まあデータベースの内容によると思うので、個人的な感想ですが。。

ちなみに私のエラーの場合

予約語ちゃんの1つ、updateカラム名に使ってました。へへ・・・。
自分しか使わないからって、適当にカラム名つけるからいけない。
通りでね、何回確認しても誤字があるわけでもないし文法的にも合ってるのにエラーが吐かれるわけですよ。
これからは気をつけよう。


全然関係ないけど、joinをあまりうまく使いこなせてないような気がするから、これまたちゃんと勉強したいところ。

Node.jsでスクレイピングした時の困りごと(cheerio-httpcli使用)

今日の内容

Node.jsでスクレイピングする時は、cheerio-httpcliというパッケージを使っています。
これ、日本語で書いてあるから使い方わかりやすいです。 はぁありがたや〜ありがたや〜。
なのでこのドキュメント読めば大体わかると思いますけど、
スクレイピング・・・はて・・・?状態から始まった当時の私は、以下の記事も参考にしていました。

上記の2つだけでも使い方等はわかると思うのでここでは省いて、
cherrio-httpcliを使ってのスクレイピングで困った事と解決した方法をメモります。
なお解決した方法ですが、100%これで解決できますという自信は持ち合わせておりません。
手当たり次第調べまくって、これ設定してみたらなんかうまく進んでるぞ・・?というものをご紹介します的な感じですので、的はずれな事書いてたらすみません。

実行できてもエラーで止まる問題

なんかね、私だけかもしれないですけどエラーが頻発して処理がよく止まってたんですよ。
そのエラーも2パターンあったような気がします。

socket hang up

socketって何ですかね。何回も調べてるけど未だにピンとこないです。
合ってるかどうかわからないけど、とりあえず接続の出入り口っていう認識を持っています。
まあそのsocketがhang upするっていうエラーが多分出てくる事があるかもしれません。

対処法

これを書いたらsocket hang upは無くなりました。

import http from 'http'

process.env.UV_THREADPOOL_SIZE = 128
http.globalAgent.maxSockets = 250

根本的な解決になっているのかなあ。
参考にした記事は何だったか忘れちゃったけど、少なくとも以下2つは読みました。

no content (or $ is not function)

こんな感じで書いたとします。

import ch from 'cherrio-httpcli'

・・・省略

const fetch = ch.fetch(targetUrl)
const $ = fetch.$

const title = $('h1').text()

しばらく実行して放置していたらこんなエラーが出てきました。

TypeError: $ is not a function
   at promise.then (/Users/name/workspace/practice/htmls.js:63:30)
   at <anonymous>

もしくはこんなエラー。

Error: no content
   at Object.fail (/Users/name/workspace/practice/node_modules/cheerio-httpcli/lib/client.js:72:48)
   at Object.<anonymous> (/Users/name/workspace/practice/node_modules/cheerio-httpcli/lib/client.js:253:14)
   at Object.<anonymous> (/Users/name/workspace/practice/node_modules/cheerio-httpcli/lib/client.js:220:9)
   at Request.self.callback (/Users/name/workspace/practice/node_modules/request/request.js:185:22)
   at emitTwo (events.js:126:13)
   at Request.emit (events.js:214:7)
   at Request.<anonymous> (/Users/name/workspace/practice/node_modules/request/request.js:1157:10)
   at emitOne (events.js:116:13)
   at Request.emit (events.js:211:7)
   at IncomingMessage.<anonymous> (/Users/name/workspace/practice/node_modules/request/request.js:1079:12)
   at Object.onceWrapper (events.js:313:30)
   at emitNone (events.js:111:20)
   at IncomingMessage.emit (events.js:208:7)
   at endReadableNT (_stream_readable.js:1064:12)
   at _combinedTickCallback (internal/process/next_tick.js:138:11)
   at process._tickCallback (internal/process/next_tick.js:180:9)

内容がない・・・?

httpプロキシ使ってIPコロコロ変えながら、5~10秒おきにアクセスして処理を行うっていうのをループさせてたんですよね。
だから大量にリクエスト送りすぎてサーバ停止させちゃった?
もしくはアクセスできなくさせられた?
とか色々考えたんですけど、1秒間に何万回もリクエストしてたわけじゃないし
IPで止められてるなら1回も実行できないんじゃないかなとか思って謎でした。

でも何とか解決できました。

対処法

目的のリンクにアクセスして然るべき処理を終えた後に、これを書き足しましょう。

import ch from 'cherrio-httpcli'

・・・省略

ch.reset()

reset()を実行すると、各種設定情報やクッキーが全て初期化されてプロセス起動時と同じ状態に戻るとのことです。 cheerio-httpcli - npm

あくまで私の場合ですが、1アクセスごとにIPはコロコロ変わるけどセッションIDは全て同一で、 途中までちゃんとループしてるのですが、突然no contentが返ってループが止まるといった状況でした。
ループ中にセッションが切れて、そのタイミングの時にアクセスしたら、セッションID持ってない・もしくは無効なセッションIDだからサーバがno contentを返したってことなのかなぁ、と思ってます。わからん。


非同期のループ処理とかも結構迷ったので書こうと思ったけど、疲れちゃったからここまで。

Nuxt.jsでgooglemapを読み込む

教えてもらったのでメモ。
googlemapの読み込みがいい感じにできた。


ディレクトリ構造
・pages
 └ index.vue
・plugins
 └ maps.js
・nuxt.config.js
・.env
使ったパッケージ

load-google-maps-api
dotenv

yarnで入れた。

$ yarn add load-google-maps-api
$ yarn add dotenv
内容

・.env

API_KEY=apiキーを入力

・nuxt.config.js

import dotenv from 'dotenv'
dotenv.config()

export default {
  env: {
    apiKey: process.env.API_KEY
  },
  plugins: [
     { src:  '~/plugins/maps', ssr: false }
  ]
}

envプロパティに使いたい環境変数を書くことで
フロント・サーバーどちらにも環境変数が共有されるよ
ということだそう。
API: env プロパティ - Nuxt.js

そしてpluginsプロパティには、使いたいプラグイン設定ファイルのパス名を書けば読み込んでくれるよと。
今回の場合はサーバーサイドでは使えないし、使わないプラグインなので
ssr: falseにしているよ。
そうすることで、フロントでのみ使うことができる。
API: plugins プロパティ - Nuxt.js

pluginsのsrcに書くパス名はドキュメント見る限り
~/plugins/設定ファイル名でも~/plugins/設定ファイル名.jsでも
どっちでも良さそうなのですね。

ここでdotenv.config()している理由は下記に記します。

・plugins/maps.js

import loadGoogleMapsApi from 'load-google-maps-api'

export default async (context, inject) => {
 const gmap = await loadGoogleMapsApi({
   key: context.env.apiKey
 })

 inject('gmap', gmap)
}

ここでだいぶ困りました。
設定したはずの環境変数が読み込まれなくて、いくらやってもundefined。
どうすればいいんじゃ〜と思ってた時に Use environment variables inside nuxt.config.js · Issue #2998 · nuxt/nuxt.js · GitHub environment variables not working · Issue #1386 · nuxt/nuxt.js · GitHub
↑読んで、nuxt.config.jsにdotenv読み込でみたらいいのかと思い実践したら解決。

あと環境変数の呼び出し方だけど、
ドキュメントに書いてあったcontext.envのキーだとダメだった。。
上記を踏まえていうなら

・process.env.apiKey
・context.env.apiKey

だったら読み込めた。

inject()に関してはあまりピンときてないけど、
とにかくpluginsに設定するとpagesの各コンポーネント
this使ってそのプラグインを使えるようになるんですよ。
だから今回の場合はthis.$gmap.~って呼べるようにしてるってことなのかな。
[docs] What does inject do? · Issue #2233 · nuxt/nuxt.js · GitHub
↑ふわっと読んだもの

・pages/index.vue

<style>
#map {
  width: 600px;
  height: 500px;
}
</style>

<template>
  <div id="map"></div>
</template>

<script>
  export default {
   data() {
     return {
       map: null
     }
   },
   mounted() {
     const element = document.getElementById('map')
     const map = new this.$gmap.Map(element, {
       center: { lat: 35.685175, lng: 139.7528 },
       zoom: 15
     }
     this.map = map
   }
  }

</script>

dataプロパティに入れてgooglemapのインスタンス保持しておけば
なんか地図を切り替えるってなった時も
setOptions()で設定を変更するだけでokだと信じています。
要は新しくインスタンスを生成しなくてよくて、使い回しできるという。
  


おわり。
ブログ書くって大変だな。