20201104のMySQLに関する記事は9件です。

Laravelで、急に「Malformed communication packet」エラーでSQLに接続できなくなった際の対応メモ

さっきまで動いていたLaravelが下記エラーで動かなくなった。。:sob:

image.png

SQLSTATE[HY000]: General error: 1835 Malformed communication packet

原因がわからないが、dockerのローカル環境と、別途用意している外部サーバーのテスト環境で同じDBサーバーに接続しており、両方とも同じタイミングで同じエラーになったので、
起因はDBサーバー側にありそうな。
追って余力があれば情報追記します。

対処...

laravelのconfig/database.phpに、

'options'   => [PDO::ATTR_EMULATE_PREPARES => true],

を追加する。

config/database.php
// 〜省略〜
'connections' => [

        'sqlite' => [
            'driver' => 'sqlite',
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
        ],

        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST_MASTER', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => false,
            'engine' => null,
            'options'   => [PDO::ATTR_EMULATE_PREPARES => true],
// 〜省略〜

これで無事エラーはなくなりました。。:smile:

ATTR_EMULATE_PREPARES はfalseの方が良さげですが、
色々言及しているサイトがあるのでそちらを参考にした方が良いかと思います。
参考:https://teratail.com/questions/233051

参考サイト:
https://teratail.com/questions/233051
https://stackoverflow.com/questions/64678367/laravel-mysql-error-sqlstatehy000-general-error-1835-malformed-communicat

※PHPや、MariaDBのバージョンをアップデートするのが良いという情報もありますが、アップデートの影響の方が大きそうなので、今回はconfigファイルを変更で汗

環境

  • PHP 7.3.19
  • Laravel Framework 5.5.48
  • 10.2.35-MariaDB-log
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのENUM型について

enumの列挙値には文字列を指定する。
DBのenum型のカラムに実際に格納される値は、列挙値のインデックスの数値。

--enum型のsizeというカラムを定義する--
CREATE TABLE shirts (
    name VARCHAR(40),
    size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
);

ENUM カラムに数字を格納するのは、分かりづらいので良くない

以下のようなnumbersというENUM型のカラムを定義する

numbers ENUM('0','1','2')

--配列と違ってインデックスは1から始まる--
INSERT INTO t (numbers) VALUES(2)

-- insertした2は、インデックスとして解釈されるため1が返る --
SELECT * FROM t 
INSERT INTO t (numbers) VALUES('2')

-- insertした'2'は、列挙値と一致するので2が返る --
SELECT * FROM t 
INSERT INTO t (numbers) VALUES('3')

-- insertした'3'は、どの列挙値とも一致しないのでインデックスとして扱われ、2が返る --
SELECT * FROM t 

公式ドキュメント: https://dev.mysql.com/doc/refman/5.6/ja/enum.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実家の会社の日報表をwebアプリ化した。(スマホ用)

はじめに

実家の会社が未だに紙ベースでやりとりをしているので、実際に使ってほしいという気持ちを持ちつつ作ってみました。(スマホ用)

概要と背景

  • 営業マンがその日行った営業を日報表にまとめてその日の終わりに提出する。
  • 紙面でのやりとりなので、事前に報告ができない、過去の営業記録をいちいち紙で探す必要がある、など不便なことが多い。
  • 日報表の項目は、訪問先住所・訪問先氏名・会見者・訪問結果・内容・日付の6項目。
  • 社員の管理は社員用テーブルで管理し、訪問履歴はセールス記録テーブルで管理する。
  • 訪問履歴は社員に個別に与えられた社員番号で個別に管理する。

開発環境

  • サーバーサイド
    • java
    • java EE
  • フロントサイド
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

基本機能

  • ログイン機能
    • 社員名と社員ごとに与えられたパスワードで管理
    • セッションによるログイン管理
  • ログアウト機能
    • セッションを削除
  • 日報表の入力・送信機能
    • 項目は概要の通り
    • 営業記録の入力の際に、社員番号を入力せずにセッション情報から社員番号を取得し、サーバーサイドで自動的に入力
  • 入力の際のチェック機能、送信の際の再確認機能
  • 入力した日報の一覧表示
    • 社員が自分の日報のみ見られる(他の社員の日報は見れない)ように設定
    • 今日、今週、今月、全て、から選ぶことが可能
  • 訪問履歴の検索機能
    • 訪問先氏名または郵便番号による検索が可能

できたもの

  • ログイン画面
    日報表ログイン.jpg

  • 日報入力画面
    日報表入力.jpg

  • 営業記録一覧を表示した例
    一覧は横スクロールで対応
    input.gif

  • 検索例
    input2.gif

工夫した点

  • 社員は結構年を取っている人が多いのでUIをなるべく大きめに設定した。
  • ajaxを使って郵便番号を入力すると該当する都道府県市区まで自動入力されるようにした。
  • 日報を入力し、送信を押したときに再確認するよう入力された情報をアラートで表示し、OKか確認するよう設計した。
  • 名前別の検索だけだと別の住所であっても重複する可能性があるため、より確実な郵便番号による検索を設けた。

作った感想

日報表をwebアプリ化してみて、紙でのやりとりより圧倒的に役立つと確信できた。(まだ使ってくれてない)

最後に

この日報表の他にも、紙でやりとりされていて、かつアプリ化できるものがたくさんあるので、これからアプリ化してどんどん実家の会社の業務効率をよくしたい。(使ってくれるかは分かりませんが。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

チャットアプリを作った。

はじめに

webアプリの練習としてチャットアプリを作ってみました。

基本機能

  • ログイン制のアプリ
    • パスワードはハッシュ化してDBで管理
  • セッションを使ったログイン管理機能
  • アカウント作成機能
  • メッセージ投稿機能
    • 投稿内容をDBに保存
    • XSS対策のエスケープ処理
  • 画像投稿機能
    • 画像をbyte配列に変換し保存し軽量化
  • 入力チェック機能
    • ログイン時
    • アカウント作成時
    • メッセージ投稿時
  • メッセージ削除・編集機能(ユーザー視点から、DBに保存してある個人のメッセージを個別に削除・編集が可能)
  • Bootstrapフレームワークを使ったアプリデザイン
  • スマホに対応したレスポンシブデザイン

開発環境

  • 使用言語
    • Java
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

画面

ログイン画面

  • PC用 roguinngamen.png
  • スマホ用
  • スマホの画面.jpg

チャット画面

  • PC用
    チャット画面.png

  • スマホ用
    tyattogamen.png

作った感想

  • 普通のチャットアプリでもいざ作ってみると面倒なことが多い。
    • 改行を判定するための処理とXSS対策の両立
  • 画像を投稿する際にbyte列に変換したほうが良いということを学んだ。表示する際にはbase64にエンコードして表示する方法を選んだが、この方法はページを表示する際に負荷がかかるため、今後の反省点にしたい。
  • デザインには自信がないため、bootstrapのおかげでだいぶマシになった。

最後に

チャットアプリといえど、その処理や構造はほぼほぼ掲示板や、他の入力フォームを伴うアプリと同じあるいは似ているので、他のアプリでも流用が効くことがわかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】MySQLによる開発

今までいくつかWebアプリケーションを制作したり学習をしてきましたが、
全てDBはデフォルトのSQLiteを使用してきました。

今回新しくアプリケーションを制作する上で本番環境をAWSにしようとするとMySQLの設定が必要・・・
ということで、MySQLを使用したいと思います!
まだまだ初心者なので、間違っているところがあればご指摘お願いします。

MySQLとは

世界で最も利用されているデータベースで、以下のような特徴があります。

  • 大容量のデータベースを高速かつ安定的に利用できる
  • 関連のあるデータ項目が、事前に定義されている
  • データベースは、テーブル単位にデータを保持し、テーブル間の関係も保持する
  • データ項目は列として表現され、関連のあるデータ項目の集合体が行として表現されたテーブルが形成される(Excelのタイトル付きの表のようなイメージ)
  • リレーショナルデータベースとのやりとりには、SQLが使用される
  • 日本語に対応している

SQLiteとの違い

Railsをインストールして、デフォルトで使用されるRDBMSはSQLite
SQLiteは以下のような特徴がある

  • それほど大きなデータベースではなく、個人利用レベルのデータベースの利用を想定している
  • リレーショナルデータベースとのやりとりには、SQLが使用される
  • 著作権が放棄されパブリックドメインで誰もが自由に利用できる
  • 消費メモリも少ないため、処理能力が低い小型デバイスでも組み込める

巨大なDBを扱う場合はMySQLを使用した方が良いらしい

MySQLで開発をしてみる

RailsはデフォルトではSQLiteを使用するため、
MySQLを使用するためには立ち上げの際に指定してあげる

rails new アプリケーション名 --database=mysql

省略もできる

rails new アプリケーション名 -d mysql

こうすることで、RDBMSをMySQLに変更することができるらしい。
念のためGemfileを確認・・・
指定しない場合は

Gemfile
# Use sqlite3 as the database for Active Record
gem 'sqlite3'

となるのに対して、指定してあげると

Gemfile
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'

となりました!!

しかし、問題が・・・。
rails new アプリケーション名 -d mysqlをした段階でエラーが発生。。。

・
・
(たくさんのログ)
・

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/mysql2-0.5.3/ext/mysql2
/Users/mariko/.rbenv/versions/2.6.5/bin/ruby -I /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/2.6.0 -r ./siteconf20201104-33105-i9fwuc.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/opt/mysql@5.7/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/opt/mysql@5.7/lib
-----
creating Makefile

current directory: /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/mysql2-0.5.3 for inspection.
Results logged to /Users/mariko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-19/2.6.0/mysql2-0.5.3/gem_make.out

An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2
         run  bundle binstubs bundler
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.
         run  bundle exec spring binstub --all
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.
       rails  webpacker:install
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

とりあえず翻訳してみる
「mysql2(0.5.3)のインストール中にエラーが発生し、Bundlerを続行できません。」
・・・わからない・・・。
とりあえずやっつけないと・・・!

解決方法

とりあえずログに書いてあることを実行

$ gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'

ERROR:  While executing gem ... (OptionParser::InvalidArgument)
    invalid argument: --source https://rubygems.org/

Mac へ MySQL をインストール

MySQLをインストールするという部分をすっかり見落としていました。
下記記事を参考にインストール!
Mac へ MySQL を Homebrew でインストールする手順

下記コマンドでインストール

$ brew update
$ brew install mysql

インストールができているか確認

$ brew info mysql

mysql: stable 8.0.22 (bottled)
Open source relational database management system
https://dev.mysql.com/doc/refman/8.0/en/
Conflicts with:
  mariadb (because mysql, mariadb, and percona install the same binaries)
  percona-server (because mysql, mariadb, and percona install the same binaries)
/usr/local/Cellar/mysql/8.0.22 (294 files, 296.5MB) *
  Poured from bottle on 2020-11-04 at 13:55:11
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/mysql.rb
License: GPL-2.0
==> Dependencies
Build: cmake ✘
Required: openssl@1.1 ✔, protobuf ✔
==> Caveats
We've installed your MySQL database without a root password. To secure it run:
    mysql_secure_installation

MySQL is configured to only allow connections from localhost by default

To connect run:
    mysql -uroot

To have launchd start mysql now and restart at login:
  brew services start mysql
Or, if you don't want/need a background service you can just run:
  mysql.server start
==> Analytics
install: 85,249 (30 days), 214,834 (90 days), 813,126 (365 days)
install-on-request: 83,371 (30 days), 209,508 (90 days), 784,193 (365 days)
build-error: 0 (30 days)

バージョン8.0.22がインストールされていた!

MySQLを起動してみる

$mysql.server start
Starting MySQL
 SUCCESS! 

接続して動作確認をしてみる

$mysql -uroot

ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

・・・・またまたエラー(泣)
パスワードがないからダメのか?と思い、とりあえず設定します。

セキュリティ設定

$mysql_secure_installation

Enter password for user root:(パスワードを入力する)

パスワードの強度をチェックしてくれるプラグインのセットアップをする?「y」

VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No: y

検証レベルを選ぶ。

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
  • LOW:8文字以上
  • MEDIUM:8文字以上 + 数字・アルファベットの大文字と小文字・特殊文字を含む
  • STRONG:8文字以上 + 数字・アルファベットの大文字と小文字・特殊文字を含む + 辞書ファイルでのチェック

本番環境ようなので、2を選択。
先ほど入力したパスワードの強度が合致していないと再度入力を求められます。
2度パスワードを打ち込むと、これで確定していいんだね?と言われるので「y」

Estimated strength of the password: 50 
Change the password for root ? ((Press y|Y for Yes, any other key for No) : y

New password: 

Re-enter new password: 
Estimated strength of the password: 100 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y

匿名ユーザ( anonymous user )を削除する?「y」

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.

リモートから root ユーザでログインできないようにします。ログインを禁止する?「y」

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

デフォルトで作成されている test という名前のデータベースを削除する?「y」

Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.
 - Removing privileges on test database...
Success.

すぐに権限テーブルをリロードして変更を有効にする?「y」

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done! 

とりあえず、設定はできたかな?

もう一度動作確認

$mysql -u root -p
Enter password:(パスワード入力)

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.32 Homebrew

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

う、、動いた!!!泣

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)

testデータベースも無くなっていました!

一度mysqlからはexitして・・・
停止もして・・・

$ mysql.server stop
Shutting down MySQL
. SUCCESS! 

アプリに戻ってbundle install

同じエラーが・・・・・・・泣

下記記事を参考に進めました!
【Rails】MySQL2がbundle installできない時の対応方法

$ brew info openssl
openssl@1.1: stable 1.1.1h (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@1.1/1.1.1g (8,059 files, 18MB)
  Poured from bottle on 2020-05-21 at 18:53:48
/usr/local/Cellar/openssl@1.1/1.1.1h (8,067 files, 18.5MB)
  Poured from bottle on 2020-11-01 at 12:26:48
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/openssl@1.1.rb
License: OpenSSL
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> /Users/mariko/.bash_profile

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Analytics
install: 835,013 (30 days), 1,906,254 (90 days), 7,237,126 (365 days)
install-on-request: 136,062 (30 days), 257,990 (90 days), 1,007,070 (365 days)
build-error: 0 (30 days)

この部分が重要らしい。

export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

下記の二つのコマンドを実行

$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
You are replacing the current local value of build.mysql2, which is currently nil

$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
You are replacing the current local value of build.mysql2, which is currently "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"

再度bundle install
インストールできました・・・・・!!!嬉泣

まとめ

アプリにMySQLを導入するまでかなり右往左往してしまいました。
読みづらかったらすみません。
少しでも誰かのお力に慣れたら嬉しいです。

参考にさせていただいた筆者の方々、本当にありがとうございました。

途中でSQLiteに変更する手順もあるみたいなので、今まで制作したアプリで試してみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】初めての認証(Laravel8でJetstreamを使う)

環境

  • OS : macOS Caralina 10.15.7
  • PHP : 7.4.11
  • laravel : 8.11.2
  • Composer : 1.10.6

今回の学習内容

No. 概要
1. Jetstream の導入
・ 導入のコマンド
・ 日本語化する
・ 動作確認で、デフォルト機能を確認( 新規登録/ログイン(email+PW認証)、ユーザー情報更新)
2. 作成されたファイルの把握に努める
※ 大量のファイルが作成されるので、理解が追いつかない。。
3. 例)基本の実装いろいろ
・ ログイン / アカウント登録後のリダイレクト先 の変更
・ usersテーブルへのカラム追加
・ user - 他モデル との紐付け
・ 所望のページにアクセス制限をつける
・ Jetstream ロゴの変更
・ アイコン写真の登録

Auth とは?

  • 認証(Ahthentication)や認可(Ahthorization)の意味。
  • Laravelでは、デフォルトで認証機能があり、コマンドで作れる。

Jetstream とは?

  • Laravel8の認証ライブラリ。
    • Laravel〜5:make:auth、Laravel6〜7:laravel/uiに分離。Laravel8では、全部入り(Jetstream)になったんだとか。。
  • Jetstream がやってくれることは、 ログイン、新規登録、メール検証、2段階認証、セッション管理、APIサポート(Laravel Sanctum)、チーム管理など。めっちゃやってくれる。。

1. Jetstream の導入

【 導入のコマンド 】

Jetstreamの導入まで
// ① Jetstream のインストール
% composer require laravel/jetstream      // composerを使ってインストール
% php -d memory_limit=-1 /usr/local/bin/composer require laravel/jetstream     // メモリ制限エラーが出たら、メモリを上げてインストール
% laravel new project-name --jet          // アプリの雛形作成のタイミングで、Jetstream をインストールも可能(Livewire/Inertiaやチーム機能を使うか?聞かれる)
// インストールが完了したら、artisan コマンドでjetstreamが使えるようになる

// ② セットアップ : JSのパッケージをインストール( livewire と inertia を選択できる)
% php artisan jetstream:install livewire
% php artisan jetstream:install livewire --teams  // チーム機能を使う場合
% php artisan jetstream:install inertia          // vueやreactなどを使う場合は、コッチらしい。。

// ③ マイグレーション実行
% php artisan migrate            // usersテーブル、password_resetsテーブル作成のマイグレーションファイルが、自動生成されてる

// ④ JSとCSSをビルド
% npm install && npm run dev    // npmコマンドが無いエラーが出たら、Nodeのインストールが必要(https://nodejs.org/ja/)
  // 若しくは、別々にNode.js と NPM をインストール
  % npm install
  % npm run dev
// public/css/app.css、 public/js/app.js2ファイルが作成される
  • ここで、ブラウザで動作確認すると、エラー発生。'Laravel\Sanctum\HasApiTokens' が見つからんらしい。。

Trait 'Laravel\Sanctum\HasApiTokens' not found

→ laravel/sanctum をインストールしてみる。

laravel/sanctumのインストール
// ⑤ laravel/sanctumのインストール
% composer require laravel/sanctum
% php -d memory_limit=-1 /usr/local/bin/composer require laravel/sanctum   // メモリ制限エラーが出たら、メモリを上げてインストール
  • デフォルトでは、welcomeページに、ログイン・新規登録ボタンが設置してくれてる。が、今回は、既存アプリに導入したので、ルートページは、welcomeページではない。
    • welcomeページに設置された、「 ログイン・新規登録ボタン 」 部分をコピペさせてもらう。
welcomeページに設置された、ログイン・新規登録ボタン部分(welcome.blade.php)
@if (Route::has('login'))
  <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
    @auth    // ログイン中の場合
      <a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 underline">ダッシュボード</a>
    @else    // 未ログインの場合
      <a href="{{ route('login') }}" class="text-sm text-gray-700 underline">ログイン</a>
      @if (Route::has('register'))
        <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">アカウント作成</a>
      @endif
    @endif
  </div>
@endif
  • @auth...@endif構文(※ Laravel7までは、@auth...@endauth)。

【 日本語化する 】

フィールドの日本語化

  1. バリデーションなどの翻訳データが公開されてるので、GitHubのLaravel-Lang/lang から、zipファイルをダウンロード。
  2. ダウンロードしたフォルダの中から、日本語化ファイルを移動↓
    • コマンドで実行する場合は、↓
日本語化設定(resources/lang/jaの生成)
php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"
// resources/lang/ja に、ファイルが生成される( auth.php、 pagination.php、 passwords.php、 validation.php )

※ ただし、微妙に英語が残ってるので、気付いた箇所は、resources/lang/js.json に、力技で日本語化。

【 動作確認 】

  • この段階で、新規登録とログイン機能が動く状態になってる。
  • ルートページに、ログイン・新規登録ボタンが出現。
新規登録ボタン → 新規登録ページ ログインボタン → ログインページ
localhost:8000/register localhost:8000/login
↓ Already registered? をクリック
(localhost:8000/login)
↓ Forgot your password? をクリック
(localhost:8000/forgot-password)
ログインページへ遷移。
PWリセットリンクをメールで送信してくれる。
  • name、email、PWは必須。
    • name、emailは、Max255文字まで。
    • emailは、文字列長(@を含む)と、一意性。
    • PWは、8文字 / 空 / PW(確認用)と不一致 で、バリデーション。
    • バリデーションの条件は、app\Actions\Fortify\CrateNewUser.php、PasswordValidationRules.php に書かれてる。
  • ログイン時に、入力を何度も間違うと、一定時間ログインできなくなる。
  • アカウント作成クリックで、DBにデータが保存できてる。
  • ログイン / アカウント作成 をクリック → dashboardページに遷移する。
dashboardページ ログイン状態 → ルートページ
localhost:8000/dashboard localhost:8000/

ログイン時のボタン表示
  • dashboardページには、Profile・ログアウトボタンが設置されてる。
Profileボタン → ユーザー情報更新ページ ログアウトボタン
localhost:8000/user/profile localhost:8000

ログイン中の端末もわかる。

ルートページへ遷移。
(未ログイン時のボタン表示)
  • ユーザー情報(name、email、photo)やPWの更新に関するバリデーション条件は、app\Actions\Fortify\UpdateUserProfileInfomation.php、UpdateUserPassword.php(※ PasswordValidationRules.phpを呼び出してる) に書かれてる。

2. 作成されたファイルたちのほんの一部

○ ルーティング

  • 認証用のルートが追加される。
  • authミドルウェアが使われてる。未ログイン時は、dashboard ビューが表示されず、ログインページにリダイレクトされる。
追加される認証用のルート
// web.php
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');   // dashboard というルート名が付けられてる

// api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

○ コントローラー ( app\Http\Controllers)

  • Jetstream では、不要なので作成されない。必要に応じて、自分で作成する。
例)app/Http/Controllers/Auth/LoginController.phpの作成の場合
% php artisan make:controller Auth\\LoginController
例)ファイル名 例)用途
Auth\RegisterController ユーザー登録用(登録/検証/作成)
Auth\LoginController ログイン用(ユーザー認証後のリダイレクトの処理)
Auth\VerificationController ログイン後の処理用(登録した全ユーザーのメール検証の処理など)
Auth\ConfirmPasswordController.php PW確認
Auth\ForgotPasswordController.php PWリセットメール
Auth\ResetPasswordController.php PWのリセット要求の処理
HomeController.php ログイン後のページ用

○ ビュー ( resources\views )

  • Jetstreamが作成した、コンポーネントファイルは、隠しファイルになってる。 ↓のコマンドで表示でき、編集可能になる。
隠しファイルの表示コマンド
% php artisan vendor:publish --tag=jetstream-views
// resources/views/vendor/jetstream ディレクトリが出現する
ファイル名 用途
auth\forgot-password.blade.php PWリセットリンクの送信先入力フォーム
auth\login.blade.php ログインページ
layouts\guest.blade.phpを読み込んでる。
auth\register.blade.php 新規登録ページ
layouts\guest.blade.phpを読み込んでる。
auth\reset-password.blade.php PWリセット
auth\two-factor-challenge.blade.php ログインページ(2段階認証)
auth\verify-email.blade.php 本人確認ページ(2段階認証)
layouts\app.blade.php ログイン時のレイアウト
(ナビゲージョンバー、ヘッダーを呼び出してる)
layouts\guest.blade.php 未ログイン時のレイアウト(ログイン・新規登録ページ)
profile\●●.blade.php プロフィール編集ページに使ってる部分テンプレート
vendor\jetstream\components\●●.blade.php(隠しディレクトリ) コンポーネントファイル(各種部分テンプレート)
welcome.blade.php welcome画面に認証用ボタンが設置される。
vendor\jetstream\components\welcome.blade.phpからコンポーネントを読み込んでる。
dashboard.blade.php ダッシュボードページ
layouts\app.blade.php、welcome.blade.phpを読み込んでる。
navigation-dropdown.blade.php ナビゲージョンバー
  • @componentで呼び出していたコンポーネントを<x-xxxx />の形式で呼び出す仕組み。Livewireでは、全力でコンポーネントを使い倒してる。。
    • x-タグは、Bladeコンポーネント。x-jet-タグは、Jetstream内で定義されてるコンポーネント。
    • 例えば、<x-app-layout>は、app/View/Components/AppLayout.php を読み込んでる。 このファイルでは、resources/views/layouts/app.blade.php のビューを呼び出してるカタチになってる。
    • 例えば、<x-jet-welcome />は、Jetstreamの隠しファイル内のresources/views/vender/jetstream/components/welcome.blade.php を読み込んでる。

○ モデル ( app\Models\User.php )

Userモデルの中身
// 値を代入するフィールドの設定
protected $fillable = ['name', 'email', 'password',];
// データを取得しないフィールドの指定
protected $hidden = ['password', 'remember_token', 'two_factor_recovery_codes', 'two_factor_secret',];
// キャストが必要なフィールドの指定
protected $casts = ['email_verified_at'=>'datetime',];
// モデルの配列に追加
protected $appends = ['profile_photo_url',];

○ 認証機能の設定 ( config\auth.php )

認証設定ファイル(config/auth.php)の中身
return [
  'defaults' => [                 // デフォルトの認証「ガード」と、PWリセットオプション
    'guard' => 'web',             // 使う設定を指定
    'passwords' => 'users',       // 使う設定を指定
  ],
  'guards' => [
    'web' => [
      'driver' => 'session',     // ドライバーの種類
      'provider' => 'users',     // 使う設定を指定
    ],
    'api' => [
      'driver' => 'token',       // ドライバーの種類
      'provider' => 'users',     // 使う設定を指定
      'hash' => false,           // APIトークンを平文ではなく SHA-256 にする場合は true
    ],
  ],
  'providers' => [
    'users' => [
      'driver' => 'eloquent',              // データへのアクセスで使用するドライバー
      'model' => App\Models\User::class,   // 指定したドライバーが eloquent の場合、使うモデル
    ],
  ],
  'passwords' => [
    'users' => [
      'provider' => 'users',              // 使うproviderの設定
      'table' => 'password_resets',       // PWリセットトークンの格納テーブル
      'expire' => 60,                     // 有効時間
      'throttle' => 60,                   // 要求制限、スロットル時間(秒)
    ],
  ],
  'password_timeout' => 10800,           // PW再確認までの時間
];

○ ミドルウェア ( app\Http\Middleware )

ファイル名 用途 エリアス
Authenticate.php ログイン中か? 未ログイン時のリダイレクトの設定 auth
EncryptCookies.php Cookieの値の暗号化
PreventRequestsDuringMaintenance.php メンテナンスモードか?
RedirectIfAuthenticated.php 認証ユーザーリダイレクトの設定( 認証済みならココにリダイレクトするよ ) guest
TrimStrings.php フォーム入力値の前後の空白を自動でトリムする
TrustHosts.php
TrustProxies.php 正しいトコからのリクエストのみを信頼して処理
VerifyCsrfToken.php CSRFチェック
AuthenticateWithBasicAuth.php Basic認証 auth.basic
SetCacheHeaders HTTPキャッシュ cache.headers
Authorize.php リクエストがルートやコントローラーに行く前に、アクションを認可する can
RequirePassword.php アクセス許可前に、PWを要求したい場合(支払いの設定変更時などに使う) password.confirm
ValidateSignature.php 署名URLが有効か?のチェック signed
ThrottleRequests.php レートリミット
(※ ○回/minまでの実行制制限)
throttle
EnsureEmailIsVerified.php メール認証してないならリダイレクト verified
  • エリアスは、 app\Http\Kernel.php で定義されてる。

○ プロバイダー ( app\Providers )

  • app.phpは、アプリが使ってるサービス一覧が書かれてる。処理の内容は、プロバイダーに書かれてる。
  • Jetstream を導入すると、認証に関するFortify、Jetstream プロバイダーが追加される。
ファイル名 用途
AuthServiceProvider.php (既存)認証のゲート、ポリシー、認証/認可サービスを登録処理
FortifyServiceProvider.php Fortifyパッケージについて(ユーザー登録/更新/PWリセットなど)
JetstreamServiceProvider.php アカウント削除、APIトークンに関する記載あり
RouteServiceProvider.php public const HOME = /Home

public const HOME = /Dashboad へ変更されてる。
ルーティング関連の処理(ルートの制限などもココで設定する)。

○ テーブル

  • users テーブル : ユーザ管理用
カラム名 カラム型 用途
name string ユーザー名
email string(一意性) メールアドレス
email_verified_at timestamp メールによる本人確認日時
password string パスワード
two_factor_secret text(null可) 2段階認証
two_factor_recovory_codes text(null可) リカバリーコード
remember_token remember_token クッキー情報
current_team_id foreignId(null可) チーム機能で使用
profile_photo_path text(null可) プロフィールのアイコン写真のパス
  • password_resets テーブル : PWリセット管理用
カラム名 カラム型 用途
email string(インデックス) メールアドレス
token string PWリセットトークン
  • sessions テーブル : セッション管理用
カラム名 カラム型 用途
id string(一意性) ユーザー識別用のラベルみたいなヤツ
user_id foreignId(一意性、インデックス) ユーザーid(外部キー)
ip_address string(null可) アクセスしたIPアドレス
user_agent text(null可) アクセスしたブラウザ情報
payload text Sessionに保存する値(base64でエンコードされてる)
last_activity integer(インデックス) 最後のアクセス日時(unixタイムスタンプ)

3. 例)基本の実装いろいろ

【 ログイン / アカウント登録後のリダイレクト先 の変更 】

  • デフォルトだと、ログインやアカウント登録 → localhost:8000/dashboard にリダイレクトされる。
  • 認証ユーザーリダイレクトの設定ファイル(app\Http\Middleware\RedirectIfAuthenticated.php) → 定義元(app\Providers\RouteServiceProvider.php)を呼び出してるカタチ。
app\Http\Middleware\RedirectIfAuthenticated.php
public function handle(Request $request, Closure $next, ...$guards) {
  $guards = empty($guards) ? [null] : $guards;
  foreach ($guards as $guard) {
    if (Auth::guard($guard)->check()) {
      return redirect(RouteServiceProvider::HOME);   // RouteServiceProvider を呼び出してる
    }
  }
  return $next($request);
}

なので、定義元の方を変更してみる。

ログインやアカウント登録後のリダイレクト先の変更(app\Providers\RouteServiceProvider.php)
public const HOME = '/home';        // Jetstream導入前
public const HOME = '/dashboard';   // Jetstream導入後
   // rootページに変更
public const HOME = '/';

【 usersテーブルへのカラム追加 】

  • 登録項目を追加してみる。
新規登録ページ プロフィール編集ページ
localhost:8000/register localhost:8000/user/profile
  • まずは、通常通り、マイグレーションファイルを作成し、usersテーブルにカラムを追加。
usersテーブルにカラム追加
% php artisan make:migration add_column_to_users_table --table=users

// マイグレーションファイルに、追加カラムを記述
public function up() {
  Schema::table('users', function (Blueprint $table) {
    $table->date('birth')->nullable();
  });
}
public function down() {
  Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('birth');
  });
}

% php artisan migrate      // マイグレーション実行

次に、新規登録時に、データを保存できるよう、修正。

新規登録のための修正
// Userモデル
protected $fillable = [
     :
  'birth',
];

// app\Actions\Fortify\CreateNewUser.php
Validator::make($input, [
      : 
  'birth' => ['nullable', 'date', 'before:yesterday'],   // null or 昨日以前 でない場合は、バリデーション
])->validate();
return User::create([
      :
  'birth' => $input['birth'],
]);

// ビューに入力フォームを設置(resources\views\auth\register.blade.php)
<div class="mt-4">
  <x-jet-label for="birth" value="{{ __('生年月日(※ 任意)') }}" />
  <x-jet-input id="birth" class="block mt-1 w-full" type="date" name="birth" required />
</div>
  • 同様に、プロフィール編集ページにも反映。
プロフィール編集ページの修正
// app\Actions\Fortify\UpdateUserProfileInformation.php
Validator::make($input, [
         :
  'birth' => ['required', 'date', 'before:yesterday'],    // 削除メソッド未実装なので、とりあえず、空にできないよーにしとく。。 
])->validateWithBag('updateProfileInformation');

if ($input['email'] !== $user->email &&
         :
} else {
  $user->forceFill([
         :
    'birth' => $input['birth'],
  ])->save();
}

// ビュー(resources\views\profile\update-profile-information-form.blade.php)
<div class="col-span-6 sm:col-span-4">
  <x-jet-label for="birth" value="{{ __('生年月日') }}" />
  <x-jet-input id="birth" type="date" class="mt-1 block w-full" wire:model.defer="state.birth" />
  <x-jet-input-error for="birth" class="mt-2" />
</div>

【 モデルの紐付け 】

  • 例えば、記事投稿にユーザーを紐付ける場合。
    • 紐付けたいテーブルに、user_idカラムを作成し、モデルにアソシエーションを追記。
Userモデルとのアソシエーション
public function user() {
  return $this->belongsTo('App\Models\User');
}
コントローラーで取得して、ビューで表示してみる
// モデルを紐づけたので、コントローラーでは、Article::all()で、ユーザー情報も取得できてる
public function index() {
  $articles = Article::all();
  return view('index', ['articles' => $articles] );
}

// ビューで呼び出す
@foreach ($articles as $article)
  <p>{{ $article -> user -> name }}</p>    // 紐づくユーザー名 を表示
@endforeach

【 アクセス制限 】

  • 例えば、投稿・編集・削除機能は、ログイン時のみに制限したい。
コントローラー
class ArticleController extends Controller {
  // 未ログイン時は、index、showのみ許可する(それ以外では、未ログインなら、loginページへリダイレクト)
  public function __construct() {      //  __construct クラスを追加
    $this->middleware('auth')->except(['index', 'show']);
  }

  // 投稿機能
  public function store(Request $request){
    $article = new Article();
    $article = \Auth::user();
    $article -> content = request('content');
    $article -> user_id = $user->id;
    $article -> save();
    return redirect() -> route('article.detail', ['article' => $article->id] );
  }

  // 投稿者のみ、詳細表示ページに、編集・削除ボタンを表示させたいので、ビューでの条件分岐のために、変数で定義
  public function show($id){
    $article = Article::find($id);
    $user = \Auth::user();
    if ($user) {
      $login_user_id = $user->id;
    } else {
      $login_user_id = "";
    }
    return view('show', ['article' => $article, 'login_user_id' => $login_user_id] );
  } 
}
ビュー
// ログイン時のみ、登録ボタンを表示する
@auth
  <div>
    <a href='{{ route("article.new") }}'>登録ボタン</a>
  </div>
@endauth

// 投稿者のみ、編集ボタン・削除ボタンを表示する
@auth
  @if ($article -> user_id == $login_user_id)   // 現在のユーザーid = 投稿者id の場合
    <a href = '{{ route("article.edit", ["article" => $article->id]) }}', class='btn'>編集ボタン</a>
    {{ Form::open(['method' => 'delete', 'route' => ['article.destroy', $article->id] ]) }}
      {{ Form::submit('削除ボタン', ['class' => 'btn']) }}
    {{ Form::close() }}
  @endif
@endauth
  • 動作確認用に、usersテーブルにダミーデータを作成しとくとイイかも。
ダミーデータの作成
// Seederに記述(database/seeders/DatabaseSeeder.php)
          :
use App\Models\User;                // Userモデルを使うよー
class DatabaseSeeder extends Seeder {
  public function run() {
    User::factory(5)->create();    // ダミーデータを5つ作成
  }
}

// ダミーデータの作成実行(ターミナル)
% php artisan db:seed

【 Jetstream のロゴ変更 】

  • ロゴを含んでるページは、ログインページ、新規登録ページ、ダッシュボードページなど。
ビュー デフォルト 変更後
ログインページ
新規登録ページ
ダッシュボードページ
プロフィール情報更新ページ
  • 今回は、FontAwesomeのアイコンを使うので、FontAwesome5を導入。ダウンロード or CDN リンクをビューファイルに貼りつける方法があるが、手っ取り早く、後者で。
参)FontAwesomeの導入方法
// layout.blade.php の <head>タグに記述
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">
// 使い方
<i class="fas fa-map-pin"></i>

ログイン、新規登録ページのロゴを変更する

  • layout ファイルは、resources/views/layouts/guest.blade.php 。
  • ロゴ部分のコードは、Jetstreamのコンポーネントにある(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php )。
  • それぞれ、resources/views/auth/login.blade.php と register.blade.php で読み込んで使ってるので、コンポーネント側の記述を変更。
ログインページ、新規登録ページのロゴの変更
// FontAwesomeを使えるよーにする(resources/views/layouts/guest.blade.php の <head>タグ内に記述)
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">

// FontAwesomeのアイコンに変更してみる(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php)
<a href="/">
  <h1>Lunch Map</h1>
  <i class="fas fa-map-pin"></i>
</a>

ダッシュボード、プロフィール情報更新ページのロゴを変更する

  • layout ファイルは、resources/views/layouts/app.blade.php 。
  • ロゴ部分のコードは、Jetstreamのコンポーネントにある(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php )。
  • それぞれのページのヘッダー部分でナビゲージョンバー(resources/views/navigation-dropdown.blade.php) を読み込んでる。さらに、ナビゲージョンバーで、Jetstreamのコンポーネントを読み込んでる。
ダッシュボード、プロフィール情報更新ページのロゴの変更
// FontAwesomeを使えるよーにする(resources/views/layouts/app.blade.php の <head>タグ内に記述)
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">

// (方法1) コンポーネントのロゴ部分を変更(resources/views/vendor/jetstream/components/application-mark.blade.php)
<i class="fas fa-map-pin fa-2x"></i>
// ヘッダーのナビゲーション部分(resources/views/navigation-dropdown.blade.php)
<a href="{{ route('article.list') }}">   // リンク先も自分の設定したページに変更(※ デフォルトは、ダッシュボード)
  <x-jet-application-mark class="block h-9 w-auto" />
</a>
// (方法2) 若しくは、ナビゲーション部分を直接変更する(resources/views/navigation-dropdown.blade.php)
<a href="{{ route('article.list') }}">
  <i class="fas fa-map-pin fa-2x"></i>
</a>

【 アイコン写真の登録 】

  • Jetstream導入時に、デフォルトで、usersテーブルに写真データ用のカラムが作成されてる。
  • Jeststream公式リファレンスを参考に、config/jetstream.php で、該当箇所のコメントアウトを外し、アイコン写真を有効化。
コメントアウトを外して有効化(config\jeststream.php)
'features' => [
  Features::profilePhotos(),
  Features::api(),
  Features::teams(),
],
  • 写真データの保存場所は、 public\strage\profile-photos 。
    usersテーブルのprofile_photo_path カラムで、ココに格納されたデータを呼び出してる。
保存場所へシンボリックリンクを貼る必要がある
% php artisan storage:link

「.env」ファイルのURLも環境に合わせて変更(※ 今回はローカル環境)。

.envファイル
APP_URL=http://127.0.0.1:8000

(エラー) profile_photo_path カラムがないよ!

jetstream sqlstate[42s22]: column not found: 1054 unknown column 'profile_photo_path' in 'field list'

  • (状況)今回、アプリ実装途中で、Jetstreamを導入した。既にマイグレーションを何度か実行しており、usersテーブルが作成されてる状態だった。
    • Jetstream 導入によって、マイグレーションファイル(xxxxx_create_users_table.php)に、↓の記述が追加されるため、既に実行済みのマイグレーションファイルに追記されてしまい、カラムが作成できてなかったのが原因。
xxxxx_create_users_table.php
// Jetstream 導入で、追加されたカラム
$table->foreignId('current_team_id')->nullable();
$table->text('profile_photo_path')->nullable();
  • なので、マイグレーション 初期化 → 再実行で、profile_photo_path カラムが作成でき、解決。
マイグレーションを初期化→再実行
% php artisan migrate:refresh
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sql_modeのonly_full_group_byを守っていないためにエラーになった件

問題

スクリーンショット 2020-11-04 午後1.44.09.png

グラフを表示しようとしたら下記のエラーが表示された。

Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'pfc-master_production.posts.created_at' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by: SELECT `posts`.`created_at` FROM `posts` WHERE `posts`.`user_id` = 8 GROUP BY date(created_at)
charts_controller.rb
@dates = dates_calorie.map { |dates| dates.created_at }

エラーログを読み解く

Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and 
Mysql2 :: Error:SELECTリストの式#1がGROUP BY句になく、

contains nonaggregated column 'pfc-master_production.posts.created_at' 
which is not functionally dependent on columns in GROUP BY clause;
GROUPBY句の列に機能的に依存していない非集計列 
'pfc-master_production.posts.created_at'が含まれています

this is incompatible with sql_mode=only_full_group_by: 
これはsql_mode = only_full_group_byと互換性がありません

SELECT SUM(`posts`.`calorie`) AS sum_calorie, date(created_at) AS date_created_at FROM `posts` WHERE `posts`.`user_id` = 8 GROUP BY date(created_at) ORDER BY created_at DESC

mapメソッドとは?

・mapメソッドを一言で表すと「各要素へ順に処理を実行してくれるメソッド」です。
・要素それぞれにアクセスし、指示した処理を行ってくれる。
【Rails入門】mapメソッドを完全攻略!配列操作の基礎を学ぼう

配列変数.map {|変数名| 具体的な処理 }

今回の場合
@dates = dates_calorie.map(配列変数) { |dates(変数名)| dates.created_at(処理) }

dates_calorieという配列には何が入っているのか?

dates_calorie = current_user.posts.group("date(created_at)").select(:created_at)

ログイン中ユーザーの投稿が1日に複数ある場合、その日ごとでグループ化しcreated_atカラムを取得したものが入っている。
すなわち、1日につき1つのcreated_atカラムが取得され、1日ごとのcreated_atカラム情報たちが配列の中に入っている。

このような情報を取得する理由は、1日ごとの体重を表示するグラフを実装するためだ。
1日に複数の投稿があった時に、グラフに表示するのは1日につき1つだけにしたいためだ。

✖️ [2020-11-09 00:00:00, 2020-11-09 00:11:00, 2020-11-10 00:12:00]
○ [2020-11-09 00:00:00, 2020-11-10 00:12:00, 2020-11-11 00:12:00]

mapメソッドを使ってどのような処理をしているのか?

@dates = dates_calorie.map(配列変数) { |dates(変数名)| dates.created_at(処理) }

上記で説明したとおり、配列dates_calorieには1日ごとのcreated_atたちが配列の中に入っていて、それをmapメソッドを使って配列の中の各要素をdatesという変数で1つずつ取り出し、created_atを取得している。

それらをgonでグラフを表示させるchart.jsに渡してグラフで使うために、インスタンス変数に代入している。

なぜ今回エラーになってしまったのか?

only_full_group_byがわからなかったため、リファレンスでonly_full_group_byについて調べた。

sql_mode = only_full_group_byとは?

GROUP BY 句で名前が指定されていない非集約カラムを、選択リスト、HAVING 条件、または (MySQL 5.6.5 以降で) ORDER リストが参照するクエリーを拒否します。

ONLY_FULL_GROUP_BY が有効な場合、次のクエリーは無効です。1 番目は、選択リスト内の非集約の address カラムが GROUP BY 句で名前を指定されておらず、2 番目は、HAVING 句の max_age が GROUP BY 句で名前を指定されていないため、ともに無効になります。

MySQLリファレンス ONLY_FULL_GROUP_BYとは?

sql_modeとは、MySQLの公式ルールのようなもので、only_full_group_byはMySQL5.6.5以降で新たに設定されたルールらしい。

・only_full_group_byというルールを守っていないためにエラーになったと考えられる。
・GROUP BY句で名前をしていないためエラーになってしまったと考えられる。

GROUP BY句とは?

・「GROUP BY」は、グループ化を行うために使用される命令。
・主に「種類ごとに集計関数を使用する」などといった形で使用するケースが多い。
・例えば「チームごとの人数を調べる」という場合など。
【SQL】GROUP BYで自在に集計!集計関数やHAVINGと合わせて使おう

select [表示する要素名] from [テーブル名] GROUP BY [グループ化する要素名];
SELECT team, COUNT(team) FROM user GROUP BY team;

対策

上記のことから、考えられる対策は2通りだ。

① SQLモードのonly_full_group_byを遵守するため、GROUP BY句を使ったコードに変更する。
② my.cnf(MySQLの設定ファイル)を変更し、only_full_group_byという制約を外す。

今回は②の方法を選択した。
理由は、時間がないためだ。

現在webエンジニアへの転職活動を始めるところで、とにかく一刻も早く企業の採用担当者の方にアプリを見せられる状態にしなくてはならない。
本当は①が望ましいが、今回は時間優先のため②を選択した。

今回の解決方法

my.confの場所を探す
[naota@ip-10-0-0-32 ~]$ mysql --help | grep my.cnf
                      order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf 

my.confを開く
[naota@ip-10-0-0-32 ~]$ vi /etc/my.cnf

読み込み専用ファイルを上書き保存する時に使う
:w !sudo tee %

my.confに下記の記述を追加した。

my.conf
[mysqld]
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

NO_ZERO_IN_DATENO_ZERO_DATEといったsql_modeを調べてみたが、これら自体にonly_full_group_byをOFFにする意味はないとわかった。
おそらく上記の設定でonly_full_group_byにしたのOFFにしたのではなく、only_full_group_byを含んで書いていないから設定されていないのだと考えられる。

only_full_group_by追加したら冒頭のエラーが発生したので、この仮説は正しかった。

上記の設定を反映させるためにMySQLを再起動した。

MySQL5.7にアップデートしたらonly_full_group_byでエラーになった

MySQLを停止
[naota@ip-10-0-0-32 ~]$ sudo systemctl stop mysqld.service

MySQLを起動
[naota@ip-10-0-0-32 ~]$ sudo systemctl start mysqld.service

グラフが表示されました!
スクリーンショット 2020-11-04 午後1.45.51.png

ソースコード

charts_controller.rbはグラフを表示させるためのコントローラー。

charts_controller.rb
def index
    # カロリー
    @sampleuser = User.find_by(id: 3)
    if user_signed_in?
      # 日付ごとで分けてカロリー合計を算出
      sum_calorie = current_user.posts.group("date(created_at)").sum(:calorie)
      # 日付ごとのカロリー合計がハッシュの形なので値を取得して配列に入れて変数に代入
      array_calorie = sum_calorie.values
    else
      sum_calorie = @sampleuser.posts.group("date(created_at)").sum(:calorie)
      array_calorie = sum_calorie.values
    end
    # gonを使ってデータをjs側に渡す
    gon.data = []
    # mapメソッドで日付ごとのカロリー合計を1つずつ取り出す
    # mapメソッドの使い方 → 配列変数.map {|変数名| 具体的な処理 }
    gon.data = array_calorie.map { |calorie| calorie }

    if user_signed_in?
      # 日付ごとにまとめてそのうちcreated_atカラムだけ取得。配列の形で格納されている
      dates_calorie = current_user.posts.group("date(created_at)").select(:created_at)
    else
      dates_calorie = @sampleuser.posts.group("date(created_at)").select(:created_at)
    end

    gon.date = []
    @dates = dates_calorie.map { |dates| dates.created_at }
    # each文で日付の表記を1つずつ取り出して変える
    @dates.each do |a|
      gon.date << a.strftime("%Y年%m月%d日")
    end


    # 体重
    if user_signed_in?
      gon.weight = current_user.posts.group("date(created_at)").select(:weight).map { |weight| weight[:weight] }
    else
      gon.weight = @sampleuser.posts.group("date(created_at)").select(:weight).map { |weight| weight[:weight] }
    end
  end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】MySQLでのユニットテストの環境設定

環境

Laravel Framework 5.8.38
My SQL 5.6

テスト用データベースの作成

まずテスト用データベースを作成する必要があるので、MySQLに入ります。

$ mysql -u root;

以下のコマンドで新しいテスト用データベースを作成。
今回はデータベース名をtest_laravel_db とします。

$ create database test_laravel_db;

作成後は一旦、MYSQLから出ます。

次に中身のテーブルを作成する必要があります。
テーブルは実際に使っているデータベースをmigrateによってコピーして使います。
その前準備としてプロジェクト直下にある.envファイルを同じ階層に複製して
ファイル名を.env.testing にしてください。
このファイルを作成する事によって、テスト実行時だけDBなどの環境がこちらのテストファイルに切り替わるようになります。

.env.testingファイルの内容を一部変更します。
変更する項目は以下の通りです。

APP_ENV=test_laravel_db
APP_KEY=
(↑空にします)
DB_DATABASE=test_laravel_db

尚、.envや.env.testingファイルの内容を変更した時はターミナルにてconfigキャッシュをクリアして下さい。

$ php artisan config:clear

先ほどAPP_KEYを空にした理由は、新しいAPP_KEYを発行すると自動で入力されるからです。
新しくAPP_KEYを発行するには以下のコードをターミナルで打ちます。

$ php artisan key:generate --env=testing

後ろに --env=testing を入れないと.env.testingの方にAPP_KEYが入りません。

上記の設定が完了したら、test_laravel_dbに向けてmigrateをします。

$ php artisan migrate --env=testing

ターミナルに成功結果が表示されると思いますが、念のためMySQL内で作れているか確認しましょう。

$ mysql -u root;
$ use test_laravel_db;
$ show tables;

実際のテーブルと同じ内容だったら、テスト用のデータベースの作成が完了です。

テストを正常に作動させるにはあと2つファイルを変更します。

プロジェクト直下にある phpunit.xmlを開きます。
下記のようにコードを追加します。

<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="test_laravel_db"/>

尚、開発環境によってserverタグをenvタグに変えなくてはいけないケースがあるようですが
私はserverタグのままで大丈夫でした。

次にconfig/database.phpを開きます。

'connections'の配列の中に'sqlite'や'mysql'などがありますが
'mysql'の内容を全てコピーして、'connections'内の中に以下のように収めます。

'test_laravel_db' => [
          'driver' => 'mysql',
          'url' => env('DATABASE_URL'),
          'host' => env('DB_HOST', '127.0.0.1'),
          'port' => env('DB_PORT', '3306'),
          'database' => env('DB_DATABASE', 'forge'),
          'username' => env('DB_USERNAME', 'forge'), 
          'password' => env('DB_PASSWORD', ''),
          'unix_socket' => env('DB_SOCKET', ''),
          'charset' => 'utf8mb4',
          'collation' => 'utf8mb4_unicode_ci',
          'prefix' => '',
          'prefix_indexes' => true,
          'strict' => true,
          'engine' => null,
          'options' => extension_loaded('pdo_mysql') ? array_filter([
           PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
           ]) : [],

設定が完了したら、ターミナルでテストを動かしてみます。

$ ./vendor/bin/phpunit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysqlで複数のorder by設定したときに少しハマった件

mysqlで複数のorder by指定

構文 is

SELECT *
FROM tables
ORDER BY name1, name2
;

知っている人からすると当たり前で何を今更って感じかもしれないのですが、
name2でソートしてから、name1でソートされるみたいでした。

動作確認のログ

vagrant@homestead:~$ mysql -V
mysql  Ver 14.14 Distrib 5.7.31, for Linux (x86_64) using  EditLine wrapper

mysql> create database sample;
Query OK, 1 row affected (0.00 sec)

mysql> use sample;
Database changed

mysql> create table users (id int, name1 varchar(10), name2 varchar(10));
Query OK, 0 rows affected (0.04 sec)
insert into users values 
(1, 'A', '1')
,(2, 'B', '1')
,(3, 'B', '1')
,(4, 'C', '1')
,(5, 'C', '2')
,(6, 'D', '1')
;
mysql> select *
    -> from users
    -> order by id;
+------+-------+-------+
| id   | name1 | name2 |
+------+-------+-------+
|    1 | A     | 1     |
|    2 | B     | 1     |
|    3 | B     | 1     |
|    4 | C     | 1     |
|    5 | C     | 2     |
|    6 | D     | 1     |
+------+-------+-------+
6 rows in set (0.00 sec)
mysql> select * from users order by name1, name2;
+------+-------+-------+
| id   | name1 | name2 |
+------+-------+-------+
|    1 | A     | 1     |
|    2 | B     | 1     |
|    3 | B     | 1     |
|    4 | C     | 1     |
|    5 | C     | 2     |
|    6 | D     | 1     |
+------+-------+-------+
6 rows in set (0.00 sec)

mysql> select * from users order by name2, name1;
+------+-------+-------+
| id   | name1 | name2 |
+------+-------+-------+
|    1 | A     | 1     |
|    2 | B     | 1     |
|    3 | B     | 1     |
|    4 | C     | 1     |
|    6 | D     | 1     |
|    5 | C     | 2     |
+------+-------+-------+
6 rows in set (0.00 sec)

その他

昇順、降順をいじる場合

name1順にならべたいけど、name2は降順でいてほしい。

mysql> SELECT * FROM users ORDER BY name1 ASC, name2 DESC;
+------+-------+-------+
| id   | name1 | name2 |
+------+-------+-------+
|    1 | A     | 1     |
|    2 | B     | 1     |
|    3 | B     | 1     |
|    5 | C     | 2     |
|    4 | C     | 1     |
|    6 | D     | 1     |
+------+-------+-------+
6 rows in set (0.00 sec)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む