20201227のMySQLに関する記事は7件です。

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-started

2)インストールの確認

ターミナル
~ % 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: passwordhost: 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:create

Rails6だとこのコマンドを実行すると

========================================
  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)

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

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-started

2)インストールの確認

ターミナル
~ % 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: passwordhost: 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:create

Rails6だとこのコマンドを実行すると

========================================
  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を接続しましょう!

以下の設定ページを開いて標準を選択します。

se.png

設定ページに以下を記述

eee.png

「名前」 → 任意のものを設定
「ホスト」 → 「127.0.0.1」
「ユーザー名・パスワード」 → ymlファイルで記述したものに合わせる
「ポート」 → 「4306」

以上を設定して「接続」するとSequelPro内で該当アプリのDBが作成されているはずです。

また、ボート番号はdocker-compose.ymlports:の記述と合わせています。

以上でDockerとdocker-composeの開発環境への導入は終了です。
ちなみに僕はここまで6時間前後かかったので、ぜひ参考にしてさくさく進めていってください。

本記文献

『さわって学ぶクラウドインフラ docker基礎からのコンテナ構築』
既存のrails6のアプリにMySQLでDockerを導入する。
Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)

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

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)を編集出来るようにします。
01_マスターデータ管理.png

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.php

composer.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)が生成されます。
マスターデータ管理にも以下のように追加されて編集出来るようになりました。
03_マスターデータ管理.png

参考サイト

https://xoops.ec-cube.net/modules/newbb/viewtopic.php?topic_id=22996&forum=10

最後に

拙い記事を最後まで見ていただきありがとうございました。

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

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;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[仕事納め]2020年にやらかしたミスをまとめる[未経験から2年目]

こんにちは。web系を中心としたプラグラマをしております。
小さなミスからそこそこ大きめのミスまで、今年1年でたくさんのミスをしてきました。
まだ新人で大きな権限を頂いていないこともあり、本番環境でやらかしてしまった皆様ほどのインパクトあるやらかしはないのですが、コーディングや開発環境構築などであれこれ反省点があるのでまとめていきたいと思います。
アンチパターンやあるあるとしてお楽しみ下さい。

コーディング系
環境系
人として

コーディング系

誤字

とにかく多かったです。
クラス名や関数名ならすぐエラーが発生して見つけやすいのですが、phpで変数名のスペルミスをしたりすると、そのままnullが代入されたりして見つけづらく、時間の無駄になりがちでした。

$address = 'hogehoge';
print $adress; // 返却値なし

くだらない誤字ですが、この手のエラーが出ない誤字で無駄にした時間も、年間を通せばかなり膨大になって来ます。
解決策としては、コードを細かく切り分けてこまめにテストを実行しました。これによって誤字箇所を発見するのが早くなった実感があります。
また、エディタはVSCodeを使用しており補完機能は元々使っていたのですが、コード量が多いとなかなか読み込まず使用を諦めて手入力することもあったので、今後補完機能をうまく活用できるように工夫していきたいです。
......そんなことより静的言語を使った方がいいのかもしれません。

エスケープ

先日LaravelselectRawメソッドを用いて生に近い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年も頑張っていきましょう!!

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

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 カラム名;

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

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の問題

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