20200525のMySQLに関する記事は3件です。

phpMyAdminでテーブル構造を表示させる方法

phpMyAdminのバージョン5.0.1で言語が英語だと構造が表示されるのに、下記のように日本語に変更すると表示されない場合の対処法です。
202005251134.png
こちらの記事を参考にしています。
解決!phpMyAdmin テーブル構造の内容が表示されない問題

作業環境

OS : Windows 10 HOME
phpMyAdmin : 5.0.1

Xamppのインストール方法

インストール方法については、別記事にて解説しています。
【環境構築】Windows10にXAMMPをインストール方法

解決方法

「Util.php」書き換える

Xamppを起動している場合は、停止します。
C:\xampp\phpMyAdmin\libraries\classesに移動します。
202005251126.png
Util.phpをエディタで開きます。
202005251128.png
1617行目の記述$ret = strftime($date, (int) $timestamp);を以下のように変更します。

$ret = strftime('%Y-%B-%d %H:%M', (int) $timestamp);

202005251127.png
Xamppを再起動して、phpMyAdminを開くと下記のように表示されました。
202005251125.png

表示されない原因

下記によると、表示されない原因は以下のとおりです。

strftime()の第一引数に、$dateの中身として%Y 年 2 月 %d 日 %H:%Mが渡されている。これ自体は仕様通りで問題が無いように見える。

しかし、strftimeを処理する際に、依存するWindowsのCライブラリでは、文字列を一度Shift-JISに変換している。つまり、UTF-8→SJIS→UTF-8の変換が行われている。ここで、漢字の「月」の字が文字化けを起こし、不正なマルチバイト文字と認識され、strftime()がfalseを吐いている、というのが真相である。

引用元:phpMyAdmin5.0.1の「状態」タブがFatal Errorで表示されないバグ

以上、phpMyAdminでテーブル構造を表示させる方法でした。

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

丁寧にNginx + MySql + Rails6 (Using Webpacker) on Dockerな開発環境を構築します。

Nginx + MySql + Rails6 (Using Webpacker) on Dockerな開発環境を構築します。
新しくプロジェクトから作成する機会なんてそうそうないので、以下手順をできるだけ要点を抑えつつまとめました。

今回しないこと。

プロジェクトの立ち上げにコミットするために以下についてはしない。

  • 実際のコーディングはしない。1
  • RSpec, Rubocopのようなコードの品質を上げるための設定はしない。
  • 開発効率を上げるためのgem登録などは設定しない。
  • i18nのような初期に設定するようなものも今回は設定しない。

asset pipelineの代わりにWebpackerを使用しています。なので、Webpackerについてはこの辺をざっと見ておくとよいです。

各バージョンについて

name version
Ruby(on docker) 2.6.5
Ruby on Rails 6.0.3
node 10.19.0
yarn 1.22.4
bundler 2.1.4
webpacker 5.1
docker 19.03.8
docker-compose 1.25.5
MySQL(on docker) 5.7
nginx(on docker) 1.17.3

※ (on docker)は、Dockerfileで指定しているバージョンです。

Rails6ではyarnを利用するので、事前にインストールしておく。

brew install yarn

1. プロジェクト構築手順

1-1. Githubにプロジェクトを作成し、git cloneしておく。

# githubでプロジェクトを作成しておく。
$ git clone git@github.com:{github_acount}/{project_name}.git
$ cd {project_name}

1-2. ruby,nodeのバージョンを指定する。

rubyとnodeのバージョンをしているするために.*-versionファイルを作成します。
※ anyenvを事前にbrew install anyenvしておくとよい。

また、rbenv, nodenvの設定については公式を参考にするとよい。

- 参考:GitHub - anyenv/anyenv: All in one for **env

1-2-1. Rubyバージョン指定

ruby : 2.6.5を指定

# インストールできるバージョンをの一覧を確認する。
$ rbenv install -l 
$ rbenv install 2.6.5
$ rbenv local 2.6.5
$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

1-2-2. Nodeバージョン指定

node : v10.19.0を指定

# インストールできるバージョンをの一覧を確認する。
$ nodenv install -l
$ nodenv install 10.19.0
$ nodenv local 10.19.0
$ node -v
v10.19.0

1-3 不要なファイルをコミットできないように.gitignoreを作成する。

.gitignoreを作成し、git commitしておく。
※ .gitignoreを作成するために事前にbrew install giboしておくとよい。

# .gitignoreを作成
$ gibo dump macOS Node Ruby Rails VisualStudioCode > .gitignore

1-4. GemfileとGemfile.lockを作成する。

$ gem install bundler
Successfully installed bundler-2.1.4
Parsing documentation for bundler-2.1.4
Done installing documentation for bundler after 1 seconds
1 gem installed

次に、Gemfileを作成する。

# Gemfileを作成する。
$ bundle init

Gemfileファイルを開きrailsのgemを登録します。
バージョンについてはrails | RubyGems.orgを確認する。

  • ./Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

- # gem "rails"
+ gem 'rails', '~> 6.0', '>= 6.0.3'

bundle installを実行しGemfile.lockファイルを作成します。

$ bundle install
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using rake 13.0.1
Using concurrent-ruby 1.1.6
...
Using rails 6.0.3
Bundle complete! 1 Gemfile dependency, 43 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Ruby on Railsプロジェクトを作成するための準備が完了しました。

2. Ruby on Railsアプリケーションを構築する。

Ruby on Railsのアプリケーションを作る。

$ bundle exec rails new . --database=mysql --skip-git --skip-test-unit --skip-sprockets

...
├─ webpack-dev-server@3.11.0
└─ ws@6.2.1
✨  Done in 3.19s.
Webpacker successfully installed
オプション 説明
. カレントディレクトリ名がプロジェクト名
--database=mysql databaseはmysql
--skip-git .gitignoreを組み込まない(事前に作成済み)
--skip-test-uni Test::Unitを組み込まない
--skip-sprockets sprocketsを利用しない

2-1 Ruby on Railsの設定する。

2-1-1 webpackerのバージョンを最新にする。

  • ./Gemfile
- gem 'webpacker', '~> 4.0'
+ gem 'webpacker', '~> 5.1', '>= 5.1.1'

2-1-2 MySQLを利用するためにdocker-composeを用意する。

# mysqlのDockerfileとmy.cnfを用意する。
# docker-compose.ymlを用意する。
$ mkdir -p ./docker/mysql
$ touch ./docker/mysql/{Dockerfile, my.cnf}
$ touch ./docker-compose.yml

各ファイルの記述はこちら。

  • ./docker/mysql/Dockerfile
FROM mysql:5.7

RUN touch /var/log/mysql/mysqld.log
  • ./docker/mysql/my.cnf
[mysqld]
collation-server=utf8mb4_general_ci
character-set-server=utf8mb4
skip-character-set-client-handshake
init-connect = SET NAMES utf8mb4
default-time-zone=+9:00
explicit-defaults-for-timestamp=1
general-log-file=/var/log/mysql/mysqld.log

[client]
default-character-set=utf8mb4
  • ./docker-compose.yml
version: '3.3'
services:
  database:
    build:
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: {project_name}_development
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./log/mysql:/var/log/mysql

mysqlアカウントでrootを利用する場合は、MYSQL_ROOT_PASSWORDを設定するだけでよい。
MYSQL_DATABASEは開発で利用するデータベース名にする。

2-1-3 Ruby on Railsのdatabase.ymlを修正する。

database.ymlを修正してrailsからmysql on dockerに接続できるようにする。

  • ./config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: 127.0.0.1

development:
  <<: *default
  database: {project_name}_development

logを取得するために2

# Nginx用のlog吐き出し用
$ mkdir ./log/mysql
$ touch ./log/mysql/mysqld.log

2-1-4 Ruby on Railsのディレクトリ構成を修正する。

Webpackerを利用してJS,CSS,Image等を扱うための構成にします。

$ mkdir -p ./app/javascript/{stylesheets, images}
$ touch ./app/javascript/stylesheets/application.scss

2-1-5 [S]CSSファイル、ImageをWebpackerで扱う

  • ./app/javascript/packs/application.js
- // const images = require.context('../images', true)
- // const imagePath = (name) => images(name, true)
+ const images = require.context('../images', true)
+ const imagePath = (name) => images(name, true)
+ import "../stylesheets/application";

2-1-6 stylesheet_pack_tagに切り替える。

  • ./app/views/layouts/application.html.erb
-    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

2-1-7 check_yarn_integrityの設定をfalseにする。

  • config/webpacker.yml
development:
  <<: *default
  compile: true

  # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
-  check_yarn_integrity: true
+  check_yarn_integrity: false

2-1-8 extract_css: false -> trueにする。

開発中でもCSSファイルを作成してほしいのでextract_cssをtrueにします。

- config/webpacker.yml

# Extract and emit a css file
- extract_css: false
+ extract_css: true

2-1-8 assetsを生成しないようにする

  • ./config/initializers/generators.rb
Rails.application.config.generators do |g|
  g.assets false
  g.test_framework false
end

2-2 MySQL on Dockerを起動する。

# mysqlを起動する。
$ docker-compose -d up database

mysqlを起動したらmysqlの照合順序を確認する。

mysql> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

2-3 ローカルPCからRuby on Railsを起動する。

# railsを起動する。
$ bin/rails s --port=3000 -b 0.0.0.0

以下の画面が表示されればOK。



3. Ruby on Rails on Dockerを構築する。

3-1. Dockerfileを作成する。

  • ./Dockerfile
FROM ruby:2.6.5

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update && apt-get install -y \
    build-essential \
    nodejs \
    yarn \
    default-mysql-client \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

ENV LANG ja_JP.UTF-8

RUN mkdir -p /app
WORKDIR /app

COPY Gemfile Gemfile.lock ./

RUN gem update bundler && bundle install

COPY . .

RUN yarn install --check-files
RUN yarn webpack --config ./config/webpack/development.js

CMD ["rails", "server", "-b", "0.0.0.0"]

3-2. docker-compose.ymlにapp:を追加する。

environmentで環境変数[RAILS_HOST]にdatabaseを指定します。
- ./docker-compose.yml

version: '3.3'
services:
+  app:
+    build: .
+    command: bundle exec rails s -b 0.0.0.0
+    volumes:
+      - .:/app:cached
+    ports:
+      - 3000:3000
+    depends_on:
+      - database
+    tty: true
+    stdin_open: true
+    environment:
+      RAILS_HOST: database
  database:
    build:
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: nginx_rails_with_webpacker_mysql_on_docker_development
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./log/mysql:/var/log/mysql

docker-composeで立ち上げる場合は環境変数hostを指定し、ローカル上で起動する場合は127.0.0.1を指定する。

  • config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
- host: 127.0.0.1
+ host: <%= ENV.fetch("RAILS_HOST") { '127.0.0.1' } %>

3-3. Ruby on Rails on Dockerを起動する。

# Ruby on Rails + MySQL on Dockerで起動する。
$ docker-compose up -d --build

ブラウザでhttp://localhost:3000にアクセスします。

改めて、Yay! You’re on Rails!の画面が表示されればOK.

4. nginx on Dockerを用意する。

Nginx(webサーバー)を用意します。

Nginx用のDockerfileとnginx.confを用意します。

# nginx用のファイルを作成する。
$ mkdir -p ./docker/nginx
$ touch ./docker/nginx/{Dockerfile, nginx.conf}

各ファイルの記述はこちら。

  • ./docker/nginx/Dockerfile
FROM nginx:1.17.3

RUN mkdir -p /usr/share/nginx/public

COPY ./docker/nginx/nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]
  • ./docker/nginx/nginx.conf
server {
  listen       80;
  server_name  web;
  root /usr/share/nginx/public;

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

  location @app {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://app:3000;
  }

  location / {
      try_files $uri @app;
  }

  location ~ ^/(assets|packs)/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location = /favicon.ico { access_log off; log_not_found off; }
  location = /robots.txt  { access_log off; log_not_found off; }

  location ~ /\.(?!well-known).* {
    deny all;
  }
}

logを取得するために

# Nginx用のlog吐き出し用
$ mkdir ./log/nginx
$ touch ./log/nginx/{access.log, error.log}

docker-compose up --buildを実行します。

ブラウザでhttp://localhostにアクセスします。
改めて、Yay! You’re on Rails!の画面が表示されればOK.

5. プロジェクトをちょっとだけカスタマイズ

こちらは、やったほうがいいと思われるが任意なあれこれです。

5-1. Add SplitChunks (Webpack V4)

SplitChunksPluginとは、共通で使用するpackageのJavaScriptファイルを纏める機能のことです。
デフォルトの設定を例としてあげておきます。チューニングする場合は公式を確認する。

  • ./config/webpack/environment.js
const { environment } = require('@rails/webpacker')
+ environment.splitChunks()

module.exports = environment

splitChunksを利用する場合は、JavaScriptファイルを読み込むヘルパーメソッドはjavascript_packs_with_chunks_tagになる。

- <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_packs_with_chunks_tag 'application', 'data-turbolinks-track': 'reload' %>

5-2. app/assetsを削除する。

でも、app/assets/config/manifest.jsがないと怒らせる。

Expected to find a manifest file in `app/assets/config/manifest.js` (Sprockets::Railtie::ManifestNeededError)

プロジェクト作成時に-–skip-sprocketsを実行してもsprocketsのgemはインストールされプロジェクト内部ではまだ利用しているようです。そのため、app/assets配下のManifestファイルを削除してしまうとSprockets::Railtie::ManifestNeededErrorが発生します。

対策としては、Gemfileにgem 'sprockets', '< 4'を追加し、bundle update sprocketsすると良いようです。(2020/05時点)

$ bundle update sprockets
Using sprockets 3.7.2 (was 4.0.0)

5-3. app/javascriptをapp/frontendにする。

app/javascriptからapp/frontendに変更するという記事が多いように感じます。

./config/webpacker.ymlでsource_entry_pathを指定できるのでお好みで。

  • ./config/webpacker.yml
default: &default
- source_path: app/javascript
+ source_path: app/frontend
  source_entry_path: packs
  public_root_path: public
  public_output_path: packs
  cache_path: tmp/cache/webpacker
  check_yarn_integrity: false
  webpack_compile_output: true

5-4. ブラウザにlocalhostとか127.0.0.1とか0.0.0.0と入れるのはちょっと。

sudo vi /etc/hostsを実行し、HOST DATABASEにお好みのドメイン名を追加する。

保存してブラウザでdev-application.localhostと入力してみよう。

127.0.0.1       dev-application.localhost

5-5. docker-compose upで、A server is already running. Check /app/tmp/pids/server.pid.と言われて起動できない。

今回環境構築するために何度かdocker-compose upを実行しました。その時に当該事象が発生しました。
これはdocker-composeのバグないそうですが、よく起きている事象のようです。
対策は、調べた限りですが2つあります。4

5-5-1. 対処方法 1

docker-compose.ymlのcommandでbundle exec rails s..の前に削除コマンドを仕込む方法です。

command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"

5-5-1. 対処方法 2

DockerのCMDにスクリプトを登録しておく。
対応していることはtmp/pids/server.pidを削除するということは一緒ですが、スクリプトにすることで実行時に事前に行いたい処理を追加していくことができます。ワンライナーなスクリプトよりも見通しがいいのでこちらもおすすめです。

# Docker
CMD [foreground]
# foreground.sh
#!/bin/bash
set -e

rm -f tmp/pids/server.pid

bin/rails s -b 0.0.0.0 -p 3000

5-6. webpack-dev-server on Dockerを用意する。

webpack-dev-serverをDockerで構築します。5
Dockerで構築するほうがいい理由についてはまだ腹落ちしていないため任意

# ./docker-compose.yml
version: '3.3'
services:
  web:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
    volumes:
      - ./public:/usr/share/nginx/public:cached
      - ./log/nginx:/var/log/nginx
    ports:
      - 80:80
    depends_on:
      - app
  app:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app:cached
    ports:
      - 3000:3000
    depends_on:
      - database
    tty: true
    stdin_open: true
    environment:
      RAILS_HOST: database
+     WEBPACKER_DEV_SERVER_HOST: webpacker
+ webpacker:
+   build: .
+   command: ./bin/webpack-dev-server
+   volumes:
+     - .:/webpacker-example-app
+   ports:
+     - '3035:3035'
+   tty: true
+   stdin_open: true
+   environment:
+     - NODE_ENV=development
+     - RAILS_ENV=development
+     - WEBPACKER_DEV_SERVER_HOST=0.0.0.0
+   depends_on:
+     - app
  database:
    build:
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: nginx_rails_with_webpacker_mysql_on_docker_development
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./log/mysql:/var/log/mysql
  • ./docker/nginx/nginx.conf
server {
  listen       80;
  server_name  web;
  root /usr/share/nginx/public;

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


  location @app {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://app:3000;
  }

  location / {
      try_files $uri @app;
  }

  location ~ ^/(assets|packs)/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;`v
  }

  location = /favicon.ico { access_log off; log_not_found off; }
  location = /robots.txt  { access_log off; log_not_found off; }

+  # Proxy webpack dev server websocket requests
+  location /sockjs-node {
+      proxy_redirect off;
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "upgrade";
+      proxy_pass http://127.0.0.1:3035; # change to match your +webpack-dev-server host
+  }

  location ~ /\.(?!well-known).* {
    deny all;
  }
}

docker-compose up --buildを実行します。

ブラウザでhttp://localhostにアクセスします。
改めて、Yay! You’re on Rails!の画面が表示されればOK.

まとめ

  • rails/webpackarには情報がちゃんと記載されている。(webpacker/doc)
  • webpackerはおすすめしないという記事もあるが、みなさん共通して利用するならちゃんと考えましょう。とおっしゃっているのでちゃんと考えるために一度導入してみて感触を掴んだほうがいい。(逆に導入しない方法も構築してみるといいと思う。)
  • 開発環境のセットアップだけでも時間が掛かる。しかし、はじめにいろいろ検討しておくと開発がしやすくなる。
  • それぞれの設定についてもなぜ、そうしたのか。をまとめておけると後から再検討や情報共有する時に役に立つ。
  • rails6のセットアップと注意点を一通り学習することができた。
  • 実際は、rails new のタイミングで react や veuの利用を選択することがあると思うのでそこは適宜この手順と照らし合わせながら進めればいいと思う。

  1. ただ、scaffoldぐらいは実施しています。載せていないだけ。 

  2. 設定はしましたが、ログが吐き出されたかは未確認だったりします。。 

  3. location ~ /\.(?!well-known).*についてはもう少し学習しないと... 

  4. /app/tmp/pids/server.pid.については、対処方法1を採用しました。 

  5. この方法でうまくwebpack-dev-serverが動いているかはまだ、未確認です。 

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

ElixirでMySQLを使う #3(Ecto/CRUD・クエリ作成)

概要

  1. ElixirでMySQLを使う #1(Ecto/MyXQLセットアップ〜DB作成)
  2. ElixirでMySQLを使う #2(Ecto/マイグレーション〜スキーマ作成〜カラム修正)

の続きです。

前回、Ectoマイグレーション〜スキーマ作成を行って、Elixir側からデータをMySQLに渡しました。
本記事では、ElixirでMySQLのCRUD操作(データ作成・読み出し・更新・削除)を実行していきます。

  • 本記事シリーズにおける実行環境等については、シリーズ #1 の記事をご参照ください。

(事前準備)データベース再作成

事前準備として、前回つくったデータベースを削除して、あたらしく作り直します。

terminal
  $ mix ecto.drop
  Compiling 1 file (.ex)
  The database for Friendsmysql.Repo has been dropped
terminal
  $ mix ecto.create
  The database for Friendsmysql.Repo has been created
terminal
  $ mix ecto.migrate

  13:36:23.224 [info]  == Running 20200521003013 Friendsmysql.Repo.Migrations.CreatePeople.change/0 forward

  13:36:23.226 [info]  create table people

  13:36:23.241 [info]  == Migrated 20200521003013 in 0.0s

  13:36:23.265 [info]  == Running 20200522004701 Friendsmysql.Repo.Migrations.ApplyNotnullToNames.change/0 forward

  13:36:23.265 [info]  alter table people

  13:36:23.278 [info]  == Migrated 20200522004701 in 0.0s
  • 結果確認(MySQL)
terminal(MySQL)
  mysql> desc people;
  +------------+---------------------+------+-----+---------+----------------+
  | Field      | Type                | Null | Key | Default | Extra          |
  +------------+---------------------+------+-----+---------+----------------+
  | id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
  | first_name | varchar(255)        | NO   |     | NULL    |                |
  | last_name  | varchar(255)        | NO   |     | NULL    |                |
  | age        | int(11)             | YES  |     | NULL    |                |
  +------------+---------------------+------+-----+---------+----------------+
  4 rows in set (0.00 sec)

  mysql> select * from people;
  Empty set (0.00 sec)

作りなおしたデータベースと、からっぽのpeopleテーブルが準備できました。

CRUD

それでは、IEx$ iex -S mix)でCRUD操作を実行していきます。

Create(データ作成)

  • Repo.insert()

はじめに、データの作成(Create)です。

以下の計4レコードを用意します。

terminal
iex(1)> people = [
...(1)> %Friendsmysql.Person{first_name: "Ryan", last_name: "Bigg", age: 28},
...(1)> %Friendsmysql.Person{first_name: "John", last_name: "Smith", age: 27},
...(1)> %Friendsmysql.Person{first_name: "Jane", last_name: "Smith", age: 26},
...(1)> %Friendsmysql.Person{first_name: "im", last_name: "miolab", age: 28},
...(1)> ]
[
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 28,
    first_name: "Ryan",
    id: nil,
    last_name: "Bigg"
  },
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 27,
    first_name: "John",
    id: nil,
    last_name: "Smith"
  },
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 26,
    first_name: "Jane",
    id: nil,
    last_name: "Smith"
  },
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:built, "people">,
    age: 28,
    first_name: "im",
    id: nil,
    last_name: "miolab"
  }
]

Enum.eachinsert()していきます。

terminal
iex(2)> Enum.each(people, fn(p) -> Friendsmysql.Repo.insert(p) end)

13:45:40.520 [debug] QUERY OK db=5.9ms decode=1.3ms queue=7.0ms idle=1457.6ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [28, "Ryan", "Bigg"]

13:45:40.528 [debug] QUERY OK db=1.3ms queue=4.8ms idle=1475.8ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [27, "John", "Smith"]

13:45:40.531 [debug] QUERY OK db=0.4ms queue=1.8ms idle=1482.1ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [26, "Jane", "Smith"]
:ok

13:45:40.534 [debug] QUERY OK db=2.3ms queue=1.0ms idle=1484.5ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [28, "im", "miolab"]

peopleテーブルにINSERTできました。

ちなみに、前回の記事みたいに1レコードだけをシンプルにinsert()実行するには、以下の通りです。

terminal
iex(3)> iiii = %Friendsmysql.Person{first_name: "iiii", last_name: "mimimi", age: 18}
%Friendsmysql.Person{
  __meta__: #Ecto.Schema.Metadata<:built, "people">,
  age: 18,
  first_name: "iiii",
  id: nil,
  last_name: "mimimi"
}

iex(4)> Friendsmysql.Repo.insert(iiii)

13:46:54.648 [debug] QUERY OK db=2.5ms queue=3.0ms idle=1575.2ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [18, "iiii", "mimimi"]
{:ok,
 %Friendsmysql.Person{
   __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
   age: 18,
   first_name: "iiii",
   id: 5,
   last_name: "mimimi"
 }}
  • 結果確認(MySQL)
terminal(MySQL)
  mysql> select * from people;
  +----+------------+-----------+------+
  | id | first_name | last_name | age  |
  +----+------------+-----------+------+
  |  1 | Ryan       | Bigg      |   28 |
  |  2 | John       | Smith     |   27 |
  |  3 | Jane       | Smith     |   26 |
  |  4 | im         | miolab    |   28 |
  |  5 | iiii       | mimimi    |   18 |
  +----+------------+-----------+------+
  5 rows in set (0.02 sec)

Create操作ができました。

Read(データ読み出し)

つづいて、登録データの読み出し・抽出(Read)を実行していきます。

全レコード 取得

  • Repo.all()
terminal
  iex(5)> Friendsmysql.Person |> Friendsmysql.Repo.all

  13:51:56.586 [debug] QUERY OK source="people" db=0.3ms queue=5.9ms idle=1513.0ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 []
  [
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 28,
      first_name: "Ryan",
      id: 1,
      last_name: "Bigg"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 27,
      first_name: "John",
      id: 2,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 26,
      first_name: "Jane",
      id: 3,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 28,
      first_name: "im",
      id: 4,
      last_name: "miolab"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 18,
      first_name: "iiii",
      id: 5,
      last_name: "mimimi"
    }
  ]

レコード全件を読み出しました。
SELECT * FROM table相当)

idを指定してレコード取得

  • Repo.get_by()
terminal
  iex(6)> Friendsmysql.Person |> Friendsmysql.Repo.get(3)

  13:53:31.176 [debug] QUERY OK source="people" db=6.2ms queue=0.3ms idle=1102.8ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`id` = ?) [3]
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 26,
    first_name: "Jane",
    id: 3,
    last_name: "Smith"
  }

id: 3に該当するレコードを抽出しました。

カラム名とフィールドに基づいてレコードを取得

  • Repo.where()
terminal
  iex(7)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(first_name: "im")

  13:54:58.257 [debug] QUERY OK source="people" db=1.8ms queue=2.0ms idle=1185.3ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["im"]
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 28,
    first_name: "im",
    id: 4,
    last_name: "miolab"
  }

  iex(8)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(age: 18)

  13:56:15.218 [debug] QUERY OK source="people" db=1.9ms queue=6.1ms idle=1269.7ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`age` = ?) [18]
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 18,
    first_name: "iiii",
    id: 5,
    last_name: "mimimi"
  }
  • first_name: imに該当するレコードを1件
  • age: 18に該当するレコードを1件

それぞれ抽出しました。

Ecto.Queryでフィルタリング抽出

もう少しつっこんだ条件抽出のクエリを書いてみます。

require Ecto.Queryで事前準備をします。

terminal
  iex(9)> require Ecto.Query
  Ecto.Query

カラム名とフィールドに基づいて重複レコードを取得

それでは、Ecto.Queryでまず重複するレコードを抽出します。
具体例として、last_nameSmithさんが2人いるので、こちらのお二方を抽出することとします。

なお、先ほどのget_by()ではレコード1件の抽出しかできないため、以下のようにEcto.Queryでのオペレーションが必要となります。

terminal
  iex(10)> Friendsmysql.Person |> Ecto.Query.where(last_name: "Smith") |> Friendsmysql.Repo.all

  14:00:10.899 [debug] QUERY OK source="people" db=0.2ms queue=5.0ms idle=1827.0ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') []
  [
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 27,
      first_name: "John",
      id: 2,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 26,
      first_name: "Jane",
      id: 3,
      last_name: "Smith"
    }
  ]

もしくは、

terminal
  iex(11)> Ecto.Query.from(p in Friendsmysql.Person, where: p.last_name == "Smith") |> Friendsmysql.Repo.all

  14:00:57.013 [debug] QUERY OK source="people" db=5.5ms idle=1940.3ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') []
  [
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 27,
      first_name: "John",
      id: 2,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 26,
      first_name: "Jane",
      id: 3,
      last_name: "Smith"
    }
  ]

2人のSmithさんを抽出しました。

絞り込み抽出

複数の抽出条件を組み合わせ実行します。
クエリをパイプライン演算子で繋いでいきます。

last_name条件抽出クエリに、first_name条件抽出クエリをつないで、AND検索っぽくします。

terminal
iex(12)> Friendsmysql.Person |> Ecto.Query.where(last_name: "Smith") |> Ecto.Query.where(age: 27) |> Friendsmysql.Repo.all

14:02:44.259 [debug] QUERY OK source="people" db=0.6ms queue=3.9ms idle=1184.8ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') AND (p0.`age` = 27) []
[
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 27,
    first_name: "John",
    id: 2,
    last_name: "Smith"
  }
]

last_nameSmithで、かつage27の人を抽出しました。

その他、抽出いろいろ

適当にいくつか例を実行します。
参考のため、それぞれに、相当するSQLを添えています。

  • 事前準備
terminal
  iex(13)> import Ecto.Query, only: [from: 2]
  Ecto.Query
  • 全レコードのage平均値を抽出
    SELECT AVG(age) FROM people
terminal
  iex(14)> query_avg = from p in "people",
  ...(14)> select: avg(p.age)
  #Ecto.Query<from p0 in "people", select: avg(p0.age)>

  iex(15)> Friendsmysql.Repo.all(query_avg)

  14:14:19.725 [debug] QUERY OK source="people" db=18.2ms queue=1.8ms idle=1914.1ms
  SELECT avg(p0.`age`) FROM `people` AS p0 []
  [#Decimal<25.4000>]
  • age > 20の人のfirst_nameを抽出
    SELECT first_name FROM people WHERE age > 20
terminal
  iex(16)> query_over_twenty = from p in "people",
  ...(16)> where: p.age > 20,
  ...(16)> select: p.first_name
  #Ecto.Query<from p0 in "people", where: p0.age > 20, select: p0.first_name>

  iex(17)> Friendsmysql.Repo.all(query_over_twenty)

  14:17:21.281 [debug] QUERY OK source="people" db=1.1ms queue=3.6ms idle=1491.4ms
  SELECT p0.`first_name` FROM `people` AS p0 WHERE (p0.`age` > 20) []
  ["Ryan", "John", "Jane", "im"]
  • age > 26の人をage降順で並びかえて抽出
    SELECT id, first_name, age FROM people WHERE age > 26 ORDER BY age DESC
terminal
  iex(18)> query_over_twentysix = from p in "people",
  ...(18)> where: p.age > 26,
  ...(18)> order_by: [desc: :age],
  ...(18)> select: [:id, :first_name, :age]
  #Ecto.Query<from p0 in "people", where: p0.age > 26, order_by: [desc: p0.age],
  select: [:id, :first_name, :age]>

  iex(19)> Friendsmysql.Repo.all(query_over_twentysix)

  14:19:08.225 [debug] QUERY OK source="people" db=4.1ms queue=3.9ms idle=1431.1ms
  SELECT p0.`id`, p0.`first_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`age` > 26) ORDER BY p0.`age` DESC []
  [
    %{age: 28, first_name: "Ryan", id: 1},
    %{age: 28, first_name: "im", id: 4},
    %{age: 27, first_name: "John", id: 2}
  ]

ほんの一例ですが、いずれも問題なく抽出実行できました。

この他にも、以下公式リファレンスEcto.Queryの抽出もろもろについて詳細がありますので、ご覧いただき試してみられると良いかとおもいます。

(補足)Ecto.Query内での変数使用の際は^が必要

変数をクエリ内で展開する際には、^(ピン演算子)が必要になります。

terminal
  iex(20)> first_name_im = "im"
  "im"

  iex(21)> Friendsmysql.Person |> Ecto.Query.where(first_name: ^first_name_im) |> Friendsmysql.Repo.all

  14:24:47.240 [debug] QUERY OK source="people" db=2.0ms idle=1170.0ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["im"]
  [
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 28,
      first_name: "im",
      id: 4,
      last_name: "miolab"
    }
  ]

first_name_imで束縛した文字列imEcto.Queryで展開する際、適用変数を^first_name_imの形にします。

Update(データ更新)

CRUDのつづきで、次はデータの更新を実行します。

  • Repo.update()
terminal
  iex(1)> require Ecto.Query
  Ecto.Query

  iex(2)> ryan = Friendsmysql.Person |> Ecto.Query.where(first_name: "Ryan") |> Friendsmysql.Repo.one

  14:28:40.395 [debug] QUERY OK source="people" db=0.3ms decode=1.0ms queue=2.7ms idle=1582.4ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = 'Ryan') []
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 28,
    first_name: "Ryan",
    id: 1,
    last_name: "Bigg"
  }

上記で準備したRyanさんのデータage: 28を、29に更新してみます。

terminal
  iex(3)> change_age_twentynine = Friendsmysql.Person.changeset(ryan, %{age: 29})
  #Ecto.Changeset<
    action: nil,
    changes: %{age: 29},
    errors: [],
    data: #Friendsmysql.Person<>,
    valid?: true
  >

  iex(4)> Friendsmysql.Repo.update(change_age_twentynine)

  14:31:55.745 [debug] QUERY OK db=7.2ms queue=4.1ms idle=1929.9ms
  UPDATE `people` SET `age` = ? WHERE `id` = ? [29, 1]
  {:ok,
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 29,
    first_name: "Ryan",
    id: 1,
    last_name: "Bigg"
  }}

結果を確認します。

terminal
  iex(5)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(first_name: "Ryan")

  14:32:16.250 [debug] QUERY OK source="people" db=0.5ms queue=0.8ms idle=1442.8ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["Ryan"]
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
    age: 29,
    first_name: "Ryan",
    id: 1,
    last_name: "Bigg"
  }

age: 29に更新がされています。

Delete(データ削除)

CRUDの最後に、データの削除を実行します。

  • Repo.delete()

先ほどのIExで実行したUpdateのつづきに、コードを書いていきます。
Ryanさん レコードを、ごめんなさいですが削除します。

terminal
  iex(6)> Friendsmysql.Repo.delete(ryan)

  14:35:11.201 [debug] QUERY OK db=4.8ms queue=4.8ms idle=1386.5ms
  DELETE FROM `people` WHERE `id` = ? [1]
  {:ok,
  %Friendsmysql.Person{
    __meta__: #Ecto.Schema.Metadata<:deleted, "people">,
    age: 28,
    first_name: "Ryan",
    id: 1,
    last_name: "Bigg"
  }}

削除できているか全件抽出して確認します。

terminal
  iex(7)> Friendsmysql.Person |> Friendsmysql.Repo.all

  14:35:52.982 [debug] QUERY OK source="people" db=0.4ms queue=2.0ms idle=173.7ms
  SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 []
  [
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 27,
      first_name: "John",
      id: 2,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 26,
      first_name: "Jane",
      id: 3,
      last_name: "Smith"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 28,
      first_name: "im",
      id: 4,
      last_name: "miolab"
    },
    %Friendsmysql.Person{
      __meta__: #Ecto.Schema.Metadata<:loaded, "people">,
      age: 18,
      first_name: "iiii",
      id: 5,
      last_name: "mimimi"
    }
  ]

Ryanさんのレコードが削除されています。

終わり

IExでElixir(Ecto)によるMySQLのCRUD操作(データ作成・読み出し・更新・削除)を実行しました。

  • Ectoはドキュメントも詳しく、パイプラインでガシガシ書けて良い感じなので、個人的にも色々もっと調べてみようと思ってます。
  • もしかしたら続きを何かしら書くかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む