Michi's Tech Blog

一人前のWebエンジニアを目指して

JavaScriptの関数を完全に理解する①

こんにちは!
スマレジ テックファームのMichiです!

今回は「JavaScriptの関数を完全に理解する」というテーマで、JSの関数について整理したいと思います。

function命令で定義する

基本形

function getTriangle(base, height) {
  return base * height / 2
}

/* 呼び出し */
getTriangle(5, 2)  // 結果:5

まずは基本形から。
function命令を用いたシンプルな記述ですが、実務ではほぼ見かけない気がします。

無名関数(匿名関数)

function(base, height) {
  return base * height / 2
}

先ほどの形から、function後の関数名を省略したタイプです。
その場限りで一度しか使用されない関数に使用されます。これも実務ではほぼ見かけない印象。

関数リテラル表現で定義する

リテラルとは、データ型に格納できる値そのもの、また、値の表現方法のことです。ま、簡単に言えば「変数に格納できる値」と考えてもらって大丈夫です。 JavaScriptにおいては関数もデータ型の一種です。なので、関数もリテラルとして表現できるし、関数リテラルを変数に代入することもできます。

const getTriangle = function(base, height) {
  return base * height / 2
}

/* 呼び出し */
getTriangle(5, 2)  // 結果:5

以上を踏まえて、関数リテラルを使って表現したのがこの形。先ほどの無名関数を変数に代入しています。

アロー関数で定義する

基本形

const getTriangle = (base, height) => {
  return base * height / 2
}

アロー関数を利用することで、先ほどの関数リテラルをよりシンプルに記述できます。
アロー関数ではfunctionの代わりに、=>(アロー)で引数と関数本体をつなぎます。だんだんと実務でよく見る形になってきました。

returnと{}の省略

const getTriangle = (base, height) => base * height / 2

関数本体({...}の中身)が1文である場合には、{}を省略できます。 また、文の値がそのまま戻り値とみなされるので、return命令も省略可能です

引数の()の省略

const getCircle = radius => radius * radius * Math.PI

さらに、引数が1個の場合は、引数をくくる()も省略できます。
さすがにここまで省略すると分かりにくいので、うちのチームではこの形は使わないルールになっています。

※注意

const hello = () => console.log('Hello World!')

上記のように、引数がない場合は、()を省略することはできません。

function命令と関数リテラルの違い

ここまではちょっとJSを触ったことがある人なら知ってること。
ここで私思いました。「結局、function命令と関数リテラルの違いって何よ?」って。
ということで、詳しく調べてみました。

関数リテラルは実行時に評価される

console.log('三角形の面積:' + getTriangle(5, 2))

const getTriangle = function(base, height) {
  return base * height / 2
}

// 結果:Uncaught ReferenceError: Cannot access 'getTriangle' before initialization 

上のコードはエラーになります。
console.logで呼び出している時点で、変数getTriangleはまだ定義されていないので当然です。 関数リテラルで定義する場合は、呼び出し元のコード(console.log)より先に記述する必要があるのです。
この結果から、「関数リテラルは実行時(代入時)に評価される」ということが分かります。

function命令は静的な構造を宣言する

console.log('三角形の面積:' + getTriangle(5, 2))

function getTriangle(base, height) {
  return base * height / 2
}

// 結果:"三角形の面積:5"

では、function命令で記述したときはどうでしょうか?
普通に考えれば、console.logで呼び出している時点で、関数getTriangleはまだ定義されていないので、これもエラーになるような気がしますが、実際には問題なく動作します。
これはfunctionが動的に実行される命令ではなく、静的な構造を宣言するためのキーワードだからです。「静的な構造」というと分かりにくいですが、要するに、「function命令はコードを解析/コンパイルするタイミングで関数を登録している」 ということです。 したがって、実行時にはすでに関数getTriangleは定義されているため、ファイル内のどこからでも呼び出すことができるというわけですね。

まとめ

JavaScriptの基本的な関数構文、およびそれぞれの違いについて説明しました。
続きは「JavaScriptの関数を完全に理解する②」でやります。

JavaScriptの日付型の罠

この記事は「つながる勉強会 Advent Calendar 2022」の12日目の記事です。

こんにちは!
スマレジ テックファームのMichiです!

今回はJavaScriptの日付型でハマった罠についてご紹介します。

やりたいこと

とある勤怠管理システムで、検索の対象範囲として「開始日」と「終了日」を入力できるものとする。
画面から入力された日付の範囲に応じて、表示される勤怠情報を絞り込みたい。

変数の説明

  • fromDate ... 検索開始日。YYYY-MM-DD形式。例)2022-12-01
  • toDate ... 検索終了日。YYYY-MM-DD形式。例)2022-12-31
  • attendances ... 各日付ごとの勤怠情報を格納する配列。配列の各要素はオブジェクトになっており、 {targetDate: 'YYYY/MM/DD'}の形式で対象日付を指定している。 例){targetDate: '2022/12/12'}

コード

// 入力された値をDateオブジェクトに変換する
const parsedFromDate = new Date(fromDate)
const parsedToDate = new Date(toDate)

// targetDateで検索対象のattendanceを絞り込む
const targetAttendances = attendances.filter((attendance) => {
  const targetDate = new Date(attendance.targetDate)
  return targetDate >= parsedFromDate && targetDate <= parsedToDate
})

問題

期待される結果に対して、検索後のデータが1つ足りないときがある。
どうやら、検索開始日に該当する勤怠情報が取得できていないっぽい。

調査

ということで、Dateオブジェクトの中身をすべてconsole.logして調べてみた。

console.log(parsedFromDate)
// 結果:Thu Dec 01 2022 09:00:00 GMT+0900 (GMT+09:00)
console.log(parsedToDate)
// 結果:Thu Dec 01 2022 09:00:00 GMT+0900 (GMT+09:00)

console.log(targetDate);
// 結果:Mon Dec 01 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:Mon Dec 02 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:Mon Dec 03 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:...

なんと、画面からの入力値と検索対象のオブジェクトで、日付の時間が違うじゃありませんか!
詳しく調べてみると、こういうことらしい。

  • YYYY/MM/DD形式 → UTC世界標準時)。時刻を指定しない場合はすべて00:00:00となる。
  • YYYY-MM-DD形式 → ローカル時間。今回はJST日本標準時)で解釈されたため、UTCからの時差+9時間が加算された。

解決策

つまり、YYYY/MM/DD形式 と YYYY-MM-DD形式 を比較していることが根本要因なので、どちらかに統一してやる必要がある。問題はどちらに統一するべきかということだが、MDNのリファレンスにはこんな記載があった。

メモ: Date コンストラクター(および Date.parse と同等)で日付文字列を解釈する際には、常に入力が ISO 8601 形式 (YYYY-MM-DDTHH:mm:ss.sssZ) であることを確認してください。他の形式で解釈した場合には、その挙動は実装によって定義されていて、すべてのブラウザーで動くとは限りません。多数の異なる形式に対応するためには、ライブラリーが役に立ちます。

というわけで、仰せの通りYYYY-MM-DD形式に統一する。

// 入力された値をDateオブジェクトに変換する
const parsedFromDate = new Date(fromDate)
const parsedToDate = new Date(toDate)

// targetDateで検索対象のattendanceを絞り込む
const targetAttendances = attendances.filter((attendance) => {
  // スラッシュをハイフンに変換してから、Dateオブジェクト化する
  const targetDate = new Date(attendance.targetDate.replace('/', '-'))
  return targetDate >= parsedFromDate && targetDate <= parsedToDate
})

これで期待通りの結果が抽出できた。

まとめ

今回はJavaScriptの日付型の罠について解説しました。
上の例では対処療法的にreplaceで表示形式を変更しましたが、そもそも設計段階で、APIから返却する値をすべてYYYY-MM-DD形式しておくのが良いと思います。

勉強会でLTしてきました!

こんにちは!
スマレジ テックファームのMichiです!

先日、大阪のITエンジニア勉強会に参加してきました!
今回はその内容をレポしたいと思います。

勉強会の概要

今回参加してきたのは、毎月大阪で開催されている「ココカラ勉強会」という勉強会です。

kokokara.connpass.com

非常にアットホームな雰囲気で、私もエンジニアになる前から毎回参加させてもらっています!
ちなみに、今のスマレジでの仕事も勉強会の縁で紹介していただいたものです。

内容としては毎回、有志3人がLT(ライトニングトーク)と呼ばれる発表を行い、その後にグループトークという名の交流の機会があります。 勉強会終了後は2次会と称して、行きたいメンバーだけで飲みに行ったりします(個人的にこっちがメインイベントだったりする←)。

勉強会の集合写真

LTしてきました!

LTでは発表者が業務や自主学習の中で気づいたことを事前にスライドにまとめ、シェアしてくれます。

今回は私もLTしてきました!
テーマは「【英語】あなたの発音、間違ってます」

「いやいや、IT全く関係ないやん!」というツッコミはなしで😅

今回このテーマを選んだ理由は2つあります。

  1. 英語ができればキャリアの幅が広がる。特にITエンジニアは日常的に英語に接する機会が多いので、英語に興味を持っている人も多いと思ったから。
  2. エンジニア歴半年の自分にとって、ITの知識より自分の得意なこと(英語)のほうがシェアできることが多いと考えたため。

LT中の私

ありがたいことに、

  • めちゃくちゃわかりやすかったです!
  • LT聞いている時、学校の先生にしか見えなかったです。
  • 実際に話せる人のLTとても面白かったです!小、中の時に聞きたかったです!

などの感想を頂きました。

ココカラ勉強会ではLT後に必ず、発表者への質問と感想をフィードバックする時間が設けらているのが他の勉強会と違った良いところかなと思います。 フィードバックを頂けると、「次もまたLTしてやろう!」という気持ちになりますね。

発表当日に使用した資料を置いておきます。
勉強会に参加していない方でも、一度見てもらえると嬉しいです!

https://www.canva.com/design/DAFStidFBWw/vyzR7CgNDbNF7zIPKYlUxw/view?utm_content=DAFStidFBWw&utm_campaign=designshare&utm_medium=link&utm_source=publishsharelink

まとめ

今回は勉強会に参加してきたレポ記事でした! 勉強会に参加することのメリットは、

  • 同業種の交友が広がる
  • LTなどで新たな知識が得られる
  • 周りの刺激を受けてモチベーションがアップする
  • 転職や副業のリファラルにつながる

など数えきれないほどあると思います。
まだ勉強会に参加したことないよというエンジニアの方は、この機会に是非参加してみてはいかがでしょうか?

【JavaScript】条件によって変数の値を振り分ける

こんにちは!
スマレジ テックファームのMichiです!

今回はJavaScriptの基本的な内容ですが、意外と知らなかったので記事にします。

やりたいこと

変数の値を条件によって振り分けたい。
例えば、18歳以上か未満で料金設定が変わる場合の実装を考えてみます。

const age = 20;

if (age >= 18) {
  const price = 2000;
} else {
  const price = 1500;
}

console.log(price);

PythonPHPならこれで問題ないのですが、JavaScriptだと変数priceが定義されていないと怒られます。

Uncaught ReferenceError: price is not defined 

原因

const, 及びlet宣言はブロックスコープに対応した変数を宣言するため。
これらの変数は、宣言したブロックスコープ内でしか使用できない(書籍『JavaScript 本格入門』 p192より)。

要するに、ifブロックの中で宣言した変数はそのifブロックの中でしか使えないよ、という話です。

解決策

その① varを使う

varで宣言された変数はブロックスコープを持ちません。
なので、最初のコードのconstvarに変えるだけで解決します。

const age = 20;

if (age >= 18) {
  var price = 2000;
} else {
  var price = 1500;
}

いやいや、今どきvar使うのはいかんでしょ。理由は変数の巻き上げが起こるからです(詳しい解説はリンク先を参照)。
というわけでこの解決策はなし。

その② ifの外で最初に変数を宣言する

ifの中で変数宣言するとブロックスコープができてしまうなら、グローバルスコープで変数宣言すれがいいやん、という話です。

const age = 20;
let price;

if (age >= 18) {
  price = 2000;
} else {
  price = 1500;
}

当たり前だけど、最初にconstで宣言すると後から条件による値を再代入できなくなるから注意。
(そもそもconstで代入なしの宣言ってできないけど)

その③ 三項演算子を利用する

そもそもこのくらいの条件分岐なら三項演算子で書いちゃいなYO

const age = 20;
const price = age >= 18 ? 2000 : 1500

コードもすっきりしてて、可読性が高いですね!
実際に僕が実務で触ったコードはもっと複雑な分岐で三項演算子は使えませんでしたが、これくらいの分岐なら三項演算子一択な気がします。

まとめ

今回はJavaScriptの基本的なブロックスコープの仕組みについて解説しました。
JSもっと勉強せねばな~。

FlaskでUPSERT機能を実装する

こんにちは!
スマレジ テックファームのMichiです!

今回はFlaskでUPSERT機能を実装する方法を解説します。
UPSERTとは、保存対象のデータに対して、すでにDBに同じキーを持つデータが存在している場合はUPDATE、存在しない場合はINSERTする処理のことです。
Flaskでは標準でこのUPSERT機能を持っていないので、自分で実装する必要があります。(PostgreSQLの場合は標準機能で対応できるらしいが、MySQL/MariaDBは対応していないらしい...)

単独UPSERT

今回は例では、ユーザーが自分の勤怠情報を登録できるシステムを例にします。
user_idtarget_dateカラムをキーにして、すでにDBに同じデータが存在する場合はUPDATE、存在しないはINSERTの処理を実行します。

def upsert(user_id, target_date, attendance_type):
    # テーブルにすでに同じuser_id, target_dateを持つデータが存在するかチェック
    attendance = Attendance.query.filter(
        Attendance.user_id == user_id,
        Attendance.target_date == target_date,
    ).first()

    # 存在する場合はattendance_typeを上書きする
    if attendance:
        attendance.attendance_type = attendance_type
    # 存在しない場合は新規のattendanceオブジェクトを作成して、session_addに加える
    else:
        attendance = Attendance(
            user_id=user_id,
            target_date=target_date,
            attendance_type=attendance_type,
        )
        db.session.add(attendance)

    # クエリの実行
    db.session.commit()
    return attendance

同じキーを持つデータが存在するか最初に問い合わせ、その結果次第でUPDATE OR INSERTの処理を分けます。 まあ、これは簡単。

一括UPSERT

def pre_upsert(user_id, target_date, attendance_type):
    # テーブルにすでに同じuser_id, target_dateを持つデータが存在するかチェック
    attendance = Attendance.query.filter(
        Attendance.user_id == user_id,
        Attendance.target_date == target_date,
    ).first()

    # 存在する場合はattendance_typeを上書きする
    if attendance:
        attendance.attendance_type = attendance_type
    # 存在しない場合は新規のattendanceオブジェクトを作成する
    else:
        attendance = Attendance(
            user_id=user_id,
            target_date=target_date,
            attendance_type=attendance_type,
        )

    return attendance


def bulk_upser(user_id, target_year, target_month, target_day, attendance_type,):
    # 一括UPSERTの対象となる日付を絞り込む
    last_day = monthrange(target_year, target_month)[1]
    date_list = [date(target_year, target_month, day) for day in range(1, last_day + 1)]
    target_dates = list(filter(lambda d: datetime.weekday(d) == target_day, date_list))

    # 対象日付の回数分pre_upsertをループして、結果を配列に格納する
    attendances = []
    for target_date in target_dates:
        attendances.append(
            pre_upsert(user_id, target_date, attendance_type)
        )

    # 一括インサートのクエリ発行(アップデート対象のオブジェクトは無視される)
    db.session.add_all(attendances)

    # クエリの実行
    commit()
    return attendances

次に複数の勤怠を一括でUPSERTしたいという場合です。
曜日ごとに一括で登録する機能を実装します。 ポイントは以下の2つです。

  1. 単独UPSERTで使ったupsert関数はpre_upsertという名称に変更して、UPDATE or INSERT対象となるオブジェクトを返すだけの関数にする。
  2. 実際のUPSERT処理はbulk_upsert関数の中で行う。db.session.add_all関数は、すでにプライマリーキーがテーブルに存在しているオブジェクトに対しては存在を無視して実行されるので、自動的にUPDAREとINSERTの処理を分けることができる。

まとめ

FlaskでUPSERT機能を実装する方法を解説しました。
一括UPSERTのUPDATE処理のほうはデータごとにクエリが発行されてしまい、大量のデータを更新するときは遅くなってしまうので注意です。
(一括UPDATEする方法はあるのか...?知っている人がいれば教えてください。)

Flaskを無理やりMVCっぽく運用してみた

こんにちは!
スマレジ テックファームのMichiです!

みなさんはFlaskというフレームワークをご存知でしょうか?
FlaskはPython製のマイクロWebフレームワークで、MVTモデル(Model, View, Template)というアーキテクチャを採用しています。

ですが、「やっぱり使い慣れたMVCの枠組みで実装したい!」ということで、今回はFlaskを無理やりMVCっぽく運用する方法を解説します。

解説

アプリのサンプルはFlask公式ドキュメントのチュートリアルより、記事用に一部改変したものを使います。

ディレクトリ構成

.
├── app
│   ├── controllers
│   └── models
│   └── static
│   └── templates
│   └── __init__.py
│   └── auth.py
│   └── config.py
│   └── database.py
│   └── route.py
├── migrations
├── .env
├── local.sqlite
├── requirements.txt

基本設定

公式ドキュメントではすべての設定を__init__.pyファイルに書きますが、設定ごとに分割したいのでファイルを分けます。

認証系

auth.py

from flask import Flask
from flask_login import LoginManager


login_manager = LoginManager()

def init_auth(app: Flask):
    login_manager.login_view = 'login' 
    login_manager.login_message = ''
    login_manager.init_app(app)

コンフィグ

config.py

from flask import Flask
from pathlib import Path


def set_config(app: Flask):
    app.config.update(
        SECRET_KEY='dev',
        WTF_CSRF_SECRET_KEY='csrf',
        SQLALCHEMY_DATABASE_URI=f'sqlite:///{Path(__file__).parent.parent / "local.sqlite"}',
        SQLALCHEMY_TRACK_MODIFICATIONS=False,
    )

データベース

database.py

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()

def init_db(app: Flask):
    db.init_app(app)
    Migrate(app, db)

Flaskインスタンスの作成

__init__.py

from flask import Flask
from flask_wtf.csrf import CSRFProtect

from app.auth import init_auth
from app.database import init_db
from app.config import set_config
from app.route import create_route


def create_app(testing: bool=False):
    app = Flask(__name__)

    csrf = CSRFProtect()
    csrf.init_app(app)

    set_config(app)
    init_db(app)
    init_auth(app)
    create_route(app)

    if testing:
        app.config.update(TESTING=True)

    return app

ここまでで作成した設定ファイルの関数を__init__.py側で呼び出し、引数としてFlaskインスタンスを渡します。 これでアプリの初期設定が完了しました。
尚、create_route(app)については解説がまだですが、後ほど解説します。

モデル

モデルも公式ドキュメントでは一つのファイルにすべてのモデルを定義していますが、今回はテーブルごとに分割します。

├── models
│   └── __init__.py
│   └── article.py
│   └── user.py

modelsディレクトリ直下にそれぞれのモデルと__init__.pyファイルを用意します。

article.py

from app.database import db

from datetime import datetime


class Article(db.Model):
    __tablename__ = 'articles'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    title = db.Column(db.String, nullable=True)
    body = db.Column(db.String, nullable=True)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)

    user = db.relationship('User', backref='articles')

user.py

from app.auth import login_manager
from app.database import db

from flask_login import UserMixin


class User(db.Model, UserMixin):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True, nullable=True)
    password = db.Column(db.String, nullable=True)


@login_manager.user_loader
def load_user(id):
    return User.query.get(id)

__init__.py

from .article import Article
from .user import User


__all__ = [Article, User]

__init__.pyに定義したモデルを記述するのを忘れないように。
ここに記述しておかないと、Flaskが各モデルを認識してくれません。

ルーティング

公式ドキュメントではルーティングとビジネスロジックをすべて同じファイル内に記述していますが、責務分離を明確にしたいので、ルーティングとコントローラにそれぞれ分割します。
まずはルーティングから。appディレクトリ直下にroute.pyファイルを置きます。

route.py

from flask import Flask
from flask_login import login_required

from app.controllers import article_controller, auth_controller, user_controller


def create_route(app: Flask):
    @app.route('/signup', methods=['GET', 'POST'])
    def signup():
        return user_controller.signup()

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        return auth_controller.login()

    @app.route('/logout', methods=['GET'])
    def logout():
        return auth_controller.logout()

    @app.route('/', methods=['GET'])
    @login_required
    def index():
        return article_controller.read()

    @app.route('/create', methods=['GET', 'POST'])
    @login_required
    def create():
        return article_controller.create()

    @app.route('/<int:id>/update', methods=['GET', 'POST'])
    @login_required
    def update(id):
        return article_controller.update(id)

    @app.route('/<int:id>/delete', methods=['POST'])
    @login_required
    def delete(id):
        return article_controller.delete(id)

全体を括っているcreate_route関数は、最初に設定した__init__.pyファイルからFlaskインスタンスを渡して呼び出します。

コントローラ

├── controllers
│   └── article_controller.py
│   └── auth_controller.py
│   └── user_controller.py

controllersディレクトリの中身はこんな感じ。
記事のCRUD(article)、認証系(auth)、ユーザー登録(user)でファイルを分割してみました。

article_controller.py

from flask import (
    abort,
    flash,
    redirect,
    render_template,
    request,
    url_for
)
from flask_login import current_user

from app.database import db
from app.models import Article


def read():
    articles = Article.query.order_by('created_at').all()
    return render_template('index.html', articles=articles)


def create():
    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = None

        if not title:
            error = 'Title is required.'

        if error is not None:
            flash(error)
        else:
            article = Article(title=title, body=body, user_id=current_user.id)
            db.session.add(article)
            db.session.commit()
            return redirect(url_for('index'))

    return render_template('create.html')


def update(id):
    article = Article.query.filter(Article.id == id).first()

    if article is None:
        abort(404, f"Post id {id} doesn't exist.")
    elif article.user_id != current_user.id:
        abort(403)

    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = None

        if not title:
            error = 'Title is required.'

        if error is not None:
            flash(error)
        else:
            article.title = title
            article.body = body
            db.session.commit()
            return redirect(url_for('index'))

    return render_template('update.html', article=article)


def delete(id):
    query = Article.query.filter(Article.id == id)
    article = query.first()

    if article is None:
        abort(404, f"Article id {id} doesn't exist.")
    elif article.user_id != current_user.id:
        abort(403)

    query.delete()
    db.session.commit()
    return redirect(url_for('index'))

auth_controller.py

from flask import (
    flash,
    redirect,
    render_template,
    request,
    url_for,
)
from flask_login import login_user, logout_user
from werkzeug.security import check_password_hash

from app.models import User


def login():
    if request.method == 'POST':
        name = request.form['name']
        password = request.form['password']
        error = None
        user = User.query.filter(User.name == name).first()

        if user is None:
            error = 'Incorrect name.'
        elif not check_password_hash(user.password, password):
            error = 'Incorrect password.'

        if error is None:
            login_user(user)
            return redirect(url_for('index'))

        flash(error)

    return render_template('login.html')


def logout():
    logout_user()
    return redirect(url_for('index'))

user_controller.py

from flask import (
    flash,
    redirect,
    render_template,
    request,
    url_for,
)
from flask_login import login_user
from werkzeug.security import generate_password_hash

from app.database import db
from app.models import User


def signup():
    if request.method == 'POST':
        name = request.form['name']
        password = request.form['password']
        error = None

        if not name:
            error = 'name is required.'
        elif not password:
            error = 'Password is required.'
        elif User.query.filter(User.name == name).first() is not None:
            error = f"User {name} is already registered."

        if error is None:
            user = User(
                name=name,
                password=generate_password_hash(password)
            )
            db.session.add(user)
            db.session.commit()
            login_user(user)
            return redirect(url_for('index'))

        flash(error)

    return render_template('signup.html')

あとは、templatesstaticにHTMLとCSSを書いて、マイグレーションを実行すればアプリ完成です!

まとめ

FlaskをMVCっぽく使う方法について解説しました。

今回はコントローラにDBとのやり取りを記述しましたが、これをサービス層に切り出すとより明確な責務分離が行えます。
(私も実務ではservicesディレクトリを切って運用を行っています)

ここまでやるならDjango使えばええやん

【初投稿】ブログはじめます

はじめまして!
株式会社スマレジ テックファーム事業部のMichiと申します。
2022年5月より異業種からWebエンジニアに転身した、いわゆる駆け出しエンジニアです!

この度、ブログをはじめることになりましたので、今日は記念すべき初投稿として自己紹介などしていきたいと思います。

自己紹介

略歴

  • 22歳 新卒で電機メーカーに就職。液晶ディスプレイの生産管理に従事
  • 26歳 英語学習のためカナダ・トロントに留学。語学学校に通いながら、生活費を稼ぐため現地でアルバイトをする生活を送る
  • 28歳 帰国し自動車メーカーに再就職。車載部品の生産管理に従事
  • 29歳 株式会社スマレジへ入社。30歳を目前にしてWebエンジニアへキャリアチェンジ

主な技術、資格など(勉強中のものも含めて)

  • バックエンド:Python(Flask, Django), PHP(Laravel), SQL(MySQL, MariaDB)
  • フロントエンド:JavaScript(Vue.js, jQuery)
  • その他:TOEIC 900(3年前カナダに住んでいるときに取ったスコアなので、多分もう今は無理...笑)

スマレジ・テックファームとは?

スマレジ・テックファームとは、経験が少ないITエンジニアを受託・SES事業を通して育成しようとする事業です。 私のような未経験エンジニアから実務経験3年未満のジュニアエンジニアを対象として、本人の成長を第一に考えた案件を探してくれます。

現在、テックファーム事業部ではエンジニアを積極的に募集しています! 少しでも興味のある方は、カジュアル面談も随時実施しておりますので、お気軽に下記リンクよりお申し込みください。

corp.smaregi.jp

このブログについて

なぜブログをはじめるのか

  • 自分自身の勉強のため
    よく言われることですが、学んだことをアウトプットすることで自身の成長につなげたいと思っています。

  • 月1万円ほしいから
    弊社にはブログ手当をいうものがありまして、会社の名前を使ってブログを書くことでなんと月1万円の手当てが貰えるのです。いい会社だなあ。

どんな記事を書いていくのか

技術記事の投稿がメインとなる予定です。 自分が業務や自己学習の中で気づいた点、つまずいた点などをアウトプットしていきます。 たまにはIT業界に対して思うことやプライベートのことなども書ければいいかな...。


とういうわけで、読者の皆様、これからどうぞよろしくお願いします!