おけらのブログ++

駆け出しWebエンジニアの奮闘記

SQLAlchemy+Alembic:モデルからマイグレーションファイルを自動生成

目次

  1. この記事について
  2. SQLAlchemyとAlembic
  3. 環境構築 〜インストールと設定〜
  4. 環境構築 〜SQLAlchemyを使ったモデルの作成〜
  5. 環境構築 〜Alembicでの複数モデルからのマイグレーション自動生成〜
  6. おまけ  〜ERAlchemyを使ったER図の自動生成〜

この記事について

この記事を読んで分かること

  • SQLAlchemyとAlembicを用いてモデルからマイグレーションファイルを自動生成する方法
  • 複数のモデルファイルを読み込んでマイグレーションを作成する方法
  • SLQAlchemyを使ったモデルの書き方

この記事では詳しく書いていないこと

  • SQLAlchemyとAlembicの概要
  • SQLAlchemyのORMの使い方

ディレクトリ構造

最終的には実際のバックエンド側のAPIサーバーで使うことを想定して

このようなディレクトリ構造でマイグレーション環境を作ることを目標とします。

├── app
│   ├── __init__.py
│   └── models
│       ├── team.py
│       └── user.py
├── database
│   └── migrations
│       ├── __init__.py
│       ├── env.py
│       ├── script.py.mako
│       └── versions
│           └── b72a859457c7_create_tables.py
└── alembic.ini
  • モデルのファイルはapp/models/***.py
  • マイグレーションファイルはdatabase/migrations/~
  • alembic の設定ファイルはプロジェクト直下

SQLAlchemyとAlembic

SQLAlchemyの特徴

f:id:Okerra:20200129011006j:plain

  • Python の ORM
  • エンジンとしてDBの接続処理を管理できる
  • 様々なDBを同一のソースコードで管理できる

要は直接SQL文を書くことなくSQLiteMySQLPostgreSQL 等のデータベースを操作することができます。

docs.sqlalchemy.org

Alembicの特徴

ちょっと前まではsqlalchemy-migrationというライブラリだったようです。

ただすでに開発はストップしており、後継者としてAlembicが開発されているようです。

環境構築〜インストールと設定〜

f:id:Okerra:20200129012155p:plain さぁ、それでは頑張って環境構築していきましょう!!

まずはライブラリのインストールと設定をしていきます。

SQLAlchemyインストール

  • Python対応バージョン 2.7または3.4以上
$ pip install SQLAlchemy

Alembicインストール

$ pip install alembic

PyMySQLインストール

今回はデータベースクライアントとしてPyMySQLを使います。

$ pip install PyMySQL

これで今回のインストール完了です!

Alembic環境の初期化

以下のコマンドを打つとAlembic環境が構築されます。

(が、まだコマンドを打たずに読み進めて下さい)

$ alembic init migrations
├── alembic.ini
├── migrations
   ├── README
   ├── env.py
   ├── script.py.mako
   └── versions

ただ、今回はdatabase/migrationsというディレクトリ構造にしたいので下記のようにします

$ alembic init database/migrations

すると最初に説明したようなディレクトリ構造になったかと思います。

├── alembic.ini
└── database
   └── migrations
       ├── README
       ├── env.py
       ├── script.py.mako
       └── versions

データベース情報の記述

例えばdockerでこのようなmysqlのDBコンテナを立てているとします。

docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -p 3306:3306 -d mysql
docker exec -it mysql bash

root@# mysql -uroot -pmysql
mysql> create database database;

マイグレーションを使ってDBを更新するための情報をalembic.iniに記載します。

# format <driver>://<user>:<password>@<host>:<port>/<database> =>適宜環境に合わせる
sqlalchemy.url = mysql+pymysql://root:mysql@127.0.0.1:3306/database?charset=utf8

もう一つ、これは任意ですがマイグレーションファイルの命名規則を変更します。

デフォルトはリビジョンが書かれていますが、それだと順序がわかりづらいので日付形式に変更します。

追加でalembic.iniを変更します。

<変更前>
file_template = %%(rev)s_%%(slug)s

<変更後>
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s

環境構築 〜SQLAlchemyを使ったモデルの作成〜

f:id:Okerra:20200129012528p:plain

モデルの作成

ここでは以下のよくあるユーザーモデルと、そのユーザーが持つ権限を管理するテーブルを作ることにします。

* User
    * id
    * 名前
    * メールアドレス
    * 郵便番号
    * 住所
    * ステータス
    * 作成日時
    * 更新日時
 
* UserAuthorization
    * id
    * ユーザーID
    * 権限
    * ステータス
    * 作成日時
    * 更新日時

まずはapp/modelsにuser.pyとuser_authorization.pyいうファイルを作成します。

$ touch app/models/user.py
$ touch app/models/user_authorization.py

作ったユーザーモデルのカラムデータを実装していきましょう!

User Model

from sqlalchemy import Column, BigInteger, Integer, String, DATETIME
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(BigInteger, primary_key=True, nullable=False)
    name = Column(String(255), nullable=False)
    email = Column(String(255), nullable=False)
    status = Column(Integer, server_default="1", nullable=False)
    created_at = Column(DATETIME, nullable=False)
    updated_at = Column(DATETIME, nullable=False)

# Relationship
user_authorizations = relationship("user_authorizations")

UserAuthorization Model

from sqlalchemy import Column, BigInteger, Integer, DATETIME, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class UserAuthorization(Base):
    __tablename__ = "user_authorizations"

    id = Column(BigInteger, primary_key=True, nullable=False)
    user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
    auth_type = Column(Integer, nullable=False)
    created_at = Column(DATETIME, nullable=False)
    updated_at = Column(DATETIME, nullable=False)

モデルはたったこれだけで完了です。

細かい仕様については次で説明していきます。

SQLAlchemyのモデルの書き方

SQLAlchemyの仕様について調べたものをまとめます。

データ型

(公式HP)によるとこれらのデータ型が用意されているようです。

BIGINT, BINARY, BIT, BLOB, BOOLEAN, CHAR, DATE, 
DATETIME, DECIMAL, DECIMAL, DOUBLE, ENUM, FLOAT, INTEGER, 
LONGBLOB, LONGTEXT, MEDIUMBLOB, MEDIUMINT, MEDIUMTEXT, NCHAR, 
NUMERIC, NVARCHAR, REAL, SET, SMALLINT, TEXT, TIME, TIMESTAMP, 
TINYBLOB, TINYINT, TINYTEXT, VARBINARY, VARCHAR, YEAR

使いたい型をsqlalchemyからインポートするようにしてください。

from sqlalchemy import Column, BigInteger, Integer, String, DATETIME
オプション設定

公式サイトはここに説明があります。

Operation Reference — Alembic 1.4.1 documentation

  • Null許可設定
name = Column(String(255), nullable=False)
  • デフォルト
status = Column(Integer, server_default="1", nullable=False)
  • PK
id = Column(BigInteger, primary_key=True, nullable=False)
  • FK

1:Nなどでリレーションをはる場合には、FK側に外部キーの設定をして

user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)

参照される側にはリレーションのテーブル名を記述します。

from sqlalchemy.orm import relationship
(省略)
user_authorizations = relationship("user_authorizations")

リレーションについては様々なパターンがあるので公式サイトを参照してください。

Basic Relationship Patterns — SQLAlchemy 1.3 Documentation

おそらくこれだけあれば、よくあるモデルのデータ構築はできるかと思います!

環境構築 〜Alembicでの複数モデルからのマイグレーション自動生成〜

f:id:Okerra:20200205095830p:plain ここでは複数のモデルファイルがあることを想定して、マイグレーションファイルを自動生成していきます。

複数モデルの読み込み

1つのモデルファイルを扱うだけなら公式ドキュメントで大丈夫ですが、複数のモデルファイルを扱うのは少々工夫が必要となります。

その際に参考にさせて頂いたサイトはこちらです。

alembic support multiple model files | Andrew's Blog

env.py

from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool, MetaData
from alembic import context

# ----------追加--------------
import importlib
import os
import sys

sys.path.append(os.getcwd())
# ----------------------------

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# ----------追加--------------
# 自動マイグレーション作成の対象モデル
target_models = [
    "app.models.user",
    "app.models.user_authorization",
]

def import_model_bases():
    lst = list(map(lambda x: importlib.import_module(x).Base.metadata, target_models))
    return lst

def combine_metadata(lst):
    m = MetaData()
    for metadata in lst:
        for t in metadata.tables.values():
            t.tometadata(m)
    return m

target_metadata = combine_metadata(import_model_bases())
# ----------------------------

以上で設定は完了です。

補足

  • モデルファイルが増えたら下記にモデルファイル名のパスを追加
target_models = [
    "app.models.user",
    "app.models.user_authorization",
]
  • データ型変化の検知
def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, 
            target_metadata=target_metadata, 
            compare_type=True  # 追加
        )

マイグレーションの実行

やっと環境が整いました。では早速マイグレーションの自動生成をしてみましょう。

下記コマンドを打つと例のようなマイグレーションファイルが生成されます。

alembic revision  --autogenerate -m "Create tables"
# ex) => database/migrations/versions/f493fb95394b_create_tables.py

生成したマイグレーションファイルを実行してみましょう。

alembic upgrade head

お疲れ様です!これで作業は完了となります!

ちなみに下記コマンドでマイグレーション実行前に戻せます。

alembic downgrade −1

おまけ 〜ERAlchemyを使ったER図の自動生成〜

最後にSQLAlchemyと相性のいいERAlchemyを紹介して終わります。

github.com

ERAlchemyはDBからER図を生成してくれるライブラリです。

インストール

# grapghvizをbrewでローカルインストール
brew install grapghviz

# ERAlchemyのインストール
pip install eralchemy

ER図の生成

インストールが完了したら下記コマンドを打ってみます。

# eralchemy -i <driver>://<user>:<password>@<host>:<port>/<database> -o <output filename>
$ eralchemy -i mysql+pymysql://mysql:mysql@127.0.0.1:3306/mysql -o er-diagram.png

するとこんな感じでER図を生成してくれました。

f:id:Okerra:20200205102012p:plain

こんなライブラリを使えばドキュメントのメンテも不要になるので、ぜひ使ってみてください!

はじめてのGraphQL

目次

  1. GraphQLとは
  2. RESTとの比較
  3. まとめ

graphql.org

GraphQLとは

GraphQLとは一言でいうと、サーバーへの問い合わせ言語となります。

似たような例でいうと、SQLはデータベースへの問い合わせ言語があります。

GraphQLも似たようなもので、とあるサーバーに対してデータをもらうためのクエリ言語となります。

従来と大きく違うのは、クライアント側で必要な情報だけをサーバー側に要求し、サーバー側はそのクエリに応じてレスポンスを返します。

RESTとの比較

GraphQLはRESTとの違いを理解すると、特徴がよく分かるかと思いますのでTwitterAPIを例に説明します。

Twitter REST API

Twitter REST APIのサイトをみるとサイドバーに大量のエンドポイントが設定されているのがわかります。 westplain.sakuraweb.com

試しにユーザー情報を取得するとしたらGET users/showにクエリを投げる必要があります。

そしてそのレスポンスには大量のパラメータが返却されます。 f:id:Okerra:20200109201054p:plain

Twitter GraphQL API

次にTwitter GraphQL APIの仕様を見ていきます。

Twitter GraphQL APIはGraphQL Hubというサイトで確認します。

このGraphQL Hubを使えば、簡単にリクエストを送り、実際にどのようなレスポンスが帰ってくるかを確認することができます。

まず GraphQL Hub にアクセスしTwitterを選択します。 f:id:Okerra:20200109195730p:plain

するとこのような画面が表示されます。 f:id:Okerra:20200109195954p:plain

GrapghQLの文法については後述しますので、とりあえず下記をそのままコピーして、左側に貼り付けたら三角マークのstartボタンを押します。

{
  graphQLHub
  twitter {
    user (identifier: name, identity: "clayallsopp") {
      id
      name
      profile_image_url
      tweets_count
      followers_count
    }
  }
}

するとレスポンスとしてユーザーのID、名前、イメージURL、ツイート数、フォロワー数が取得できました。

{
  "data": {
    "graphQLHub": "Use GraphQLHub to explore popular APIs with GraphQL! Created by Clay Allsopp @clayallsopp",
    "twitter": {
      "user": {
        "id": "48464282",
        "name": "Clay Allsopp",
        "profile_image_url": "http://pbs.twimg.com/profile_images/1003496219241414656/r_lwv_6L_normal.jpg",
        "tweets_count": 2514,
        "followers_count": 2317
      }
    }
  }
}

ここで注目したいのは、レスポンス内容はリクエストで設定したパラメーターのみがレスポンスで返却されているという点です。

このようにGrapghQLのAPIを使えば、ある特定のエンドポイントに欲しいデータだけをリクエストするだけで必要な情報を取得できます。

まとめ

REST

  • エンドポイントが複数あり、決まったエンドポイントにリソース取得のクエリを投げる
  • レスポンスは決まったデータ構造で不要なデータも全て返される

GraghQL

  • 決まったエンドポイントは1つのみ
  • 取得できるデータはクライアント側が欲しいデータのみ取得可能

【Vol.1】Flutter入門〜環境構築編〜

f:id:Okerra:20191115210457p:plain

目次

  1. Flutterとは
  2. MacにFlutterをインストール
  3. Android 開発環境の準備
  4. iOS 開発環境の準備
  5. Flutterサンプルアプリの実行
  6. エラーの対処方法

Flutterを使ってみる

春にWebエンジニアデビューをしてから約半年が経ちました。

そしてひょんなことからFlutterを勉強してみることになりました!

調べてみるとFlutter For Webがででているようなので、数年後はもしかするとWebの世界でも流行っているかもしれないので今のうちに勉強していても損はないはず!

そんなこんなで今回はFlutterの第1回目の投稿となります!

開発環境

  • OS: MacOS Mojave 10.14.6
  • Flutter:  1.9.1

1. Flutterとは

最近のアプリ開発ではほとんどがAndroidiOSの両方でリリースをすることが多いと思います。

その場合、フレームワークや開発方法は大きく違うため開発には大きなコストが必要となります。

しかしFlutterはクロスプラットフォームと呼ばれ、同じコードで両方のOSに対応したアプリ開発ができるためかなりの効率化が期待できます。

今回はFlutterでの開発に必要な環境構築と実際にクロスプラットフォームを体感するために、デフォルトのサンプルアプリを起動してAndroidiOSのどちらでも動作できることを確認していきます。

2. MacにFlutterをインストール

まずは公式ページからFlutter本体をインストールします。

MacOSをクリックします。

f:id:Okerra:20191115225130p:plain

最新のFlutter SDKをダウンロードしてください。

f:id:Okerra:20191115224939p:plain

ダウンロードが完了したら、適当な場所で展開します。

私の場合は今回はホームディレクトリにdevelopmentというフォルダを作ります。

$ mkdir ~/development
$ cd ~/development

ダウンロードしたSDKを先ほど作ったフォルダに移動して解凍します。

$ mv ~/Downloads/flutter_macos_v1.9.1+hotfix.6-stable.zip ./
$ unzip ./flutter_macos_v1.9.1+hotfix.6-stable.zip

Flutterコマンドを使えるように解凍したフォルダのパス/flutter/binPATHを通します。

export PATH="$PATH:/Users/username/development/flutter/bin"

試しにFlutterのバージョンを表示してみましょう!

$ flutter --version
Flutter 1.9.1+hotfix.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 68587a0916 (9 weeks ago) • 2019-09-13 19:46:58 -0700
Engine • revision b863200c37
Tools • Dart 2.5.0

これで最初の第一歩、Flutterのインストールが完了しました!

ついでに~/.bashrc等にFlutterコマンドのaliasも設定しておきましょう!

alias fl='flutter'

3. Android 開発環境の準備

Flutterをインストールしたら、次はAndroid Studioをインストールしていきます。

Android StudioAndroidアプリの統合開発環境のことです。

もし既にインストールされている方は、Flutterのプラグインをインストールする所までスキップしてください。

Android Studioのインストール

まずは公式ページにいってダウンロードしましょう!

f:id:Okerra:20191115233527p:plain

ダウンロードが完了したらdmgファイルを開いてApplicationフォルダにドラッグ&ドロップします。

f:id:Okerra:20191115234036p:plain

Android StudioにFlutterプラグインのインストール

インストールしたら、まずはAndroid Studioを起動しましょう。

起動したら右下のConfigureからPlugginを選択します。

f:id:Okerra:20191115235241p:plain

ここでFlutterを検索してインストールします(画像は既にインストールしてしまってます)

f:id:Okerra:20191115235649p:plain

ちょっと一休み 〜 Flutter doctorで診断する

ここで一度Flutter doctorというコマンドを紹介します!

flutter doctorとはFlutterに関連するセットアップが完了しているかチェックをしてくれます。

もしNGだった場合は必要なコマンドなども表示してくれるため非常に優しい仕様になってます。

ぜひ一度チェックしてみてください!

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.9.1+hotfix.6, on Mac OS X 10.14.6 18G103, locale ja-JP)

[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    ✗ Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses
[!] Xcode - develop for iOS and macOS (Xcode 11.2.1)
    ✗ Xcode requires additional components to be installed in order to run.
      Launch Xcode and install additional required components when prompted.
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart
        side.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/platform-plugins
      To install:
        sudo gem install cocoapods
[✓] Android Studio (version 3.5)
[!] VS Code (version 1.39.2)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[!] Connected device
    ! No devices available

例えば [!] VS Code (version 1.39.2) というのがNGになっていますが、VSCodeからFlutterのプラグインをインストールするとOKになりました!

f:id:Okerra:20191116233713p:plain

Androidエミュレーターの設定

さぁいよいよAndroid環境の設定はこれで最後となります。

まずはエミュレーター(Virtual Device)をコマンドラインから作成します。(最後のAndroidは任意の命名)

$ flutter emulator --create --name Android
Emulator 'Android' created successfully.

もしここでエラーが出た場合は対処方法を最後に書いていますのでそちらを確認してみてください。

ここまで上手くいったら最後にエミュレーターを起動してみましょう。

$ flutter emulator --launch Android

このようにAndroidエミュレーターが表示されれば成功です!

f:id:Okerra:20191116102823p:plain

4. iOS 開発環境の準備

まずはXcodeをインストールしましょう。

ただXcodeMacであれば大体は入っているかと思いますので、説明は省きます。

FlutterはXcode9.0以上である必要がありますのでご注意ください。

次にXcodeの設定変更をします。

$ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer

そしてライセンスの同意を行います。

$ sudo xcodebuild -license

最後にAndroidと同様にiOSエミュレーターを起動してみます。

$ open -a Simulator
$ flutter emulator --launch apple_ios_simulator

この通り、iOSエミュレーターが表示されれば無事成功です!

f:id:Okerra:20191116102509p:plain

お疲れ様です。これでAndroidiOSのFlutter環境構築が完了しました。

環境構築だけであればここで終わりとなりますが、せっかくなのでFlutterのサンプルアプリをビルドして、AndroidiOSのどちらもサンプルアプリが動くことまで体感してみます。

5. Flutterサンプルアプリの実行

Windowsの方もいるかと思いますのでAndroid Studioをつかっていきます。

とても簡単なのであとちょっと頑張ってください!

まずはAndroid Studioを起動し、[Start a new Flutter project]を押してください。

f:id:Okerra:20191116105204p:plain

[Flutter Application] -> [Next]を選択します。

f:id:Okerra:20191116105307p:plain

次にプロジェクト名等を入力してください。[flutter_first_appp]など。

今回はそれ以外は変更する必要はありません。最後に [Next] を押します。

f:id:Okerra:20191116105642p:plain

そしてパッケージ名に利用するCompanyDomainを設定します。

パッケージ名はユニークである必要がありますが、ここではサンプルなのでそのままにします。

f:id:Okerra:20191116110218p:plain

ここまで完了したらこのようなプロジェクト画面が開くはずです。

f:id:Okerra:20191116185913p:plain

ではサンプルアプリをビルドしてみましょう!!

Androidであれば右上をAndroidを選択してRunボタンを! f:id:Okerra:20191116190453p:plain

iOSであればいiPhoneを選択してRunボタンを! f:id:Okerra:20191116191134p:plain

そうすればこのようにそれぞれのエミュレーターが起動することを確認できました!!

f:id:Okerra:20191116191442p:plain

さいごに

はじめてのFlutterでしたが、とても簡単に構築することができました。

今回はサンプルアプリを実行しただけですが、次からはもう少し踏み込んだ内容をまとめたいと思います。

連載できるように頑張ります!!!

エラー対処

Failed to create emulator ***

$ flutter emulator --create --name Android
Failed to create emulator Android'.

No suitable Android AVD system images are available. You may need to install these using sdkmanager, for example:
  sdkmanager "system-images;android-27;google_apis_playstore;x86"

You can find more information on managing emulators at the links below:
  https://developer.android.com/studio/run/managing-avds
  https://developer.android.com/studio/command-line/avdmanager

この場合はエラー内で表示されているように下記コマンドを打ってください。

$ sdkmanager "system-images;android-27;google_apis_playstore;x86"
sdkmanager: command not found

私の場合、さらにsdkamanagerのパスが通ってないようでエラーとなりました。

sdkmanagerは/Users/username/Library/Android/sdk/tools/binに実態がありますのでパスを通します。

# Android Studio
export ANDROID_HOME=~/Library/Android;
export ANDROID_SDK_ROOT=$ANDROID_HOME/sdk;
export PATH=$ANDROID_SDK_ROOT/tools/bin:$PATH;

NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema

これがでたらJava8系が入っていないようです。

そのためOracleのページからJava8のJDKをインストールしましょう!(JREではないです!)

f:id:Okerra:20191116195732p:plain

もし別のJavaが入っているならこの辺りを参考にすれば良いと思います!

ローカルDBでデータを作成し、AWSのDBにインポートする

やりたいこと

最近仕事でLaravelを使った開発をやっています。
AWSサーバーにあるDevelop環境にテストデータを追加したかったのですが、諸事情でいきなりAWSサーバーでSeederを実行するのはリスクがありローカルで作成したレコードをdumpしてAWSサーバーDBにインポートするという作業をしました。

f:id:Okerra:20190422225734p:plain

おおまかな流れ

  • ローカルDBのレコードをdumpする
  • AWS側のテーブルのidの連番になるようにdumpファイルを修正
  • AWSサーバーにdumpしたsqlファイルを移動する
  • AWSサーバーにdumpファイル をAWSサーバーのDBにインポートする

mysqldumpコマンドでレコードをdumpする

レコードをdumpするためにmysqldumpコマンドを使います。 説明は省きますがデータベース単位やテーブル単位など様々な用法があるようなので、それは下記参照ください。 qiita.com

今回はレコード単位でさらにテーブルの中のid=XXX以上のレコードをdumpしたかったためmysqldumpにWHEREオプションを合わせて実行します。

最終的なコマンド

$ mysqldump -u USERNAME --no-create-info DB_NAME TABLE_NAME --where="id >= 100" > dump.sql

--no-create-info
今回の場合AWS側のDBは既にあるテーブルにレコードを追加したかったため、このオプションをつけました。これをつけなければCREATE TABLEクエリが発行されるため既に登録されているレコードも全て初期化されてしまいます。

--where
『--where(-w)』オプションを指定することでいつものSQL文と同じように条件を絞ることができます

これでレコードのdumpファイルの完了です。

AWS側のidに連番となるようにdumpファイルを修正する

AWS側のテーブルには既にレコードが登録されており、それの最終idから連番となるように追加をしたいです。そのためにdumpファイルをちょっとだけ修正します。 登録済みのレコードそのままに追加するのではなく、テーブルごと置き換える場合は不要です。

修正前

INSERT INTO `TABLE_NAME` * VALUES (100,AAA,SATO,sato@test.com),(101,AAB,SUZUKI,suzuki@test.com),(102,AAC,YAMADA,yamada@test.com).........


修正後

INSERT INTO `TABLE_NAME` (user_id, name, email) VALUES (AAA,SATO,sato@test.com),(AAB,SUZUKI,suzuki@test.com),(AAC,YAMADA,yamada@test.com).........

ポイント

  • *の部分にauto incrementのid以外の全カラムを書く
  • 正規表現を使ってVALUESのid部分の値を削除する

こうすることで空となったid部分は、挿入先のテーブルの連番となるように追加されます。

ローカルサーバーのファイルをAWSサーバーに移動する

dumpファイル ができたら、もうあとは余裕だ!と思っていいたら思わぬところで躓きました。

dumpファイル をどうやってAWSサーバーに持っていくのか

最初はコピペしてみたもののサイズが大きすぎてvimが途中で止まってしまいました。そのためちゃんと調べたところscpコマンドというSSH先のサーバーにファイル転送するコマンドがありました。

scp コマンド
scpコマンドとはローカルサーバーからリモートサーバーにファイルを転送したり、その逆でリモートサーバーからローカルサーバーにファイルを転送したりできます。

今回は鍵認証を使ってローカルからリモートにコピーします。 鍵は既に~/.ssh/key.pemというファイルがあったのでこれを -i オプションを使って転送します。

# フォーマット
$ scp -i 秘密鍵ファイル ファイル名 送信先のアドレス:ファイルを保存するディレクトリ

# 例
$ scp -i ~/.ssh/key.pem /{ローカルファイルのパス}/dump.sql ec2-user@XXX.amazonaws.com:/{保存するディレクトリパス}

参考 qiita.com docs.aws.amazon.com

dumpファイル をAWSサーバーのDBにインポートする

最後にAWS側のDBにmysqlコマンドを使用してdumpしたファイルをインポートさせます。

$ mysql -h XXX.amazonaws.com -u username -p DB_NAME < dump.sql


以上でローカルでテストデータを作成し、AWSのDBにインポートする方法でした。正直たかがテストデータを作成するだけでかなり時間がかかってしまいました。

おまけ

- mysqlテーブルのauto incrementの値を確認する

mysql> show table status like 'TABLE_NAME';

- mysqlテーブルのauto incrementの値を更新する

mysql> ALTER TABLE TABLE_NAME AUTO_INCREMENT = 1000;

- 組み込みエンジニアから念願のWebエンジニアに転職しました

Rubyでsambal を使ってデータを取得する

仕事でsambaにアクセスして自動でデータを取ってくるツールを作ることがあったので、その記録をしていきます。

以下サイトが大変参考になりました。
Ruby から Windows の共有フォルダにアクセスする - akishin999の日記

sambalライブラリ

今回はRubyでsambalというライブラリを使って実現しました。そもそもsambaアクセスがどのように出来るかは以下のサイトがわかりやすいです。
実践初級ITブログ LinuxからWindowsの共有ドライブにアクセス(smbclientコマンド)

samba-clientのインストール

sambalはsamba-clientをラッパーしているようなのでまずはsamba-clientをインストールします。

yum install -y samba-client

sambalのインストール

Gemでsambalのインストールを行います。

bundle init

Gemfileが生成されるのでgem 'sambal'と記載してbundle installします。
これにて事前準備完了です。



sambalのコマンド

sambalのコマンドについては下記で公開されています。今回使用したものについて説明します。
GitHub - akishin/sambal: Ruby Samba Client

Sambal::Client.new: インスタンスの生成

client = Sambal::Client.new(host:  host,
                            share:  dir,
                            user:   user,
                            password: pass)

まず何をするにしてもこれが必要になります。これでclientオブジェクトを生成することで下記のコマンドが使えるようになります。

client.ls : ディレクトリのファイル一覧を取得
puts client.ls

このコマンドの戻り値から以下のような情報が取得できます。

  • type => ファイル or ディレクト
  • size => ファイルサイズ
  • modified => 更新日
puts client.ls
{
"data"=>{ 
:type=>:directory, 
:size=>"0", 
:modified=>2018-06-25 14:26:53 +0900}, 

"test.zip"=>{
:type=>:file, 
:size=>"7331462", 
:modified=>2018-06-25 14:17:24 +0900}, 

"IMG_0217.mp4"=>{
:type=>:file, 
:size=>"2042582", 
:modified=>2018-06-25 11:51:09 +0900}, 
}

データの取り出し方サンプル

info = client.ls
info.each do |key, data|
  if((data[:type].to_s == "file") && (data[:size] != 0)) then
    #ファイルを取得
    client.get(key.to_s,  "hoge.mp4")
  end
end

keyには例でいうdataやtest.zip,IMG_0217.mp4が入ってきます。
そしてdataからハッシュで:typeや:size,:modifiedの中身を取り出すことができます。

client.get: sambaからデータを取得する

実際にsambaからデータを取得する際に使うコマンドです。第一引数にはそのファイル名(path)をいれ、第二引数には取得後のファイル名をいれます。

client.get("remote_file.*", "local_file.*")

ここで注意しなければならないのは、あくまでダウンロードできるのはファイルだけということです。
いろいろと調べたのですが、おそらくフォルダごとダウンロードすることはできなさそうです。そのためフォルダごとダウンロードしたい場合は、自分で再帰処理を作ってダウンロードしなければなりません。

client.cd: ディレクトリ位置を変える

これもUNIX系のcdコマンドと同じ感覚で使用できます。現在のディレクトリから変更したいディレクトリ名をいれて変更します。

client.close :終了処理

これもお作法で最後にcloseしてインスタンスを破棄します。


sambalを使ってデータを取ってくる

ここからが本題のデータ取得方法についてです。今回は下記のようなディレクトリ構成になっている想定でやっていきます。
f:id:Okerra:20180624002410p:plain
例として、ファイル実行時に取ってきたいファイルの日付を入力するとそのフォルダの中のテキストファイルを取ってくるようにします。

ソースコードsample

# download_samba.rb
require 'yaml'
require 'date'

begin
  # config情報取得
  config = YAML.load_file("config.yml")
  host = config["user"]["host"]
  dir  = config["user"]["dir"]
  user = config["user"]["user_id"]
  pass = config["user"]["pass"]

  # SambaClient open
  client = Sambal::Client.new(host:     host,
                              share:    dir,
                              user:     user,
                              password: pass)
  
  puts " When will you get a data?(YYYYMMDD):"
  date = STDIN.gets.chomp

  # データ取得後のファイル格納フォルダをsystem関数を使って作成
  # 日付と日時から生成
  now = Time.now
  output_fld =  now.strftime('%Y%m%d_%H%M%S')
  system("mkdir " + output_fld)

  puts "******** Download Start ********"

  # download file
 ret = download_file(client, date, output_fld)

  puts "******** Download End ********"

  client.close
end
#----------------------------------------------
#        module : ダウンロード処理
# function name : download_file()
#         input : client => samba client object
#         input : date   => YYYYMMDD
#         input : wk_fld => work directory path
#----------------------------------------------
def download_file(client, date, wk_fld)

  # samba(data/YYYYMMDD)にあるファイル一覧を取得
  client.cd "data"
  client.cd date
  dir_info = client.ls

  # Download 
  dir_info.each do |key, data|
    if((data[:type].to_s == "file") && (data[:size] != 0)) then
      # ファイルを取得
      client.get(key.to_s, "./" + wk_fld.to_s + "/" + key.to_s)
    end
  end

  return
end

ruby初心者のため誤っていたり、もっと良い書き方があるかもしれません。
またブログ用に修正しており、このまま実行するとエラーが出るかもしれません。

configファイル
# download_samba.rb
require 'yaml'

begin
  # config情報取得
  config = YAML.load_file("config.yml")
  host = config["user"]["host"]
  dir  = config["user"]["dir"]
  user = config["user"]["user_id"]
  pass = config["user"]["pass"]
# config.yml
user:
  host: example.co.jp
  dir: data
  user_id: hoge
  pass: hogehoge

今回はチーム内に展開する想定だったためログインのためのconfig情報を外に出して、変更出来るようにしました。
Rubyでyaml形式の設定ファイルを用意して、値を取得する - Code Log

ポイント client.ls

やはりディレクトリ内のファイル情報の扱い方(client.ls)がポイントとなると思います。下記のようにファイル種別が"file"のみを取得して、フォルダ内のフォルダ以外のファイルをすべてダウンロードします。

  dir_info = client.ls

  # Download 
  dir_info.each do |key, data|
    if((data[:type].to_s == "file") && (data[:size] != 0)) then
      # ファイルを取得
      client.get(key.to_s, "./" + wk_fld.to_s + "/" + key.to_s)
    end
  end

最後の一言

今回はrubyによるsambalライブラリを使ってsambaにアクセスすることができました。
sambalに関する情報が意外と調べてもなかったので、今後使いたい人の手助けになれば幸いです。

CSS positionプロパティの使い方

HTMLやCSSの勉強を始めた人は、誰もが思った通りのレイアウトに出来ず、苦しんだ経験があるのではないかと思います。

私も同じように苦しんだのですが、CSSのpositionプロパティの使い方を理解してから急にレイアウトのレベルが上がったことを実感しました。そのため、この記事で同じような人たちの助けとなれば非常に嬉しいです。

具体的にはpositionのrelativeやabsoluteの使い方についてのまとめとなります。

作る画面について

私が作成中のアプリのトップページ画面を例に説明していきます。
最近は画面仕様の重要性に気づいたため、まずどのような画面を作るかKeynoteを使って簡単にまとめるようにしています。

そして本記事で作ろうとしている画面がこちらとなります。この画像をpositonプロパティを使ってレイアウトしていきます。positionプロパティの使い方を知りたい方には、少し遠回りになりますが順を追って説明します。
f:id:Okerra:20180304190031p:plain

positionプロパティの考え方

relativeとabsolute

positionプロパティには設定値がいくつかありますが、ここではrelativeとabsoluteのみ使って説明します。

このプロパティには親classと子classの考えがあります。
f:id:Okerra:20180304191931p:plain
親classは基準点となるclassで、設定値にはrelativeを設定します。
肝心の子classにはabsoluteを設定します。そうすることで、親classを基準(x, y)=(0, 0)としてそこからどれだけ離れているかを指定することができます。

具体的な位置の検討

子classには親classからどれだけ離れているかを指定する必要があります。そこで具体的な位置を考えていきます。

1. 画像の中心位置の決定

まずは画像の中心位置について検討します。レスポンシブになるように比率で考えます。
f:id:Okerra:20180304201359p:plain
これにより画像の中心は(x, y)=(75vw, 50vh)で表現できます。

vh -> 表示されているディスプレイ高さ
vw -> 表示されているディスプレイ幅

100vw:100vh なら画面いっぱいを表します。75vw, 50vhなら画面の幅75%、高さ50%の位置を表現できます。
直接pxで表現することもできるのですが、どの画面サイズで見ても同じ位置に来るようにvh,vwの比率表現を使います。

2.画像の左上の座標を考える

画像の左上の位置を考えていきます。
f:id:Okerra:20180304194656p:plain

画像の中心が分かりましたが、間違ってもこれを設定しないように注意して下さい。
f:id:Okerra:20180304194142p:plain
このようになっていしまいます。

そのため次にこの中心からどれだけ離れているかを考えます。
今回は画像の大きさが400pxと決めていましたので、x,yの座標は下のように表現できることがわかります。
f:id:Okerra:20180304194349p:plain

実装

ここまで分かればあとは実装するだけです。

HTML

<div class="toppage-art">
  <div id="img-bg">
  </div>
  <div class="top-img">
    <img src="img.jpg", alt="top_art">
  </div>    
</div>

CSS

//背景となる親プロパティの設定
.toppage-art{
  width: 100vw; 
  height: 100vh;
  position: relative;                 //絶対位置の設定
}

//位置が分かりやすいように背景色を設定(positionの説明とは関係なし)
.toppage-art #img-bg{
  height: 100vh;
  width: 75vw;
  background-color: #e1e1e1; 
}

//子classの設定
.toppage-art .top-img{ 
  position: absolute;
  padding: 0;
  top:  calc(50vh - (400 * 0.5));    //親classの上からの位置
  left: calc(75vw - (400 * 0.5));    //親classの左からの位置    
}
 //画像の大きさ指定
.top-img img {
  width: 400px;
  height: 400px;
}

これでようやく思った通りの位置に設定することができました。
f:id:Okerra:20180304200008p:plain

私もhtml/cssの勉強を始めたばかりですが、positonを理解するとレイアウト表現の幅がぐっと広がるのでぜひ使ってみて頂きたいと思います。

参考サイト、書籍

positon について分かりやすいサイト
saruwakakun.com

HTML CSSを勉強するのにおすすめの本

HTML&CSSとWebデザインが 1冊できちんと身につく本

HTML&CSSとWebデザインが 1冊できちんと身につく本

RailsのDBから登録数の多い順にソートして取得する方法

Railsのデータベース操作がまだまだ未熟なため、簡単なことですがメモしておきます。
今回はデータベースに登録されている情報から、あるカラムに注目し多い順にソートしてインスタンスを取得する方法についてまとめます。

前置き

イメージを湧きやすくするために作成中のサービスについて説明します。

今作っている機能で、ユーザーがCDを検索して気に入ったものがあれば、お気に入り登録をするという機能があります。
その時のデータベースのリレーションはこのようになっています。

User n ----- 1 中間テーブル 1 ------ n Album

そして今回ソート対象の中間テーブルをMysqlで見ると下記のようになっています。
f:id:Okerra:20180304153057p:plain
複数のユーザーがどのアルバムを登録したかの紐付け表となっています。

このデータベースから登録数の多い順に、アルバム情報を取得する方法を検討します。

GROUP BY と ORDER と COUNT

登録数の多い順に表示するのは難しいことはなく、GROUP BYとORDER、COUNTを使えばできます。今回はその中でもトップページにTOP1位のアルバムを表示したかったためFirstを使っていますが、付けなければ重複をなくし、多い順にソートした全インスタンスを取得できます。

class ToppagesController < ApplicationController
  def index
    @topArt = RelationAlbum.group(:album_id).order("count(album_id) desc").first;
    @album  = Album.find(@topArt.album_id)
  end
end

これをControllerに記述した時に、実際に実行されるSQL文もrails cを使って見てみます。
firstなしの場合

SELECT `relation_albums`.* FROM `relation_albums` GROUP BY `relation_albums`.`album_id` ORDER BY count(album_id) desc

firstありの場合

SELECT `relation_albums`.* FROM `relation_albums` GROUP BY `relation_albums`.`album_id` ORDER BY count(album_id) desc  LIMIT 1

ちなみに知りませんでしたが、firstをつければLIMITというSQL文が付け加えられるようです。今までControllerが上手くやってくれていましたが、たまにはrails cで実行されるSQL文を見ると結構勉強になります!

そして4月のTechAcademyコンテストで絶対に勝ちたいので、これから頑張ります。
(過去のコンテストの優秀賞の作品のレベルが高いのでビビってます...)