20210124のMySQLに関する記事は8件です。

MySQLパーティションを使い、バッチ処理で日毎にデータを登録、取得するSQL

バッチ処理で、自動でパーティション追加、データ登録、パーティション削除をする処理を作り、それに必要なSQL部分を抜粋しました。

やりたいこと(実際に作った処理とは異なります)

  • あるゲームのプレイ結果を、日時で集計し、集計結果を画面に表示したい。
  • バッチ処理で、集計し、結果を過去1週間分を保持し、過去分は毎日削除したい。
  • 最新のパーティションからデータを取得したい。

事前準備

テーブルを作る

CREATE TABLE IF NOT EXISTS `game_point_log` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` INT NOT NULL COMMENT 'ユーザーID',
  `point` INT UNSIGNED NOT NULL COMMENT 'ポイント',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登録日時',
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
  PRIMARY KEY (`id`, `created_at`))
ENGINE = InnoDB
PARTITION BY RANGE COLUMNS(created_at)(
PARTITION p20210113 VALUES LESS THAN ('2021-01-13'),
  • パーティションの種類は、「RANGE」にします。
  • パーティションのキーには、created_atを使います。
  • パーティションのキーは、PRIMARY KEYにしないといけないので、id、created_atをプライマリーキーに指定します。
  • パーティションはバッチ処理で追加しますが、1つだけ事前に作っておきます。

バッチ処理

以下の順で処理します。

1. 追加するパーティション名を決める。

ここでは日付とします。(例:2021年1月23日なら、p20210123)

2. パーティションを追加する

ALTER TABLE game_point_log ADD PARTITION (
    PARTITION [パーティション名] VALUES LESS THAN ('[日付条件、どの日時までを対象にするか?]')
);

例:
ALTER TABLE game_point_log ADD PARTITION (
    PARTITION p20210124 VALUES LESS THAN ('2021-01-24')
);
  • 日付をキーに作ります。
  • LESS THANは、既存のパーティションの条件〜指定した日付までの間のデータを入れるという意味なので、今日の日付データを入れたい場合は、翌日の日付にすると良いです。

3. データを登録する

DB側でパーティションに入れてくれるので、通常のINSERT文で登録できます。

4. 古くなったパーティションを削除する

古くなったパーティションを一発で削除するコマンドはない(たぶん・・・。)
ので、今使っているパーティションを取得して、その中から削除したいパーティションを取得します。

EXPLAIN PARTITIONS SELECT * FROM game_point_log

SQLを実行すると、partitionsという項目で、カンマ区切りでパーティション名が取得できます。
例:p20210124,p20210125,p20210126

それをカンマで分割で配列化し並び替えたりして消したい等でパーティションを探し、

ALTER TABLE game_point_log DROP PARTITION [パーティション名]

例:
ALTER TABLE game_point_log DROP PARTITION p20210124

で削除します。

取得処理(最新のパーティションからデータ取得)

最新のパーティションを自動で選択して取得してくるコマンドはない(たぶん・・・。)
ので、取得する時も、古くなったパーティションを削除する時に使ったEXPLAINコマンドを使って、partitionsを取得し、自分で探します・・。

調べたパーティションを指定して、

SELECT * FROM game_point_log PARTITION ([パーティション名]);

: 
SELECT * FROM game_point_log PARTITION (p20210113);

とすると、対象のパーティションに入っているデータ(対象の日付データのみ)が取得できるので、where文なしで取得でき便利です。

参考URL

MySQL 5.6 リファレンスマニュアル

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

docker-composeでのMySQL環境構築(タイムゾーン、UTF-8対応)

はじめに

ローカル開発環境を立てるためにMySQLのコンテナを用意することがよくあるが、(主にタイムゾーンとUTF-8の対応で)毎回同じようなところで詰まって調べていたので、備忘録も兼ねて自分的な「いつものやつ」を残しておく。

結論

https://github.com/p5750/docker-mysql-sample

ディレクトリ構成

./docker/myqsl/data をvolumeとすることでDBのデータを永続化する。
なお、初回起動時にこのディレクトリに余計なファイルが入っているとコケることがあるので注意。
.gitkeep は置いておいて大丈夫だった。

構成
.
├── docker
│   └── mysql
│       ├── data
│       │   └── .gitkeep
│       └── Dockerfile
├── .env
├── .gitignore
└── docker-compose.yml

各ファイルの内容

Dockerfile

MySQLのバージョンはお好みで。
タイムゾーンを Asia/Tokyo に、ロケールは en_US.UTF-8 にしている。 日本語にしたければ ja_JP.UTF-8 にする。

Dockerfile
FROM mysql:8.0.23
RUN apt-get update && \
    apt-get install -y tzdata locales && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen
ENV LANG en_US.UTF-8

CMD ["mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]

.env

適宜書き換える。

.env
MYSQL_ROOT_PASSWORD=root_password
MYSQL_DATABASE=sample
MYSQL_USER=sample_user
MYSQL_PASSWORD=sample_password

MYSQL_PORT=3306
ADMINER_PORT=8080

docker-compose.yml

version は特に理由がなければ最新でよさそう。使えるものは公式のドキュメントでわかる。
Adminerはお好みで入れる。個人的にはローカル用なら入れといて損はしないかなと思っている。

docker-compose.yml
version: '3.8'
services:
  db:
    build: ./docker/mysql
    env_file:
      - .env
    ports:
      - "${MYSQL_PORT}:3306"
    volumes:
      - ./docker/mysql/data:/var/lib/mysql
  adminer:
    image: adminer:4.7.8-standalone
    ports:
      - "${ADMINER_PORT}:8080"

.gitignore

データ永続化用の docker/mysql/data と、認証情報が含まれる .env は必ずignoreする。
.idea はJetBrainsのIDE用。VSCodeなら .vscode になる。

.gitignore
docker/mysql/data
.env
.idea

コマンド

Dockerの基本操作は本稿の趣旨ではないので割愛する。

DBを抹消して作り直すとき

ゴミが残るので、必ず先に down する。

docker-compose down                    # コンテナを止めて削除する

rm -rf ./docker/mysql/data             # volumeでマウントしているデータをディレクトリごと消す
mkdir ./docker/mysql/data              # ディレクトリを作り直す
touch ./docker/mysql/data/.gitkeep     # gitに.gitkeepをコミットしているなら作り直す。
                                       #   ※差分出てるはずなのでgit resetとかで復旧してもよい。

docker-compose up -d                   # 再度立ち上げる。Dockerfileに変更が入っていたら --build も必要

リンク集

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

AWS_WEB3層環境構築③

前回の記事
https://qiita.com/shinichi_yoshioka/items/e358f57a3ecb7735c091
前々回(初回)の記事
https://qiita.com/shinichi_yoshioka/items/7226b9ebaad06c569c80
構成図
構成図_v4.png

前回の続きをやっていく。
VSCodeすら使ったことなかったが、VSCodeの拡張機能にdrawioというものがあって、
カッコ良いアイコンなどが使えるのを教えてもらった。
さっそく使ってみたが、色のセンスはお察しだ。

初回記事ではDBの切り替えをしたいと書いたが、まずは正常な状態を作りたい。
WEBサーバにはリバースプロキシの設定(To:APサーバ)をし、APサーバでJSPを使って、
DBを参照できることを正常な状態とすることにした。

◆リバースプロキシの設定
対象:WEBサーバ
/etc/nginx/conf.d 配下にserver.confというファイルを作る。

cd /etc/nginx/conf.d
vi server.conf

server.confの中身は、APサーバのプライベートアドレス:ポート番号(8080)を指定した。

#server.conf
server{
   location / {
        proxy_pass    http://172.16.3.246:8080/;
    }
}

nginxのサービスを再起動(systemctl restart nginx)して、ブラウザにWEBサーバのパブリックアドレスを入力し、
APサーバのtomcatのページが表示されることを確認した。
[自宅PC]-[Internet]-[WEBサーバ]-[APサーバ] ←ここまでのイメージ
tomcat5.PNG

◆JDBC for MySQLのインストール
対象:APサーバ
APサーバにJDBCドライバーをインストールしようと思ったのだが、AmazonLinux用がなかった^^;;;
ということで、前回作ったAPサーバはぶっ壊して、再度RedHatで作りなおして、Tomcatのインストールまでは済んだとこ。
これがクラウドの良いところである。(オンプレでOS選定ミスってたら、首飛んでた)
EIPを使っていたので、記事の不整合もない!
OSやJavaのバージョン、Tomcatの状態はこんな感じ。
tomcat3.PNG
MySQLコミュニティサーバからJDBCドライバーのRPM(RHEL用)をダウンロードしインストールしようとしたが、
依存関係でopenjdk-headlessが必要と言われた。
yumでopenjdk-headlessをインストール後に、mysql-connector-javaをRPMコマンドでインストールした。

wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.23-1.el8.noarch.rpm
yum install java-1.8.0-openjdk-headless -y
rpm -ivh mysql-connector-java-8.0.23-1.el8.noarch.rpm
rpm -qa | grep mysql

tomcat4.PNG

◆サンプルデータベースの用意
対象:DBサーバ
参照するためのDBを用意するため、以下からworld databaseをインポートすることにした。
https://dev.mysql.com/doc/index-other.html
まずはDBサーバにssh接続し、wgetコマンドにてダウンロードした。
gz形式だったため、gunzipコマンドにて解凍した。

wget https://downloads.mysql.com/docs/world.sql.gz
gunzip world.sql.gz

次にMySQLにログインし、SOURCEコマンドにて、world.sqlをインポートした。

mysql> SOURCE root/world.sql;/r

データベースに"world"がインポートされた。

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| TEST               |
| auth               |
| mysql              |
| performance_schema |
| sys                |
| world              |
+--------------------+

データベースをworldに切り替え、テーブルが存在することを確認した。

mysql> use world;

mysql> show tables;
+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+
3 rows in set (0.00 sec)

MySQLはデフォルトで、外部からのアクセスを許可されていないため、
以下の権限追加をした。
権限追加後は、FLUSHにて権限を再読み込みした。
※*(すべてのデータベース).*(すべてのテーブル)を認識できるようにユーザ名@接続元IPアドレス(APサーバ)へ権限を与える

mysql> GRANT all ON *.* TO root@'172.16.3.246' IDENTIFIED BY 'MySQLのパスワード';     
mysql> FLUSH PRIVILEGES;    ←権限関係の再読み込み

◆JDBCドライバの配置とサンプルJSPの作成
対象:APサーバ
JDBCドライバーは/lib配下に配置しないとダメらしく、初期配置から移動させた。

mv /usr/share/java/mysql-connector-java.jar /opt/tomcat/lib/

tomcat5.5.PNG
JSPについては初心者でggりまくった結果、JSPも配置が重要らしく、
/opt/tomcat/webapps配下にjspディレクトリを作成し、パーミッションを750にし、所有者はtomcatに変更した。

cd /opt/tomcat/webapps
mkdir jsp
chown tomcat:tomcat jsp
chmod 750 jsp

tomcat6.PNG

/opt/tomcat/webapps/jsp配下には、test.jspを作成したが、
JSPの知識がなさすぎて、以下URLを参考にさせていただいた。
https://michael-e29.hatenadiary.org/entry/20111107/1320630444

jdbc:mysql://172.16.3.6:3306/world
↑DBサーバのアドレス:ポート番号/データベース名
※worldはデータベース名。
※ID,Name,CountryCode,District,Populationはデータベースのカラム名

###test.jspの中身###
<%@ page contentType="text/html; charset=utf-8" import="java.sql.*" %>

<html>
<head>
<title>DB参照テスト</title>
</head>

<body>
<h1>DB参照テスト</h1>

<tr>
<td>ID</td> <td>Name</td> <td>CountryCode</td> <td>District</td> <td>Population</td>
</tr>

<%
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn=DriverManager.getConnection("jdbc:mysql://172.16.3.6:3306/world?" +
        "user=root&password=MySQL@001&useUnicode=true&characterEncoding=utf-8");
    Statement st=conn.createStatement();
    ResultSet res = st.executeQuery("select * from city;");

    while(res.next()){
        out.println("<tr>");
        out.println("<td>" + res.getString("ID") + "</td>");
        out.println("<td>" + res.getString("Name") + "</td>");
        out.println("<td>" + res.getString("CountryCode") + "</td>");
        out.println("<td>" + res.getString("District") + "</td>");
        out.println("<td>" + res.getString("Population") + "</td>");
        out.println("</tr>");
    }
    st.close();
    conn.close();

%>
</table>
</body>
</html>

APサーバとDBサーバの連携の準備ができたので、APサーバのTomcatサービスを再起動。
あとDBサーバのmysqldサービスを再起動し、ブラウザにJSPのパスを入力する。
ちなみにJSPファイルを置いたパスは /opt/tomcat/webapps/jsp/test.jsp だが、
ブラウザでは/webapps/配下を入力する。
http://WEBサーバのパブリックアドレス/jsp/test.jsp
DB参照.PNG

ぐちゃぐちゃだけど、一応DB参照できたので
一応WEB3層構成はできた・・・。
めちゃ時間かかったけど、つまづきながらハマりながら色々覚えれたので良かったです。
次回はNATゲートウェイの作成と、AnsibleでJSP内の文字列を置換することで
参照するDBを切り替えようと思います。

つづく

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

Docker環境のDB(MySQL)コンテナに接続する方法

やりたいこと

Dockerで作成したDB(MySQL)コンテナに入ってデータベースをいじりたい。:frowning2:

ソースコード

長いので折りたたんでいます。

docker-compose.yml
docker-compose.yml
#docker-compose.ymlのバージョン
version: "3.8"
#docker volumeの設定
volumes:
  docker-volume:

#services以下に各コンテナの設定を書く
services:
  #Webサーバーのコンテナ
  web:
    image: nginx:1.18
    ports:
      - "8000:80"
    depends_on:
      - app
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
  #アプリケーションのコンテナ
  app:
    build: ./docker/php
    volumes:
      - .:/var/www/html
  #データベースのコンテナ
  db:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: db_name
      MYSQL_USER: db_user
      MYSQL_PASSWORD: db_password
      MYSQL_ROOT_PASSWORD: root
      TZ: "Asia/Tokyo"
    volumes:
      - docker-volume:/var/lib/mysql
  # phpMyadominのコンテナ作成
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    depends_on:
      - db
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOSTS=db
      - PMA_USER=db_user
      - PMA_PASSWORD=db_password
    ports:
      - "8080:80"
    volumes:
      - ./docker/phpmyadmin/sessions:/sessions

.env
.env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:8EM1Yt7LJjZSRaEjtdhXCVShJEI0GGo6FG6IZHXCuis=
APP_DEBUG=true
APP_URL=http://localhost:8000

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=db_name
DB_USERNAME=db_user
DB_PASSWORD=db_password

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie
SESSION_LIFETIME=120

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

試したこと

rootのパスワードを使っても接続できない。

terminal
$ docker-compose exec db bash
root@74539701c71c:/# mysql -u user -p
Enter password: 
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

ちなみにホストとポート番号を確認すると...

         Name                   Command           State           Ports         
--------------------------------------------------------------------------------
laravel_todo_api_app_1   docker-php-entrypoint    Up      9000/tcp              
                         php-fpm                                                
laravel_todo_api_db_1    docker-entrypoint.sh     Up      0.0.0.0:3306->3306/tcp
                         mysqld                           , 33060/tcp           
laravel_todo_api_phpmy   /docker-entrypoint.sh    Up      0.0.0.0:8080->80/tcp  
admin_1                  apac ...                                               
laravel_todo_api_web_1   /docker-entrypoint.sh    Up      0.0.0.0:8000->80/tcp  
                         ngin ...                                               

解決法

$ mysql -h 127.0.0.1 -P 3306 -u root -p

ホストはlocalhostと指定するとローカルマシンのmysqlソケットを探しに行くのでエラーになる。
ポート番号の指定は大文字で-P

参考

参考にさせていただきました。ありがとうございました。
【Docker】コンテナ内のデータベース閲覧(ローカル,EC2)
docker内のMySQLに接続したい

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

XAMPPの環境設定でFound Path: " " -k runservice Expected Path: " " -k runservice とエラーが出た時の対処法

はじめに

Windows環境でXAMPPの環境構築をした時にハマった内容と解決した方法をまとめます。

環境

Windows 10
XAMPP 8.0.1

ハマった内容

XAMPPで環境構築をしたところ、Apache,mysqlともに下記エラーが出て使えない。

スクリーンショット 2021-01-24 13.20.21.png

やってみたこと

以前(私以外の方が)ApacheとMySQLを同PCにインストールして使っていたようで、
アンインストールがしっかりされているかの確認と対象ファイルの削除をした。
しかし、エラーの表示内容は変わらずだった。

参考にしたページ

https://stackoverflow.com/questions/30547759/apache-service-detected-with-wrong-path/52457073

スクリーンショット 2021-01-24 13.25.53.png

解決した方法

コマンドプロンプトで下記実行しました。

C:¥WINDOWS¥system32>sc delete Apache2.4
C:¥WINDOWS¥system32>sc delete MySQL

結果的にこれで問題が解決しました。

まとめ

どうやらアンインストールしてもファイルが残ってたようです。
無事にXAMPPが使えるようになりました。
メキシコの方に感謝します。

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

DockerでPHP環境を作る(mac)

はじめに

勤め先でもPHPを使っており自宅でPHPを勉強するために、ローカルでPHP開発環境を整えることにしました。
MAMP(マンプ)でも良いかなと思いましたが、せっかくなのでDockerで作ってみます。
(開発中に色々詰まってしまったところがあるので、備忘録がわりのメモです。)

手順

  1. Dockerによる環境構築
  2. ファイル・ディレクトリの準備
  3. 各ファイルの作成
  4. コンテナを起動
  5. サイトにアクセス

1. Dockerによる環境構築

Dockerを使うには、「Docker for Mac」のインストールが必要です。
Qitaに素敵な記事がたくさんあるので、ここでの説明は省略します。

今回はDockerで以下のコンテナを使います。

  • nginx
  • PHP
  • MySQL
  • PHPMyAdmin

使用するコンテナが複数あるので、今回は「Docker Compose」を使って環境を作っていきます。

2. ディレクトリ・ファイルの準備

Dockerを起動する場所を作成しましょう。
まずはフォルダを1つ用意し、次のようなファイル構造となるようにしてください。
用意するフォルダは何でも良いのですが、今回は「php-test」という名前で作成します。

php-test
├── docker-compose.yml
├── mysql
│   └── data
├── nginx
│   └── nginx.conf
├── php
│   ├── Dockerfile
│   └── php.ini
└── www
   └── html
       └── index.php

3. 各ファイルの作成

用意したファイルの中身を作成していきます。
中身を作成するファイルは、docker-compose.ymlnginx.confDockerfilephp.iniindex.phpの5つです。

docker-compose.yml

version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./www/html:/var/www/html
    depends_on:
      - php

  php:
    build: ./php
    volumes:
      - ./www/html:/var/www/html
    depends_on:
      - db

  db:
    image: mysql:5.7
    ports:
      - 13306:3306
    volumes:
      - ./mysql/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    ports:
      - 8888:80
    depends_on:
      - db

nginx.conf

server {
  listen 80;
  server_name _;

  root  /var/www/html;
  index index.php index.html;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  location / {
      try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
      fastcgi_pass php:9000;
      fastcgi_index index.php;    
      fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      include       fastcgi_params;
  }
}

Dockerfile

FROM php:7.2-fpm
COPY php.ini /usr/local/etc/php/
RUN docker-php-ext-install pdo_mysql

php.ini

date.timezone = "Asia/Tokyo"

index.php

設定情報を確認するためにphpinfo();としていますが、なんでもいいです。

<?php
phpinfo();
?>

4. コンテナを起動

いよいよコンテナ起動です。
ターミナルを開き、docker-compose.ymlがあるフォルダ(今回はphp-test)に移動します。

下記コマンドで、php-testフォルダに移動。

cd php-test

下記コマンドで、コンテナを起動。

docker-compose up -d

以下のような記述がターミナル内に表示されたら、起動は成功です。

Creating php_01_db_1 ... done
Creating php_01_phpmyadmin_1 ... done
Creating php_01_php_1        ... done
Creating php_01_nginx_1      ... done

なお下記コマンドで、コンテナを停止できます。

docker-compose stop

5. サイトにアクセス

最後にサイトにアクセスします。
それぞれ下記画像のような画面が表示されたら、成功です。
お疲れ様でした。

Webサイト:http://localhost:8080/
localhost_8080_config.php.png

管理画面:http://localhost:8888/
localhost_8888_ (1).png

ユーザー名:root
パスワード:secret

以上です。
最後までお読みいただき、ありがとうございました。


お世話になったサイト

【補足】Qitaに投稿されているDockerインストール記事

下記の中から自分に合いそうなものを1つ選んで、進めてみると良いと思います。

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

【Laravel】テーブル名が長い時に、Laravel7から導入された外部キー制約をつけるマイグレーションエイリアスを使う方法

久しぶりのQiitaへの投稿です。

未経験からエンジニアに転職をして、2年目に突入したバックエンドエンジニアです。
DBに外部キー制約を追加をしようと思い、Laravel7から使えるエイリアスでmigrationファイルを作成していました。
ただ、テーブル名が長い時にハマってしまい、できる人には当たり前すぎる内容かもしれませんが、振り返りの意味も込めて投稿します。

開発環境

  • 言語:PHP7.4.8
  • フレームワーク:Laravel7.28.3
  • DB:MySQL8.0.22

テーブル名が短い時

目標のテーブル構造

公式ガイドでもよく使われるような下記のようなテーブルを作り、postsテーブルのuser_idに外部キー制約をかけることを目指します。

image.png

Laravel7より前の時の記法

Laravel7より前のバージョンで外部キー制約をつけるためには、下記のようにmigrationファイルを作成をしていました。
一度user_idのカラムを作成してから別の行で外部キー制約をつけています。

        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users');
        });

image.png

そんなに難しくもないですが、少しだけ面倒です。

Laravel7以降で使えるエイリアスを使用

そこでLaravel7以降では下記のようなエイリアスができ、foreignIdconstrained()を使えば一行でも外部キー制約をかけることができるようになりました。
短くて楽ですね。

        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained();
            $table->timestamps();
        });

image.png

テーブル名が長い時

目標のテーブル構造

問題のパターンです。下記のようなテーブルを想定して試してみます。
名前は適当に僕が好きなスポーツからとってきましたが、とりあえず2つのテーブル名が長く文字数の合計が一定数を超えると同じ現象になります。

image.png

Laravel7以降で使えるエイリアスを使用

せっかく新しい記法があるということなので、foreignIdを使った新しいエイリアスを使ってやってみます。

        Schema::create('national_basketball_association_players', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->foreignId('national_basketball_association_team_id')->constrained();
            $table->timestamps();
        });

キー名が長すぎるようで下記のようなエラーが出てしまいました。

  SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'national_basketball_association_players_national_basketball_association_team_id_foreign' is too long (SQL: alter table `national_basketball_association_players` add constraint `national_basketball_association_players_national_basketball_association_team_id_foreign` foreign key (`national_basketball_association_team_id`) references `national_basketball_association_teams` (`id`))

通常であれば、Laravelのスキームビルダの制約命名規則に従い、自動的に外部キー制約の名前は変換されるため、外部キー制約名は下記のようになります。

(接続元テーブル名)_(外部キー名)_foreign

しかし、今回この命名規則に当てはめようとすると、national_basketball_association_players_national_basketball_association_team_id_foreignという名前になりますが、これは文字数が長すぎるということでエラーになってしまいました。

Laravel7以降で使えるエイリアスを使用(nameでキー名変更)

同じようなことが他の制約を作る時でも起こっていたのを思い出します。
nameメソッドを使えば直せたような気がするのでnameメソッドを使って試してみました。
外部キー制約名は省略した、basketball_players_team_id_foreignを設定します。

        Schema::create('national_basketball_association_players', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->foreignId('national_basketball_association_team_id')->constrained()->name('basketball_players_team_id_foreign');
            $table->timestamps();
        });

先程と違い、今度はエラーも起こらず、migrationファイルが無事に通りました。
念のためにdatabaseを確認してみます。

image.png

外部キー制約がついていません。。。
多分routeを作成する時に使うnameメソッドと勘違いしていたのですが、ここで結構ハマってしまいました。

migration関連でnameメソッドは用意されていなさそうでした。

エイリアスメソッドのコードを確認

改めて今回のエイリアスのforeignIdメソッドを確認してみます。

    public function foreignId($column)
    {
        $this->columns[] = $column = new ForeignIdColumnDefinition($this, [
            'type' => 'bigInteger',
            'name' => $column,
            'autoIncrement' => false,
            'unsigned' => true,
        ]);

        return $column;
    }

引数がカラム名のみで1つしか受け付けていないので、キー名を短くして渡そうとしても無理そうです。

また、foreignIdの後に使っているconstrainedも確認してみます。

    public function constrained($table = null, $column = 'id')
    {
        return $this->references($column)->on($table ?? Str::plural(Str::beforeLast($this->name, '_'.$column)));
    }

こちらも外部キー制約名を渡す引数がなく、無理そうです。

結果:Laravel7以降で使えるエイリアスを使用(indexでキー名変更)

下記のようにindexメソッドを使って、外部キー名を指定するとうまくいきました。

        Schema::create('national_basketball_association_players', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->foreignId('national_basketball_association_team_id')->constrained()->index('basketball_players_team_id_foreign');
            $table->timestamps();
        });

公式ガイドを見てみるとindexメソッドは「基本的なインデックス追加」とだけ書いてありましたが、外部キー制約の名前を作成をする際にも使えるようです。
uniqueキー作るときは、第2引数にインデックスキー名指定できるのに対して、外部キー制約の場合はindexメソッドを使わないといけなさそうなので書き方が結構違いますね。

image.png

https://readouble.com/laravel/7.x/ja/migrations.html

※何か誤解していたり、他にいい方法があれば教えていただけると助かります!!!

Laravel7より前の時の記法でやる方法

もちろん元々使っていた記法を使って、foreignメソッドの中の第2引数に外部キー制約名を指定してもうまくいきます。
このやり方は元々やったことあったのですが、ハマるくらいなら最初からこっち使っておけばよかったなと思いました、、、

        Schema::create('national_basketball_association_players', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->unsignedBigInteger('national_basketball_association_team_id');
            $table->foreign('national_basketball_association_team_id', 'basketball_players_team_id_foreign')->references('id')->on('national_basketball_association_teams');
            $table->timestamps();
        });

image.png

感想

チュートリアルで使うようなusersとかpostsとかのテーブルと違い、実務ではテーブル名が長くなるケースは命名規則によりけりだと思いますが、結構発生しそうなイメージなので意外と困ることもあるかなと思います。

また、今回少し深く調べてみて、フレームワークのコードをより深くみるようにしたのはよかったなと思いました。

それにしてもnameメソッドを使ったときに何故かmigrationファイル通るのに外部キー制約がついていないのは結構たちが悪かったです。。。

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

railsで簡単ログイン(ゲストユーザーログイン機能)を実装する方法

今回はポートフォリオを見てもらう確率を上げるために必須な、簡単ログインの実装方法を書いていきます。

開発環境

Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系

前提

devise導入済み
通常の新規登録、ログイン、ログアウト機能に関しては実装済み
投稿機能ができるアプリを作っており、postsモデルとpostsコントローラーが登場しますが、ご自身の例に置き換えてご理解ください。

ルーティング設定

まずはルーティングを設定します。

# HTTPメソッドはpostで、'/posts/guest_sign_in'というURLでpostsコントローラーのnew_guestアクションを参照する
post '/posts/guest_sign_in', to: 'posts#new_guest'
routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "posts#index"
  # 次の1行を追加
  post '/posts/guest_sign_in', to: 'posts#new_guest'
  resources :posts do
    resources :comments, only: [:create, :destroy]
    collection do
      get 'search'
    end
  end
  resources :users, only: :show
  post 'like/:id' => 'likes#create', as: 'create_like'
  delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
end

コントローラーにnew_guestアクションを記述

それでは次にpostsコントローラーにアクションを定義していきます。
deviseのメソッド find_or_create_by や sign_in を使っていますので、気になる方は調べてみてください。
また、パスワードは SecureRandom.alphanumeric を用いてランダム生成しつつも、全て英字、全て数字のパスワードが生成されないように工夫しています。

参考:https://qiita.com/take95/items/61b181449b38d4415fc3

posts_controller.rb
def new_guest
  # emailでユーザーが見つからなければ作ってくれるという便利なメソッド
  user = User.find_or_create_by(email: 'guest@example.com') do |user|
  # 自分はユーザー登録時にニックネームを必須にしているのでこの記述が必要
  user.nickname = "ゲスト"
  # 英数字混合を必須にしているので、ランダムパスワードに、英字と数字を追加してバリデーションに引っかからないようにしています。
  user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
  end
  # sign_inはログイン状態にするデバイスのメソッド、userは3行目の変数userです。
  sign_in user
  # ログイン後root_pathに飛ぶようにしました。
  redirect_to root_path
end

また、deviseでメール確認機能を実装済みの場合、user.confirmed_at = Time.nowを追加する必要があります。

posts.controller.rb
def new_guest
  user = User.find_or_create_by(email: 'guest@example.com') do |user|
  user.nickname = "ゲスト"
  user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
  # 以下一文を追加
  user.confirmed_at = Time.now
  end
  sign_in user
  redirect_to root_path
end

ビューを追加

簡単ログインのリンクをヘッダーに追加します。
自分の場合は部分テンプレートに切り出しているので、その部分テンプレート内の記述を編集します。

コンソールでrails routesを打ってパスを確認しましょう。その後ヘッダーに次の1文を追加します。

<li><%= link_to 'ゲストログイン(閲覧用)',posts_guest_sign_in_path, class: "guest-login", method: :post %></li>

この時、HTTPメソッドをつけ忘れないように注意してください

_header.html.erb
<nav>
  <ul class='lists-right'>
    <% if user_signed_in? %>
    <li><%= link_to current_user.nickname, user_path(current_user.id), class: "user-nickname" %></li>
    <li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "logout" %></li>
    <% else %>
  # 以下1文を追加
    <li><%= link_to 'ゲストログイン(閲覧用)',posts_guest_sign_in_path, class: "guest-login", method: :post %></li>
    <li><%= link_to 'ログイン',new_user_session_path, class: "header-login" %></li>
    <li><%= link_to '新規登録',new_user_registration_path, class: "sign-up" %></li>
    <% end %>
  </ul>
</nav>

以上で簡単ログイン(ゲストユーザーログイン機能)を実装できました。
参考になれば幸いです。

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