- 投稿日:2020-12-27T17:17:06+09:00
Ruby on Rails ✕ Docker 開発中のアプリにDocker導入(MySQL)
はじめに
はじめてのDocker導入だと、何から手を付けたら良いのかわからない、ということがよくありますが、まさに自分がそれでした...
本記事では開発途中・完成したRailsアプリケーション(DB: MySQL)にDockerを導入する方法・流れを解説していきます。
参考までに自分は6時間位かけてローカルに導入できました。その苦労を忘れないようにしっかりアウトプットしていこうと思います(笑)
間違っていたら指摘してくださると幸いです!Dockerの導入
1)Dockerのインストール
まずMacにDockerのアプリをインストールしてください
それに関しては以下の記事がわかりやすいです!
DockerをMacにインストールするインストールできたらターミナルで以下コマンドを実行しましょう!
ターミナル~ % docker run -d -p 80:80 docker/getting-started2)インストールの確認
ターミナル~ % docker -v以下のように出力されたらインストール成功です。
Docker version 20.10.0, build 7287ab3
ターミナル~ % docker-compose -vこちらも同じように
docker-compose version 1.27.4, build 40524192
と出力されたら成功です。3)開発中・完成済みのアプリでDockerファイルの作成
次にDockerを導入するためにDockerファイルを作成し、設定を記述していきます。
テキストエディタでも可能ですが、自分の場合はターミナルから入力して方がエラーもなくスムーズだったため、そちらの方法を記載していきます。①Dockerfileの作成
まずアプリのルートディレクトリにDockerfileを作成して、記述していきます。
ルートディレクトリというのは以下の図のようにアプリ直下のディレクトリを表します。dock_app ----|-- app |-- bin |-- config |-- db ・・・・・・ ・・・・・・ |-- Gemfile |-- Gemfile.lock |-- package.json |-- Rakefile |-- README.md |-- Dockerfile ←「Dockerfile] |-- docker-compose.yml ←[docker-compose.yml]手順としては以下の順に進んでいきます。
ターミナル① ~ % cd Dockerを導入したいアプリのパス ② アプリ名 % vi Dockerfile #ファイルを開いたら「i」でインサートモードにして以下記述 FROM ruby:2.6.5 #アプリのRubyバージョン RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /アプリ名 WORKDIR /アプリ名 ADD ./Gemfile /アプリ名/Gemfile ADD ./Gemfile.lock /アプリ名/Gemfile.lock RUN gem install bundler #bundlerをインストールしないとエラーが出る RUN bundle install ADD . /アプリ名 #記述ができたら「escキー」を押して「:wq」で保存②docker-compose.ymlファイルの作成
ターミナル① アプリ名 % vi docker-compose.yml ② docker-compose.ymlに以下記述 #「i」と入力しインサートモードで記述 version: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 'password' # このままpasswordとしても問題なく動く ports: - "4306:3306" #DockerコンテナとSequelpro接続の為に必要な設定 web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/アプリ名 ports: - "3000:3000" depends_on: - db #記述が終了したら「escキー」を押して「:wq」で保存4)config/database.ymlファイルの編集
(テキストエディタ)default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password # passwordの設定がなければ、このままで良い socket: /tmp/mysql.sock host: db development: <<: *default database: アプリ名_development追加されたのは
password: password
とhost: db
の2つのみ。5)docker-compose buildでコンテナ作成
ターミナルアプリ名 ~ % docker-compose build上記コマンドを実行してコンテナを作成しましょう。※時間がかかることがあります
以下のように出力されれば成功です。
ターミナルRemoving intermediate container dac250609513 ---> f0ba8d685e44 Step 9/9 : ADD . /tumlog ---> f50cb7681119 Successfully built f50cb7681119 Successfully tagged tumlog_web:latestちなみに初心者の方だとコマンド実行中に赤字で
debconf: delaying package configuration, since apt-utils is not installed
と表示されますが、自分の調べた範囲では特に気にするようなこともない?出力のようです。6)コンテナ上でDB作成・migrationの実行
コンテナが作成できたらコンテナ上でDBを作成していきます。
ターミナルアプリ名 % docker-compose run web bundle exec rake db:createRails6だとこのコマンドを実行すると
======================================== Your Yarn packages are out of date! Please run `yarn install --check-files` to update. ======================================== To disable this check, please change `check_yarn_integrity` to `false` in your webpacker config file (config/webpacker.yml).このように出力されることがあります。
このように出力されたら、エラー分通りconfig/webpacker.ymlファイル
の記述を以下のように編集しましょう。webpacker.yml# Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules check_yarn_integrity: true # check_yarn_integrity: true を falseに変えましょう → check_yarn_integrity: falseこれでエラーは解決できると思うので、記述を変更したら、もう1度DB作成のコマンドを実行しましょう。
ターミナルCreated database 'アプリ名_development' Created database 'アプリ名_test' #上記のようにcreatingがcreated...doneとなったら成功続いてmigrationを実行しましょう。
ターミナルアプリ名 % docker-compose run web bundle exec rake db:migrate == 20201222010929 Createテーブル名: migrating ==================================== -- create_table(:テーブル名) -> 0.0095s == 20201222010929 Createテーブル名: migrated (0.0096s) =========================== # 上記のようにrails db:migrateを実行したときのようにマイグレーションが実行されれば成功です7)コンテナの起動
最後にコンテナを起動して無事、アプリがブラウザで表示されるか確認しましょう。
コンテナの起動コマンドを実行しましょう。
ターミナルアプリ名 % docker-compose up # 起動していれば以下の様の出力が出る db_1 | 2020-12-27T06:45:52.494912Z 0 [Note] Event Scheduler: Loaded 0 events db_1 | 2020-12-27T06:45:52.497330Z 0 [Note] InnoDB: Buffer pool(s) load completed at 201227 6:45:52 db_1 | 2020-12-27T06:45:52.497669Z 0 [Note] mysqld: ready for connections. db_1 | Version: '5.7.32' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) web_1 | => Booting Puma web_1 | => Rails 6.0.3.4 application starting in development web_1 | => Run `rails server --help` for more startup options web_1 | Puma starting in single mode... web_1 | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in Pajamas web_1 | * Min threads: 5, max threads: 5 web_1 | * Environment: development web_1 | * Listening on tcp://0.0.0.0:3000 web_1 | Use Ctrl-C to stopコマンドを実行し、サーバーが起動したらローカル環境にアクセスしてみましょう
http://localhost:3000/
無事に表示・挙動が確認できれば開発環境にDockerを導入できたということになります。自分はこれだけで6時間くらいかかったので、ぜひ皆さんは本記事を活用して、さくさく進めていってください!!
本記文献
①『さわって学ぶクラウドインフラ docker基礎からのコンテナ構築』
② 既存のrails6のアプリにMySQLでDockerを導入する。
③ Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)
- 投稿日:2020-12-27T17:17:06+09:00
Ruby on Rails ✕ Docker✕MySQL開発中のアプリにDockerとdocker-composeを導入
はじめに
はじめてのDocker導入だと、何から手を付けたら良いのかわからない、ということがよくありますが、まさに自分がそれでした...
本記事では開発途中・完成したRailsアプリケーション(DB: MySQL)にDockerを導入する方法・流れを解説していきます。
参考までに自分は6時間位かけてローカルに導入できました。その苦労を忘れないようにしっかりアウトプットしていこうと思います(笑)
間違っていたら指摘してくださると幸いです!Dockerの導入
1)Dockerのインストール
まずMacにDockerのアプリをインストールしてください
それに関しては以下の記事がわかりやすいです!
DockerをMacにインストールするインストールできたらターミナルで以下コマンドを実行しましょう!
ターミナル~ % docker run -d -p 80:80 docker/getting-started2)インストールの確認
ターミナル~ % docker -v以下のように出力されたらインストール成功です。
Docker version 20.10.0, build 7287ab3
ターミナル~ % docker-compose -vこちらも同じように
docker-compose version 1.27.4, build 40524192
と出力されたら成功です。3)開発中・完成済みのアプリでDockerファイルの作成
次にDockerを導入するためにDockerファイルを作成し、設定を記述していきます。
テキストエディタでも可能ですが、自分の場合はターミナルから入力して方がエラーもなくスムーズだったため、そちらの方法を記載していきます。①Dockerfileの作成
まずアプリのルートディレクトリにDockerfileを作成して、記述していきます。
ルートディレクトリというのは以下の図のようにアプリ直下のディレクトリを表します。dock_app ----|-- app |-- bin |-- config |-- db ・・・・・・ ・・・・・・ |-- Gemfile |-- Gemfile.lock |-- package.json |-- Rakefile |-- README.md |-- Dockerfile ←「Dockerfile] |-- docker-compose.yml ←[docker-compose.yml]手順としては以下の順に進んでいきます。
ターミナル① ~ % cd Dockerを導入したいアプリのパス ② アプリ名 % vi Dockerfile #ファイルを開いたら「i」でインサートモードにして以下記述 FROM ruby:2.6.5 #アプリのRubyバージョン RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /アプリ名 WORKDIR /アプリ名 ADD ./Gemfile /アプリ名/Gemfile ADD ./Gemfile.lock /アプリ名/Gemfile.lock RUN gem install bundler #bundlerをインストールしないとエラーが出る RUN bundle install ADD . /アプリ名 #記述ができたら「escキー」を押して「:wq」で保存②docker-compose.ymlファイルの作成
ターミナル① アプリ名 % vi docker-compose.yml ② docker-compose.ymlに以下記述 #「i」と入力しインサートモードで記述 version: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 'password' # このままpasswordとしても問題なく動く ports: - "4306:3306" #DockerコンテナとSequelpro接続の為に必要な設定 web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/アプリ名 ports: - "3000:3000" depends_on: - db #記述が終了したら「escキー」を押して「:wq」で保存4)config/database.ymlファイルの編集
(テキストエディタ)default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password # passwordの設定がなければ、このままで良い socket: /tmp/mysql.sock host: db development: <<: *default database: アプリ名_development追加されたのは
password: password
とhost: db
の2つのみ。5)docker-compose buildでコンテナ作成
ターミナルアプリ名 ~ % docker-compose build上記コマンドを実行してコンテナを作成しましょう。※時間がかかることがあります
以下のように出力されれば成功です。
ターミナルRemoving intermediate container dac250609513 ---> f0ba8d685e44 Step 9/9 : ADD . /アプリ名 ---> f50cb7681119 Successfully built f50cb7681119 Successfully tagged アプリ名_web:latestちなみに初心者の方だとコマンド実行中に赤字で
debconf: delaying package configuration, since apt-utils is not installed
と表示されますが、自分の調べた範囲では特に気にするようなこともない?出力のようです。6)コンテナ上でDB作成・migrationの実行
コンテナが作成できたらコンテナ上でDBを作成していきます。
ターミナルアプリ名 % docker-compose run web bundle exec rake db:createRails6だとこのコマンドを実行すると
======================================== Your Yarn packages are out of date! Please run `yarn install --check-files` to update. ======================================== To disable this check, please change `check_yarn_integrity` to `false` in your webpacker config file (config/webpacker.yml).このように出力されることがあります。
このように出力されたら、エラー分通りconfig/webpacker.ymlファイル
の記述を以下のように編集しましょう。webpacker.yml# Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules check_yarn_integrity: true # check_yarn_integrity: true を falseに変えましょう → check_yarn_integrity: falseこれでエラーは解決できると思うので、記述を変更したら、もう1度DB作成のコマンドを実行しましょう。
ターミナルCreated database 'アプリ名_development' Created database 'アプリ名_test' #上記のようにcreatingがcreated...doneとなったら成功続いてmigrationを実行しましょう。
ターミナルアプリ名 % docker-compose run web bundle exec rake db:migrate == 20201222010929 Createテーブル名: migrating ==================================== -- create_table(:テーブル名) -> 0.0095s == 20201222010929 Createテーブル名: migrated (0.0096s) =========================== # 上記のようにrails db:migrateを実行したときのようにマイグレーションが実行されれば成功です7)コンテナの起動
最後にコンテナを起動して無事、アプリがブラウザで表示されるか確認しましょう。
コンテナの起動コマンドを実行しましょう。
ターミナルアプリ名 % docker-compose up # 起動していれば以下の様の出力が出る db_1 | 2020-12-27T06:45:52.494912Z 0 [Note] Event Scheduler: Loaded 0 events db_1 | 2020-12-27T06:45:52.497330Z 0 [Note] InnoDB: Buffer pool(s) load completed at 201227 6:45:52 db_1 | 2020-12-27T06:45:52.497669Z 0 [Note] mysqld: ready for connections. db_1 | Version: '5.7.32' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) web_1 | => Booting Puma web_1 | => Rails 6.0.3.4 application starting in development web_1 | => Run `rails server --help` for more startup options web_1 | Puma starting in single mode... web_1 | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in Pajamas web_1 | * Min threads: 5, max threads: 5 web_1 | * Environment: development web_1 | * Listening on tcp://0.0.0.0:3000 web_1 | Use Ctrl-C to stopコマンドを実行し、サーバーが起動したらローカル環境にアクセスしてみましょう
http://localhost:3000/
無事に表示・挙動が確認できれば開発環境にDockerを導入できたということになります。自分はこれだけで6時間くらいかかったので、ぜひ皆さんは本記事を活用して、さくさく進めていってください!!
8)Docker環境(開発環境)のRailsアプリとSequelProを接続する
最後にDBを接続しましょう!
以下の設定ページを開いて標準を選択します。
設定ページに以下を記述
「名前」 → 任意のものを設定
「ホスト」 → 「127.0.0.1」
「ユーザー名・パスワード」 → ymlファイルで記述したものに合わせる
「ポート」 → 「4306」以上を設定して「接続」するとSequelPro内で該当アプリのDBが作成されているはずです。
また、ボート番号は
docker-compose.yml
のports:
の記述と合わせています。以上でDockerとdocker-composeの開発環境への導入は終了です。
ちなみに僕はここまで6時間前後かかったので、ぜひ参考にしてさくさく進めていってください。本記文献
①『さわって学ぶクラウドインフラ docker基礎からのコンテナ構築』
② 既存のrails6のアプリにMySQLでDockerを導入する。
③ Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)
- 投稿日:2020-12-27T15:54:49+09:00
EC-CUBE4 プラグインでマスターテーブルを追加する
はじめに
初めての投稿となります。
よろしくお願いします。仕事で始めてEC-CUBE4を利用したECサイトを仕事で制作しました。
その際、プラグインでマスターテーブルを新規追加することになったのですが、
テーブルの新規追加はよく見かけるのに対し、マスターテーブルの記事が少ないように感じたので作成方法を簡単に記事にまとめよう思います。新規プラグイン作成方法については多くのサイトが存在するのでそちらを参考にしください。
目標
マスターテーブル(mtb_sample)の新規追加。
管理画面>設定>システム設定>マスタデータ管理 へ登録。最終的なマスターテーブル
形は以下のしたいと考えています。
id name sort_no discriminator_type 1 テスト1 1 samplemtbconfig 2 テスト2 2 samplemtbconfig 3 テスト3 3 samplemtbconfig 4 テスト4 4 samplemtbconfig 管理画面>設定>システム設定>マスタデータ管理
以下の設定にマスターテーブル(mtb_sample)を編集出来るようにします。
EC-CUBE Version
Version 4.0.4プラグイン
プラグイン生成時の項目は以下にしています。
name:SamplePlugin code:SamplePlugin ver: 1.0.0プラグインのディレクトリ構成
SamplePlugin\ |―― Entity\ |―― Master\ |―― SampleMtbConfig.php |―― Repository\ |―― Master\ |―― SampleMtbRepository.php |―― Form\ |―― Type\ |―― Extension\ |―― SampleMasterdataType.php |―― composer.json |―― PluginManager.phpcomposer.json
プラグインの情報の記述
composer.json{ "name": "ec-cube/SamplePlugin", "version": "1.0.0", "description": "SamplePlugin", "type": "eccube-plugin", "require": { "ec-cube/plugin-installer": "~0.0.7" }, "extra": { "code": "SamplePlugin" } }Entity\Master\SampleMtbConfig.php
まずはエンティティクラスを作成します。
普通のテーブルであればここに setId()やgetid()などを記述していくのですが、必要ありません。以下の記述でマスターテーブルの名前を設定します。
@ORM\Table(name="mtb_sample")
SampleMtbConfig.php<?php namespace Plugin\SamplePlugin\Entity\Master; use Doctrine\ORM\Mapping as ORM; /** * SamplePlugin * * @ORM\Table(name="mtb_sample") * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255) * @ORM\HasLifecycleCallbacks() * @ORM\Entity(repositoryClass="Plugin\SamplePlugin\Repository\Master\SampleMtbRepository") * @ORM\Cache(usage="NONSTRICT_READ_WRITE") */ class SampleMtbConfig extends \Eccube\Entity\Master\AbstractMasterEntity { }Repository\Master\SampleMtbRepository.php
Repositoryはテーブルのデータを操作する際に利用されます。
SampleMtbRepository.php<?php namespace Plugin\SamplePlugin\Repository\Master; use Eccube\Repository\AbstractRepository; use Plugin\SamplePlugin\Entity\Master\SampleMtbConfig; use Symfony\Bridge\Doctrine\RegistryInterface; class SampleMtbRepository extends AbstractRepository { /** * * @param RegistryInterface $registry */ public function __construct(RegistryInterface $registry) { parent::__construct($registry, SampleMtbConfig::class); } }Form\Extension\SampleMasterdataType.php
管理画面>設定>システム設定>マスタデータ管理 へ登録や変更を行うことが出来る機能へ反映させるための記述です。
SampleMasterdataType.php<?php namespace Plugin\SamplePlugin\Form\Type\Extension; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Eccube\Form\Type\Admin\MasterdataType; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; class SampleMasterdataType extends AbstractTypeExtension { /** * * @var EntityManagerInterface */ protected $entityManager; /** * * @param EntityManagerInterface $entityManager */ public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } /** * * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $masterdata = []; /** @var MappingDriverChain $driverChain */ $driverChain = $this->entityManager->getConfiguration()->getMetadataDriverImpl(); /** @var MappingDriver[] $drivers */ $drivers = $driverChain->getDrivers(); foreach ($drivers as $namespace => $driver) { if ($namespace == 'Eccube\Entity' || $namespace == 'Plugin\SamplePlugin\Entity') { $classNames = $driver->getAllClassNames(); foreach ($classNames as $className) { /** @var ClassMetadata $meta */ $meta = $this->entityManager->getMetadataFactory()->getMetadataFor($className); if (strpos($meta->rootEntityName, 'Master') !== false && $meta->hasField('id') && $meta->hasField('name') && $meta->hasField('sort_no') ) { $metadataName = str_replace('\\', '-', $meta->getName()); $masterdata[$metadataName] = $meta->getTableName(); } } } } $builder ->add('masterdata', ChoiceType::class, [ 'choices' => array_flip($masterdata), 'expanded' => false, 'multiple' => false, 'constraints' => [ new Assert\NotBlank(), ], ]) ; } /** * * {@inheritdoc} */ public function getBlockPrefix() { return 'admin_system_masterdata'; } /** * * {@inheritdoc} */ public function getExtendedType() { return MasterdataType::class; } }PluginManager.php
PluginManager.phpはプラグインのインストールやアンインストール時に呼び出されますので、プラグインインストール時に、マスターデータを書き込む記述をします。
PluginManager.php<?php namespace Plugin\SamplePlugin; use Eccube\Plugin\AbstractPluginManager; use Plugin\SamplePlugin\Entity\Master\SampleMtbConfig; use Symfony\Component\DependencyInjection\ContainerInterface; class PluginManager extends AbstractPluginManager { /** * * @param array $meta * @param ContainerInterface $container */ public function install(array $meta, ContainerInterface $container) { $em = $container->get('doctrine.orm.entity_manager'); $CustomerQuestionnaire = new SampleMtbConfig(); $CustomerQuestionnaire->setId('1'); $CustomerQuestionnaire->setName('テスト1'); $CustomerQuestionnaire->setSortNo('1'); $em->persist($CustomerQuestionnaire); $em->flush(); $CustomerQuestionnaire = new SampleMtbConfig(); $CustomerQuestionnaire->setId('2'); $CustomerQuestionnaire->setName('テスト2'); $CustomerQuestionnaire->setSortNo('2'); $em->persist($CustomerQuestionnaire); $em->flush(); $CustomerQuestionnaire = new SampleMtbConfig(); $CustomerQuestionnaire->setId('3'); $CustomerQuestionnaire->setName('テスト3'); $CustomerQuestionnaire->setSortNo('3'); $em->persist($CustomerQuestionnaire); $em->flush(); $CustomerQuestionnaire = new SampleMtbConfig(); $CustomerQuestionnaire->setId('4'); $CustomerQuestionnaire->setName('テスト4'); $CustomerQuestionnaire->setSortNo('4'); $em->persist($CustomerQuestionnaire); $em->flush(); } /** * * @param array $meta * @param ContainerInterface $container */ public function enable(array $meta, ContainerInterface $container) { } /** * * @param array $meta * @param ContainerInterface $container */ public function disable(array $meta, ContainerInterface $container) { } }プラグインインストール
以上のコードを記述後、プラグインをインストールすれば(mtb_sample)が生成されます。
マスターデータ管理にも以下のように追加されて編集出来るようになりました。
参考サイト
https://xoops.ec-cube.net/modules/newbb/viewtopic.php?topic_id=22996&forum=10
最後に
拙い記事を最後まで見ていただきありがとうございました。
- 投稿日:2020-12-27T10:19:00+09:00
SQLをターミナルでいろいろやってみる。初心者編その2
はじめに
SQLをターミナルで操作する方法を学んだのでアウトプットしていこうと思います。
本当に最初の最初の操作方法のその2です。今回はSQLデータの追加・編集・削除についてです。
データを操作するSQL
・SELECT データの検索
・INSERT データの登録
・UPDATE データの更新
・DELETE データの削除SELECT
データ取得時に使用
・取得するカラムを指定する
mysql> SELECT 《カラム名》 FROM 《テーブル名》・テーブルに登録されている全てのカラムデータの取得
mysql> SELECT * FROM 《テーブル名》アスタアリスク * はワイルドカード。全てのパターンにマッチするものという意味を持つ。
今回だと全てのカラムという意味のワイルドカード。INSERT
テーブルにデータを登録する
・すべてのカラムに値を入れる場合
mysql> INSERT INTO 《テーブル名》 VALUES(値1, 値2, 値3);(例) mysql> INSERT INTO goods VALUES(1, "消しゴム", 100);・特定のカラムのみに値を入れる場合
mysql> INSERT INTO テーブル名(カラム名1, カラム名2) VALUES(値1, 値2);(例) mysql> INSERT INTO goods(id, name) VALUES(2, "消しゴム");●指定されていないカラムにはNULLが設定される。
・登録されているデータの確認
mysql> SELECT * FROM 《テーブル名》;UPDATE
データの更新
mysql> UPDATE 《テーブル名》 SET 《変更内容》 WHERE 《条件》;(例) mysql> UPDATE goods SET price = 120 WHERE id = 2;DELETE
データの削除
mysql> DELETE FROM 《テーブル名》 WHERE 《条件》;(例) mysql> DELETE FROM goods WHERE id = 2;
- 投稿日:2020-12-27T09:21:27+09:00
[仕事納め]2020年にやらかしたミスをまとめる[未経験から2年目]
こんにちは。web系を中心としたプラグラマをしております。
小さなミスからそこそこ大きめのミスまで、今年1年でたくさんのミスをしてきました。
まだ新人で大きな権限を頂いていないこともあり、本番環境でやらかしてしまった皆様ほどのインパクトあるやらかしはないのですが、コーディングや開発環境構築などであれこれ反省点があるのでまとめていきたいと思います。
アンチパターンやあるあるとしてお楽しみ下さい。コーディング系
誤字
とにかく多かったです。
クラス名や関数名ならすぐエラーが発生して見つけやすいのですが、phpで変数名のスペルミスをしたりすると、そのままnullが代入されたりして見つけづらく、時間の無駄になりがちでした。$address = 'hogehoge'; print $adress; // 返却値なしくだらない誤字ですが、この手のエラーが出ない誤字で無駄にした時間も、年間を通せばかなり膨大になって来ます。
解決策としては、コードを細かく切り分けてこまめにテストを実行しました。これによって誤字箇所を発見するのが早くなった実感があります。
また、エディタはVSCodeを使用しており補完機能は元々使っていたのですが、コード量が多いとなかなか読み込まず使用を諦めて手入力することもあったので、今後補完機能をうまく活用できるように工夫していきたいです。
......そんなことより静的言語を使った方がいいのかもしれません。エスケープ
先日LaravelのselectRawメソッドを用いて生に近いSQLを書く機会がありました。WHERE句の対象となるカラムにバックスラッシュを用いたパスが入っていて、この部分をエスケープ出来ておらず検索結果が0となる失敗を犯しました。この質問と大体同じ内容です。
// 失敗例 DB::table('hoge') ->selectRaw('* WHERE `path`="PATH\\TO\\SOME\\MODEL"') ->get();一見エスケープ出来ていそうですが、phpではダブルクォート内ではバックスラッシュが
//
→/
となり、かつ、Laravelのraw関係のメソッドではエスケープされない(というかrawって書いてあるのでそりゃそうですね)ので、MySQLに送られるクエリは以下のようになります。SELECT * FROM hoge WHERE `path`='PATH\TO\SOME\MODEL'結果、MySQLでもエスケープされて無事死亡しました。
できるだけこのような生っぽいSQLは書かないのが一番ですが、この時は諸事情で仕方なかったので、以下のようにして解決しました。// 成功例 DB::table('hoge') ->selectRaw('* WHERE `path`="PATH\\\\TO\\\\SOME\\\\MODEL"') ->get();SELECT * FROM hoge WHERE `path`='PATH\\TO\\SOME\\MODEL'ともかく、何かとバックスラッシュは問題のタネになりやすいので警戒していきたいと思います。
環境系
Dockerコンテナの内か外か
Dockerを立ち上げてシェル作業する時、必要なコマンドをコンテナの内側で行うのか外側で行うのかを間違えて
command not found
になったり、最悪の場合想定外の処理が始まったりします(しました)。
基本的にどのコマンドをどこで実行するのかはプロジェクトによるので、実行前に確認するようになりました。特にcomposerの実行場所で事故ったので気をつけていきたいと思います。バージョン違い
composerで依存パッケージをインストールすることが多いのですが、マイナーバージョンは指定しておらず不具合が発生する場合があります。
また、私が今年特に詰まったのはcomposer自体のバージョン違いです。
2020年10月頃にcomposerのバージョン2.0がリリースされました。これに気づかずdocker-compose
コマンドを実行してしまった結果、DockerFile内にcomposer関係の諸々のコマンドが実行されてしまい、大量のエラーが発生しました。
この件の解決策は、こちらの方と同じ方法を使いました。
コマンドは以下のものです。curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --1 --filename=composerバージョンアップの詳細はこちらです。
人として
連絡が遅い
思い出すと胃が痛くなります.......
進捗が悪いのに、頑張って挽回しようと試行錯誤していて締め切りギリギリまで上司への進捗が遅れている旨の連絡をしませんでした。上司からは特に怒られず、淡々と「了解です。次は早めに連絡・相談して下さい」という内容のメッセージが返ってきて逆に怖かったです。
とにかく、早めの報告・連絡・相談は本当に大切です。技術的なミスより何より連絡不足が一番信用を失うと思いました。仕様通りでない
仕様変更が頻繁でも、仕様書が読みづらくても、決定した仕様が一意である以上、仕様通りでないのは絶対にダメですよね。
コミット前にもう一度、成果物のチェックを怠らない。大事だと思います。
あと、最初に仕様書を通しで読んだ時に矛盾点を見つけられるようになりたいです。実装を始めてから気づくことが多いので.......まとめ
結局、ほとんどのミスが確認不足に起因していました。
コミット前、コマンド入力前、上司への報告前......この記事を振り返ってミスを減らしていきたいと思います。
というわけで、皆様1年間お疲れ様でした。2021年も頑張っていきましょう!!
- 投稿日:2020-12-27T08:10:33+09:00
SQLをターミナルでいろいろやってみる。初心者編
はじめに
SQLをターミナルで操作する方法を学んだのでアウトプットしていこうと思います。
本当に最初の最初の操作方法です。MySQLに接続
% mysql -u root
CREATEでデータベースを作成
mysql> CREATE DATABASE 《データベース名》;
※SQL文の終わりには必ずセミコロンをつけること!テーブル一覧の表示
mysql> SHOW DATABASES;
データベースの削除
mysql> DROP DATABASE 《データベース名》;
操作するデータベースの選択
mysql> USE 《database名》;
(「どのデータベースにあるテーブルか」を選択)テーブルの作成
mysql> CREATE TABLE テーブル名 (カラム名1 カラム名1の型, カラム名2 カラム名2の型, …);
【カラム名の型(MySQLにおける)】
INT :数字
VARCHAR(M) :最大M文字の文字列
(例)
商品idを数値で保存するidカラムと、商品名を文字列で保存するnameカラムの作成
mysql> CREATE TABLE goods (id INT, name VARCHAR(255));作成したテーブルの表示
mysql> SHOW TABLES;
テーブルの構造の表示(追加したカラムの確認)
mysql> SHOW columns FROM 《テーブル名》;
対象テーブルの指定
mysql> FROM 《テーブル名》
カラム情報の更新
mysql> ALTER TABLE 《テーブル名》 操作
カラムを1つだけ追加
mysql> ALTER TABLE テーブル名 ADD カラム名 カラムの型;
カラムを複数追加
mysql> ALTER TABLE テーブル名 ADD (カラム名 カラムの型, ……);
カラムの変更
mysql> ALTER TABLE テーブル名 CHANGE 古いカラム名 新しいカラム名 新しいカラムの型;
例)
"goods"テーブルの "zaiko"というカラム名から"stockというカラム名へ変更
mysql> ALTER TABLE goods CHANGE zaiko stock int;カラムの削除
mysql> ALTER TABLE テーブル名 DROP カラム名;
- 投稿日:2020-12-27T01:00:41+09:00
PyMySQLでBulk Updateをする方法と注意点【Python】
結論
- INSERT ... ON DUPLICATE KEY UPDATE 構文の場合はexecutemanyを使う。
- ELT&FIELDでのUpdateをする場合はプリペアドステートメントでパラメータをセットする。
INSERT ... ON DUPLICATE KEY UPDATE 構文
VALUES
などの一度にデータを挿入するクエリではexecutemany
メソッドを使うことができます。conn = pymysql.connect( mysql_endpoint, user=username, passwd=password, db=dbname ) def bulk_insert_and_update_users(): prepared_statement = [ [ 1, # id 'Qiita太郎', # name 20 # age ], [ 2, 'Qiita花子', 18 ] ] with conn.cursor() as cursor: sql = ( 'INSERT INTO users ' '(id, name, age) ' 'VALUES (%s, %s, %s) ' 'ON DUPLICATE KEY UPDATE ' 'name = VALUES(name), ' 'age = VALUES(age)' ) cursor.executemany(sql, prepared_statement) conn.commit()これを実行するとこのようなクエリが生成されます。
INSERT INTO users (id, name, age) VALUES (`1`, `Qiita太郎`, `20`), (`2`, `Qiita花子`, `18`) ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age);シンプルで分かりやすいですね。
ELT & FIELD
INSERTをしない場合や
INSERT ... ON DUPLICATE KEY UPDATE
によるauto_increment
の問題が気になる場合はこちらを使用することになると思います。(参考)
その場合、いくつか注意しなくてはいけない点があるので見ていきましょう。conn = pymysql.connect( mysql_endpoint, user=username, passwd=password, db=dbname ) def bulk_update_users(): records = [ { 'user_id': 1, 'user_name': 'Qiita太郎', 'user_age': 20 }, { 'user_id': 2, 'user_name': 'Qiita花子', 'user_age': 18 } ] id_list = [] name_list = [] age_list = [] for record in records: id_list.append(str(record['user_id'])) name_list.append(record['user_name']) age_list.append(record['user_age']) id_strings = ','.join(['%s'] * len(id_list)) name_strings = ','.join(['%s'] * len(name_list)) age_strings = ','.join(['%s'] * len(age_list)) sql = ( 'UPDATE users SET ' 'name = ' 'ELT(FIELD(id, %(user_ids)s), %(user_names)s), ' 'age = ' 'ELT(FIELD(id, %(user_ids)s), %(user_ages)s) ' 'WHERE id IN (%(user_ids)s);' '' % dict(user_ids=id_strings, user_names=name_strings, user_ages=age_strings) ) prepared_statement = tuple(id_list) \ + tuple(name_list) \ + tuple(id_list) \ + tuple(age_list) \ + tuple(id_list) with conn.cursor() as cursor: cursor.execute(sql, prepared_statement) conn.commit()特徴的なのはこの部分ですね。
id_list = [] name_list = [] age_list = [] for record in records: id_list.append(str(record['user_id'])) name_list.append(record['user_name']) age_list.append(record['user_age']) id_strings = ','.join(['%s'] * len(id_list)) name_strings = ','.join(['%s'] * len(name_list)) age_strings = ','.join(['%s'] * len(age_list)) sql = ( 'UPDATE users SET ' 'name = ' 'ELT(FIELD(id, %(user_ids)s), %(user_names)s), ' 'age = ' 'ELT(FIELD(id, %(user_ids)s), %(user_ages)s) ' 'WHERE id IN (%(user_ids)s);' '' % dict(user_ids=id_strings, user_names=name_strings, user_ages=age_strings) )これを実行すると変数
sql
には以下のような文字列が格納されます。UPDATE users SET name = ELT(FIELD(id, %s, %s), %s, %s), age = ELT(FIELD(id, %s, %s), %s, %s) WHERE id IN (%s, %s);今回はわかりやすくするために助長に書いていますが、実際には
%s
の数は全て同じなのでrecords
の要素数から文字列を生成して使いまわしても問題ないです。そして%演算子で挿入する順番で全てのパラメータを結合しています。
prepared_statement = tuple(id_list) \ + tuple(name_list) \ + tuple(id_list) \ + tuple(age_list) \ + tuple(id_list) # (1, 2, `Qiita太郎`, `Qiita花子`, 1, 2, 20, 18, 1, 2)これを
conn.execute
の引数に渡すことで以下のようなクエリが実行されます。UPDATE users SET name = ELT(FIELD(id, `1`, `2`), `Qiita太郎`, `Qiita花子`), age = ELT(FIELD(id, `1`, `2`), `20`, `18`) WHERE id IN (`1`, `2`);なんでこんな面倒なことするの?
ただ単にクエリを作るだけなら文字列結合してしまえば簡単です。
しかしこれだとSQLインジェクションに対する脆弱性があります。例えばユーザーの名前を
"'; DROP TABLE users; '"
に設定されてしまった場合に防御のしようがありません。ダメな例id_list = [] name_list = [] age_list = [] for record in records: id_list += [f"`{str(record['user_id'])}`"] name_list += [f"`{record['user_name']}`"] age_list += [f"`{record['user_age']}`"] id_list_s = ",".join(id_list) name_list_s = ",".join(name_list) age_list_s = ",".join(age_list) sql = ( 'UPDATE users SET ' 'name = ' f'ELT(FIELD(id, {id_list_s}), {name_list_s}), ' 'age = ' f'ELT(FIELD(id,{id_list_s}), {age_list_s}) ' f'WHERE id IN ({id_list_s});' )一応
replace
などで'
を\'
に置き換えるという対策もできなくはないですが、execute
実行時にpymysql側でエスケープしてくれるので素直にプリペアドステートメントでパラメータを挿入する方が良いでしょう。(参考)おまけ
pymysqlで実際に実行されたSQL文を確認するには
execute
後にself.executed
に保存されるので、このプロパティにアクセスすることで確認が可能です。with conn.cursor() as cursor: sql = "SELECT * FROM users WHERE id = %s" cursor.execute(sql, (1)) print(cursor._executed) # SELECT * FROM users WHERE id = 1参考資料
PyMySQL
10.5.5 MySQLCursor.executemany() Method
MySQL で ON DUPLICATE KEY UPDATE を使ってたら、カンスト(カウンターストップ)した話
imploding a list for use in a python MySQLDB IN clause
Pythonプリペアドステートメント。 SELECT INの問題