弱々だけど Flask-SQLAlchemy (ver3.1.x) をQuickStartしたい

# 学習に至った経緯
　プログラミング学習歴約１年で諸々分かってない状態ではあるのですが、無料共助学習エンジニアコミ二ティ（Progaku）において、「flask（Pythonのフレームワーク）＋ SQLite（RDBMS≒DBサーバー）」で、プログラムを作成する有志のモブプロ（≒少人数チーム開発）に現在参加させてもらっています。
 
　そこで、SQLを直書きするより、Flask-SQLAlchemy なるものを活用する方が楽そう＆セキリティも堅牢そうということで、自分のチートシートとして公式ドキュメントのQuickStartの部分のざっくりとした邦訳やそれに関する用語等を書き留めました。

　※Progakuの方の話によると使用する方が [SQLインジェクション（※資料⑥を参照）](#参考にさせていただいた資料)等のサイバー攻撃を回避しやすくなるそうです。

　なお、**結論一番重要そうなのは、[「データの変更と照会」](#データの変更と照会)** の部分であるように感じました（※👆のように、Qiitaにおけるページ内をリンク今更覚えました。）

　また、学習歴たかが１年では知識量も知れているので、分からない用語が多々あり、以下の [よわよわ用語集](#よわよわ用語集) にまとめました。

<h2>そもそもSQLAlchemy、Flask-SQLAlchemyとは</h2>

<details><summary>SQLAlchemy、Flask-SQLAlchemy概要</summary>

[以下の資料](#参考にさせていただいた資料) や知り合いの既に使われたことがある方によれば、そもそも
<h4>SQLAlchemy、Flask-SQLAlchemy は「Pythonにおける」ORM：（Obeject Relational Mapping（またはMapper））の１つ</h4>

らしいです。（[特に資料②、③、⑤あたり](#参考にさせていただいた資料)が分かりやすい気が）

　※要するに、**紐付けてあるDB、RDBMS（Relational DataBase Management System（ここではSQLite）を操作するSQLの（セットの）代わりとなる楽な書き方**の１つらしいです。
___

　なお、[資料②](#参考にさせていただいた資料)を読む限り、SQLAlchemy、Flask-SQLAlchemyを使うには、まず.py のマイグレーションファイル（データベース及びそのテーブルを生成する際の設計図になるファイル）を作る必要がありそうです。
 
　※Python の別のフレームワークである Django やScrapy では、[資料②](#参考にさせていただいた資料)にあるように、ターミナルである程度「プロジェクト」と呼ばれる単位のプログラムの雛形（のフォルダやファイル）を一気に作りますが、**flask は基本最初スカスカというかフォルダを0から作るところから始めます。(-_-;)（flaskにもそのような雛形を作るコマンドが実は自分が知らないだけで実はある？？）**
 
　ただ、「 0 の状態でフォルダから作る必要がある」＝「設計・フォルダ構成（またはファイル構成）が多少変でも動きやすい」とも言えるので、そこは一長一短ある気がします（※実際、[資料⑧](#参考にさせていただいた資料)のアプリを動画に沿って作ったときに、完全に同じ階層構造にしなくても動きました。）

　※私は民間プログラミングスクール利用時にPHPとLaravel（PHPのフレームワーク）メインで学習を進めていましたが、**Laravel にもEloquent ORMというORM がありました。（要するに、ORM は各フレームワークや言語ごとに複数あるという注意喚起です。** ※[資料⑦](#参考にさせていただいた資料)の５・６章あたりを見ると分かるように **Django にも Django で ORMがあります**）
</details>

# 公式ドキュメント

https://www.sqlalchemy.org/

https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/

<h2>③とりあえず、覚え書きを付け足しながら、後者の公式ドキュメントQuick Start の自分に必要そうな部分をざっくり邦訳を書き留めていく（※コーディングに実質不要そうな部分は省略しました）</h2>

（※邦訳は日本語への意訳が難しい場合はカタカナ、または英語のままにしました。下記の太字部分にあるように、上のリンクの**SQLAlchemyが分かっていれば、Flask-SQLAlchemyも分かる**ようですが、**厳密には、Flask-SQLAlchemy は SQLAlchemy のラッパー（wrapper）だそうです。（※[用語集①](#よわよわ用語集)）
　※要は、Flask-SQLAlchemy は SQLAlchemy を Flask で使いやすくするためにさらに簡単に使いやすくしたもの**）
____

<h2>クイックスタート序文（👇ここから公式ドキュメントの邦訳および、自分用覚え書き）</h2>

　**Flask-SQLAlchemy は SQLAlchemy のサポートを追加する Flask に対しての拡張機能**です。それぞれのWebリクエストやモデル、エンジンに関連付けられるセッションのように、共通のオブジェクトや、それらのオブジェクトを用いるパターン（[※用語集②](#よわよわ用語集)）を設定することで、Flask で SQLAlchemy で使うことを容易にします。
　**Flask-SQLAlchemy は、仕組みまたは使用方法はSQLAlchemy と変わりません。深くORM を扱う仕組みを学ぶためには、SQLAlchemy の公式ドキュメント（※上の②の前者のリンク）を参照してください。**
____

<h2>インストール</h2>

　Flask-SQLAlchemy は PyPI を通して利用することができ、様々なPythonのツールでインストールすることができます。例えば、以下のpipコマンドで最新バージョンをインストールしたり、最新のバージョンに更新できます。
```powershell
pip install -U Flask-SQLAlchemy
```

<h2>拡張機能の初期化</h2>

　まず以下のSQLAlchemy コンストラクタを使うことで db（DataBase）オブジェクトを作りましょう。DeclarativeBase または DeclarativeBaseNoMeta のいずれかのサブクラスをコンストラクタに渡します。

```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
  pass
　#この「pass」は上記のコードをコピペしたときにBaseクラスが
  #一時的にエラーとして認識されないようにするためのもの
  #開発時は必要なものに差し替える

db = SQLAlchemy(model_class=Base)
```
　基本モデルクラスのカスタマイズについて詳しくは、「[モデルとテーブル](#モデルとテーブル)」を参照

<h2>SQLAlchemy のオブジェクトについて</h2>

　**一度オブジェクトが生成されると、モデルを定義している db.Model クラスや、クエリを実行する db.session を使えるようになります。** SQLAlchemy オブジェクトは、管理する（or 付随する？）オブジェクトをカスタマイズするための追加引数も取ります。（👈どうも、ここ数日公式ドキュメントを眺めている限り、DBとのセッションにSQLのようなものを紐付ける？？(＠ ＠)？？ようです。[データの変更と照会](#データの変更と照会)参照）

<h2>拡張機能の設定</h2>

　次のステップは、Flask アプリへの拡張機能の接続です。Flask アプリに唯一必要な設定は、SQLALCHEMY_DATABASE_URI key です。それは、SQLAlchemy にどのデータベースに接続するべきかを伝えるコネクションのための文字列です。

　Flaskアプリケーションオブジェクトを作り、あらゆる設定を読み込み、db.init_appを呼び出すことで、そのアプリケーションと一緒に SQLAlchemy 拡張（機能）クラスを初期化します。**以下の例では、アプリのインスタンス・フォルダに格納されているSQLiteのデータベースに接続しています。**

```python
# appインスタンスを生成
app = Flask(__name__)
# アプリのインスタンス・フォルダに格納されているSQLiteのデータベースを構成
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
# 拡張機能を使って、アプリごと丸々初期化
db.init_app(app)
```
（上記の SQLALCHEMY_DATABASE_URI key のような）コネクションのための文字列の拡張や他にどんな設定キーが使われるかを調べるには[構成設定（？,※原文は Configuration）](#構成設定)を参照

# モデルの定義

　db.Modelサブクラスを以下のように書くことでモデルクラスを定義します。モデルは CamelCase クラス名を snake_case（単語間を_（アンダーバー）でつなぐ変数の記名法） に変換してテーブル名を生成するものです。（※Laravelでいうところのマイグレーションファイル）

```python
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

class User(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)   #文字列型、プライマリーキー制約
    username: Mapped[str] = mapped_column(unique=True)　
    email: Mapped[str]

#ここでは、データベースのユーザーテーブルを定義している
#上記の場合だと、id、username、email カラムを生成する
#左辺はデータ型、右辺は主キー制約などオプションのため必ずしも右辺が必要なわけではない
```
　モデルやテーブルの作成に関するより多くの情報を得るためには、[モデルとテーブル](#モデルとテーブル)を参照してください。

 ※なお、普通の SQLAlchemy と異なり、Flask-SQLAlchemy のモデルは、<INS>　</INS>tablename<INS>　</INS> が設定されておらず、プライマリキーカラムが定義されていれば、自動的にテーブル名が生成される

# テーブルの作成

（上のモデルの定義のように、作成するアプリに必要な）すべてのモデルとテーブルの定義が済んだら、以下のように、**SQLAlchemy の create_all()メソッドを呼び出して、データベースの中にテーブルスキーマ（[※用語集③](#よわよわ用語集)）を作成します。**<INS>以下にはアプリケーションコンテキストが必要です。この時点ではリクエストに入っていないので、手動で作成してください。</INS>（👈「実際以下の２行はappやdbを上記の定義してから、付け足す」という意味ではないかと思われます。）

```python
with app.app_context():
    db.create_all()
```

　もし、**他のモジュール（.pyファイルのこと）でモデルを定義しているのであれば、それらを（SQLAlchemy の）create_all()メソッドを呼び出す前にそれらをimportしなければなりません。** そうしないと、SQLAlchemyはそれらを知り得ないので、それらのモデルからテーブルは作られません。

　もし、同一のテーブルがデータベースにすでに存在するなら、create_all()メソッドはテーブルを更新しません。もし、あるモデル紐付いたテーブルのカラムを変更するなら、[Flask-Alembic](#マイグレーションファイル) を持つ [Alembic](#マイグレーションファイル) または、[Flask-Migrate](#マイグレーションファイル) のようなマイグレーションライブラリーを使って、データベーススキーマを更新するマイグレーション（[※用語集④](#よわよわ用語集)）を生成しましょう。

<h2>データクエリ（query(英)の原義は「問い合わせる」）</h2>

　Flask の CLI コマンドについては以下のもの（この節の注釈部分）がすでに邦訳が公式にありました。

　Flask の view ファイル や CLIコマンドの範囲内で、db.session を使って、クエリを実行してモデルのデータを修正することができます。

　**SQLAlchemy は自動的に、任意のキーワード引数を対応するデータベースのカラムやその他の属性（attribute）に割り当てているモデルそれぞれに対して、<INS>　 </INS>init<INS>　 </INS> メソッドを定義します。**

　**db.session.add(obj) でセッションにオブジェクトを追加し、挿入されるようにします。オブジェクトの属性（attribute）を修正することはオブジェクトの更新につながります。**

　**db.session.delete(obj) でオブジェクトを削除します。**

　**あらゆるデータを修正・追加・削除した後、db.session.commit() を呼び出すことを覚えておきましょう。**（👈普段の日常生活であまり使わないと思うのですが、**ITにおける「commit」は「変更を保存する」「処理や変更などを確定させる、反映させる」**）

　**db.session.execute(db.select(...)) でデータベースからデータをSELECT（取得）するクエリを構成します。クエリの構築は SQLAlchemy の主な機能です。** そのため、SELECT文に関する全てを学ぶためには、[SELECTに関するチュートリアル](#チュートリアル)を読むことになるでしょう。クエリの結果をリストの形で得るためには、scalars()メソッド、単一の結果を得るためには、scalar()メソッドをたいてい使うことになるでしょう。

```python
#@app.routeはFlaskでよく見られるルーティング設定（デコレータと呼ばれるもの）
#ユーザーが「/users」 URLにアクセスしたときFlaskは user_list 関数を呼び出す
@app.route("/users")
def user_list():
    #.scalars()は、クエリの結果をスカラー値（この場合は User オブジェクトのリスト）として返す。
    users = db.session.execute(db.select(User).order_by(User.username)).scalars()

    #上のdb.session.executeで取得したクエリの結果をビューにusersという名前で渡す
    return render_template("user/list.html", users=users)

@app.route("/users/create", methods=["GET", "POST"])
def user_create():
    if request.method == "POST":

        #User クラスのインスタンスを作成し、フォームから取得した username と email を設定
        user = User(
            #username、email は各 form の name属性
            username=request.form["username"],
            email=request.form["email"],
        )
        #新しいユーザーオブジェクトをデータベースセッションに追加
        db.session.add(user)
        #データベースへの変更をコミットし、新しいユーザーをデータベースに保存
        db.session.commit()
        #user_detail関数を呼び出し、その際にid=user.id は新しく作成されたユーザーのIDをURLに渡す
        return redirect(url_for("user_detail", id=user.id))

    #POST送信でない場合の返り値
    return render_template("user/create.html")
    
#<int:id> はURLの一部として整数型の id パラメータを受け取ることを示す
#※「/user/1」や「/user/42」のようなURLになる
@app.route("/user/<int:id>")
def user_detail(id):
    
    #db.get_or_404(User, id) は、SQLAlchemyのヘルパーメソッドで
    #指定したIDに対応する User オブジェクトをデータベースから取得しようとする
    #User はデータベースのテーブルに対応するモデルクラス
    #id はURLから取得したユーザーID
    #指定されたIDに対応するユーザーがデータベースに存在しない場合、自動的に404エラーページを返します。
    #⇒存在しないユーザーIDが指定された場合にエラーハンドリングが簡素化
    
    user = db.get_or_404(User, id)
    return render_template("user/detail.html", user=user)

@app.route("/user/<int:id>/delete", methods=["GET", "POST"])
def user_delete(id):
    user = db.get_or_404(User, id)

    if request.method == "POST":
        db.session.delete(user)
        db.session.commit()
        return redirect(url_for("user_list"))

    return render_template("user/delete.html", user=user)
```
　クエリの構築をするために Model.query の使用を見かけるかもしれない。これは SQLAlchemy ではレガシーとされている古いクエリのインタフェースです。代わりに、db.session.execute(db.select(...))の形式を使うようにしましょう。

クエリに関するより多くの情報を得るためには、[データの変更と照会 （※本文は Modifying and Querying Data）](#データの変更と照会)を参照してください。


<details><summary>FlaskのCLIコマンドの邦訳</summary>

以下のものがありましたが、
https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/cli.html
これは元々以下のものの一部であり、公式の最新版ではないことに注意
https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/index.html
</details>

<h2>覚えておくべきこと</h2>

　ほとんどの場合、SQLAlchemyを通常通り使うはずだ。SQLAlchemy の拡張機能のインスタンスは、以下のものを生成し、設定し、アクセス権を与えます：

　● SQLAlchemy.Model は宣言モデルの基底クラスです。これは、__tablename__を必要とする代わりに、自動的にテーブル名を設定します。
　● SQLAlchemy.session はアプリケーションコンテキスト（[※用語集⑥](#よわよわ用語集)）にスコープされたセッションです。
　● SQLAlchemy.metadata や SQLAlchemy.metadatas は設定で定義されるそれぞれのエンジンへのアクセスを提供します。
 　● SQLAlchemy.create_all() は全てのテーブルを作成します
  　● クエリを実行し、セッションとエンジンにアクセスするには、アクティブな Flask アプリケーション コンテキスト内を設定している必要があります。
  
# モデルとテーブル

　db.Model クラスを使ってモデルを定義するか、db.Table を使ってテーブルを作成しましょう。どちらもFlask-SQLAlchemyのバインドキーを扱い、特定のエンジンに関連付けます。

 <details><summary>バインドキー</summary>

**Flask アプリケーションで複数のデータベースを使用する場合に役立つ機能（通常、Flask-SQLAlchemy を使用してデータベース接続を管理する際には、単一のデータベースへの接続を行うようですが、複数のデータベースを扱う必要がある場合には、bind keys を使用することで各データベースを識別し、操作することができるそうです。）**

<h4>①Bind Keys の設定</h4>

以下のようなSQLALCHEMY_BINDS という設定オプションを使って、複数のデータベース接続を設定。（※キーが bind key、値がデータベースの接続URIになる。）

```python
SQLALCHEMY_BINDS = {
    'users': 'mysql://user@localhost/users_db',
    'appmeta': 'sqlite:///appmeta.db'
}
```
<h4>②モデルの定義</h4>

各モデルにおいて、以下の__bind_key__ 属性を設定することで、どのデータベースを使用するかを指定

```python
class User(db.Model):
    __bind_key__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

class AppMeta(db.Model):
    __bind_key__ = 'appmeta'
    id = db.Column(db.Integer, primary_key=True)
    version = db.Column(db.String(50))
```
</details>

以下、上記の [モデルの定義](#モデルの定義)、 [テーブルの作成](#テーブルの作成)、[データの変更と照会 （※本文は Modifying and Querying Data）](#データの変更と照会)に書いてある内容と概ねかぶるため省略

# データの変更と照会

<h3>挿入・更新・削除</h3>

　ORMを使用するデータの更新に関する多くの情報を得るためには、SQLAlchemy のORMチュートリアルや他の SQLAlchemy の[公式ドキュメント](#公式ドキュメント)を参照しましょう。

**データを挿入する** には、db.session.add()にモデルオブジェクトを渡します。
```python
#Userクラスのインスタンスを作成
user = User()

#新しいユーザーオブジェクトをデータベースセッションに追加
db.session.add(user)

#データベースへの変更をコミットし、新しいユーザーをデータベースに保存
db.session.commit()
```

　**データを更新する** にはモデルオブジェクトにおける属性（attribute）を修正します。
```python
#user.verified属性は、Userモデルの一部であり、
#ユーザーが検証されたかどうかを示すために使用されるブール値の属性？？
user.verified = True
db.session.commit()
```

　**データを削除する** には db.session.delete() にモデルオブジェクトを渡します。
```python
db.session.delete(user)
db.session.commit()
```
（いずれにせよ）**データを変更等した後は、db.session.commit() を呼び出して、データベースの変更を確定（コミット）させなければなりません。そうしないと、リクエストの終了時に破棄されます。**

<h3>SELECT文</h3>

　ORM を使って、データをクエリで操作する情報をより手に入れるためには SQLAlchemy クエリガイドや他の SQLAlchemy の公式ドキュメントを参照してください。

**（SQLAlchemyにおいては、）クエリは db.session.execute() を通して実行されます。そこでは以下のように引数に select()を用いることができます。** select を実行すると、返されたレコードを操作するための多くのメソッドを備えた結果オブジェクトが返されます。
```python
#「db.select(User)」で Userモデルに対するSELECT文を作成
#「.filter_by(username=username)」でクエリにフィルタを追加して、
# username属性が指定されたusername変数の値に一致するユーザーを絞り込む

# 「db.session.execute(...)」でこのクエリを現在のデータベースセッションで実行
# 「.scalar_one()」結果を1つのオブジェクトとして返します。もし結果が0件または2件以上の場合、例外が発生
user = db.session.execute(db.select(User).filter_by(username=username)).scalar_one()

#「.order_by(User.username)」でクエリに並べ替えを指定し、username属性に基づいて昇順に並べ替える。
#「.scalars()」でリストの形で結果を複数のオブジェクトとして返す。

users = db.session.execute(db.select(User).order_by(User.username)).scalars()
```
____
<h3>👇以下、「ビューのためのクエリ」の手前のところまで、Chat-GPT3.5先生由来(-_-;)なので、検証後必要に応じて修正します。</h3>

____

<h3>上のコードにおける「.filter_by」と「filter」</h3>

<h3>.filter_by</h3>

　**一般的なSQLにおけるWHERE句と同様の機能を果たす。**

次のコードは同じ
```python
user = db.session.execute(db.select(User).filter_by(username='john_doe')).scalar_one_or_none()
```
```sql
SELECT * FROM user WHERE username = 'john_doe';
```

なお、上記のように複数条件を指定した場合
<h3>.filter</h3>

**Pythonの比較演算子を使用して条件を指定する。複数の条件を組み合わせることができる。** 

次のコードは同じ
```python
user = db.session.execute(db.select(User).filter(User.username == 'john_doe', User.email == 'john@example.com')).scalar_one_or_none()
```

```sql
SELECT * FROM user WHERE username = 'john_doe' AND email = 'john@example.com';
```
_____

<h3>SQLAlchemyのSELECTで、「.filter」 を使うときに他の論理演算子を使う場合はどのように記述するか</h3>

____

<h4>　or_を使用して複数の条件を結合</h4>

```python
from sqlalchemy import or_

# 複数の条件をORで結合
user = db.session.execute(
    db.select(User).filter(
        or_(User.username == 'john_doe', User.email == 'john@example.com')
    )
).scalar_one_or_none()
```
これは次のSQLと同値

```sql
SELECT * FROM user WHERE username = 'john_doe' OR email = 'john@example.com';
```
____

<h4>　not_を使用して条件を否定</h4>

```python
from sqlalchemy import not_

# 条件を否定
user = db.session.execute(
    db.select(User).filter(
        not_(User.username == 'john_doe')
    )
).scalar_one_or_none()
```
これは次のSQLと同値

```sql
SELECT * FROM user WHERE username <> 'john_doe';
```
_____

<h4>　ANDとORを組み合わせた例</h4>

```python
from sqlalchemy import and_, or_

# 複雑な条件を作成
users = db.session.execute(
    db.select(User).filter(
        or_(
            and_(User.username == 'john_doe', User.email == 'john@example.com'),
            and_(User.username == 'jane_doe', User.email == 'jane@example.com')
        )
    )
).scalars()
```
これは次のSQLと同値

```sql
SELECT * FROM user 
WHERE (username = 'john_doe' AND email = 'john@example.com') 
OR (username = 'jane_doe' AND email = 'jane@example.com');
```
____

<h2>Flask-SQLAlchemy における SQL の UPDATE、DELETE に相当するようなより具体的な表記例（例えば、ユーザーのメールアドレス等を更新・削除する場合）</h2>

<h3>①基本的なFlaskアプリケーションとSQLAlchemyの設定（以下の②-A,B,C,D いずれにも共通）</h3>

```python
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return f'<User {self.username}>'

# データベースの初期化（初回実行時のみ）
with app.app_context():
    db.create_all()
```
<h3>②-A：db.select()を使ってメールアドレスを更新するためのエンドポイントを作成する場合</h3>

```python
@app.route('/update_email', methods=['POST'])

#JSON形式でusernameとnew_emailをPOSTリクエストを受け取る
def update_email():
    data = request.json
    username = data.get('username')
    new_email = data.get('new_email')

    #指定されたusernameに一致するユーザーをデータベースから取得
    #このとき、ユーザーにuniqueキーを設定する必要あり
    user = db.session.execute(db.select(User).filter_by(username=username)).scalar_one_or_none()

    #ユーザーが存在しない場合、404エラーを返す
    if user is None:
        return jsonify({"error": "User not found"}), 404

    #ユーザーが存在する場合、email属性を更新し、変更をデータベースにコミット
    user.email = new_email
    db.session.commit()

    #成功した場合、成功メッセージを返す
    return jsonify({"message": "Email updated successfully"})

if __name__ == '__main__':
    app.run(debug=True)
```

<h3>②-B：db.update()を使ってメールアドレスのドメインを「一括」更新するためのエンドポイントを作成する場合</h3>

```python
@app.route('/bulk_update', methods=['POST'])
def bulk_update():

    #JSON形式でnew_email_domainをPOSTリクエストを受け取る。
    data = request.json
    new_email_domain = data.get('new_email_domain')

    #Userテーブルのすべてのレコードのメールアドレスを、ユーザー名に新しいドメインを追加した形式に一括更新
    db.session.execute(
        db.update(User)
        .values(email=db.func.concat(User.username, new_email_domain))
    )
    db.session.commit()

    #成功した場合、成功メッセージを返す
    return jsonify({"message": "Emails updated successfully"})

if __name__ == '__main__':
    app.run(debug=True)
```

<h3>②-C：ユーザーを削除するためのエンドポイントを作成する場合</h3>

```python
@app.route('/delete_user', methods=['DELETE'])
def delete_user():
    data = request.json
    username = data.get('username')

    # JSON形式でusernameをDELETEリクエストを受け取る
    user = db.session.execute(db.select(User).filter_by(username=username)).scalar_one_or_none()

    # ユーザーが存在しない場合、404エラーを返す
    if user is None:
        return jsonify({"error": "User not found"}), 404

    # ユーザーが存在する場合、そのユーザーをデータベースセッションから削除し、変更をデータベースにコミット
    db.session.delete(user)
    db.session.commit()

    
    # 成功した場合、成功メッセージを返す
    return jsonify({"message": "User deleted successfully"})

if __name__ == '__main__':
    app.run(debug=True)
```
<h3>②-D：db.delete()を使ってあるドメイン含むメールアドレスのレコードを「一括」削除するためのエンドポイントを作成する場合</h3>

```python
@app.route('/bulk_delete', methods=['DELETE'])
def bulk_delete():

    # JSON形式でemail_domainをDELETEリクエストを受け取る
    data = request.json
    domain = data.get('email_domain')

    # 指定されたドメインを持つメールアドレスを持つユーザーを一括で削除し、変更をデータベースにコミット
    db.session.execute(
        db.delete(User)
        .where(User.email.like(f'%@{domain}'))
    )
    db.session.commit()

    # 成功した場合、成功メッセージを返す
    return jsonify({"message": "Users with the specified domain deleted successfully"})

if __name__ == '__main__':
    app.run(debug=True)
```

## ビュー（views）のためのクエリ

Flask のビューの関数を記述する場合、エントリー（[用語集⑧](#よわよわ用語集)）が見つからない場合、404 Not Foundエラーを返すと便利です。Flask-SQLAlchemy はいくつかの追加のクエリーメソッドを提供しています。（※引数などについてはより詳しくはここに載っていました。https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/api/）

・**SQLAlchemy.get_or_404()** は、id を与えられたレコードが存在しないならば、404 Not Found を返します。
・**SQLAlchemy.first_or_404()** は、クエリが結果を返さなければ 404 を返し、そうでなければ最初の結果を返します。
・**SQLAlchemy.one_or_404()** は、クエリがちょうど1つの結果を返さなかった場合は 404 を返し、そうでない場合は結果を返します。

```python
#<int:id>は、整数のIDをパラメータとして受け取ることを意味する
@app.route("/user-by-id/<int:id>")
def user_by_id(id):
    user = db.get_or_404(User, id)
    return render_template("show_user.html", user=user)

#URLの一部である<username>は、ユーザー名をパラメータとして受け取ることを意味する
@app.route("/user-by-username/<username>")
def user_by_username(username):
    user = db.one_or_404(db.select(User).filter_by(username=username))
    return render_template("show_user.html", user=user)
```
以下のようにして、カスタムメッセージを 404 エラーに付け加えることができる。
```python
user = db.one_or_404(
    db.select(User).filter_by(username=username),
    description=f"No user named '{username}'."
)
```

## レガシークエリインターフェイス

　クエリを構築するために、Model.query または session.query を使用するかもしれません。そのクエリインターフェイス（以下リンクを参照）は SQLAlchemy においてはレガシーと見なされます。代わりに **session.execute(select(...))** をより使うようにしましょう。

 レガシークエリインターフェイス公式ドキュメント
 https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/legacy-query/
 
# 構成設定

現時点では不要ではないかと思ったため、必要に応じて随時追記します
公式ドキュメント原文は以下　
https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/config/

# マイグレーションファイル

現時点では不要ではないかと思ったため必要に応じて随時追記します

<h3>Flask-Alembic</h3>
公式ドキュメント原文は以下の通り

https://flask-alembic.readthedocs.io/en/latest/

<h3>Flask-Migrate</h3>
公式ドキュメント原文は以下の通り

https://flask-migrate.readthedocs.io/en/latest/

# チュートリアル

時間を見て随時追記します（公式ドキュメントは以下の通り）

<h3>SELECT に関するチュートリアル</h3>

https://docs.sqlalchemy.org/en/20/tutorial/data_select.html
____


<h3>SQLAlchemy のORMチュートリアルのURL</h3>

https://docs.sqlalchemy.org/en/20/tutorial/orm_data_manipulation.html
____

# よわよわ用語集

※冒頭の [学習に至った経緯](#学習に至った経緯) に戻る

<details><summary>① （ORM の）ラッパー（wrapper）</summary>

ラッパー（wrapper）とは、「大元となるORMの機能をさらに簡単に使えるように抽象化・拡張したもの」を指す言葉のようです。例えば、Django ORM における Django Model Methods（共通の操作をモデルに追加する方法）など（※下記の例参照）

```python
from django.db import models

class BaseModel(models.Model):
    class Meta:
        abstract = True

    def save_and_log(self):
        self.save()
        print(f"{self.__class__.__name__} saved.")
```
　※上のコード例はあくまでChat-GPT（しかも3.5(-_-;)）に聞いて返ってきたものなので必要に応じて差し替える必要があります。

</details>

<details><summary>② （ORMやDB設計の話の中にでてくる）パターン</summary>

「データベースとオブジェクト指向プログラミング言語の間でデータのやり取りを簡素化するための設計（の使いやすい型（または雛形））のようなもの」のような意味合いのようです。[資料⑨](#参考にさせていただいた資料)参照
</details>

<details><summary>③ テーブルスキーマ</summary>

データベース内のテーブルの構造を定義するもので、以下のような情報のことを指す。
（※データベーススキーマも同様と思われます）

①テーブル名: テーブルの名前。
②カラム名: 各カラムの名前。
③データ型: 各カラムに格納されるデータの型（上のモデルの定義だとintやstr）。
④制約:
　･NOT NULL: カラムにNULL値を許可しない。
　･PRIMARY KEY: テーブル内で一意に識別されるカラムまたはカラムの組み合わせ。
　･FOREIGN KEY: 他のテーブルのカラムとの関係を示す。
　･UNIQUE: カラムの値が一意であることを保証する。
　･CHECK: カラムの値が特定の条件を満たすことを保証する。
⑤デフォルト値: カラムに値が指定されなかった場合のデフォルト値。
⑥インデックス: データの検索を高速化するためのインデックス。
</details>

<details><summary>④ マイグレーション（Migration）</summary>

　元の意味は「移住・移転・移動」など。IT用語としては既存システムやソフトウェア、データなどを別の環境に移転したり、新しい環境に移行すること。ここでは、もっと厳密に言うとテーブルに関する情報をDBに反映させることと思われます。
</details>

<details><summary>⑤ エラーハンドリング</summary>

プログラムがエラーを起こした時に、すぐに実行を終了せずに、あらかじめ用意しておいた処理を行うこと。Pythonの場合、例外処理も含む。例えば、Flaskのエラーハンドリング参考記事については[資料⑪](#参考にさせていただいた資料)のようなものがありました。
</details>

<details><summary>⑥ アプリケーションコンテキスト、コンテキスト</summary>

<h3>アプリケーションコンテキスト</h3>

アプリケーションがどのように振る舞うかを決定するもの（言語、環境変数（？）等）。そのため、Flaskアプリケーションコンテキストとは、Flaskアプリケーションが動作する際に必要な一連の情報を提供するコンテキストを指す。Flaskアプリケーションの内部状態を管理し、アプリケーションの実行中にアクセスされるべきオブジェクトや設定を含む。具体的には、appインスタンスや設定オブジェクト（config）、ログ機能（logger）などが含まれる。

<h3>（プログラミングにおける）コンテキスト</h3>

プログラミングやソフトウェア開発において、コンテキストは特定の操作や機能が動作する際に必要な情報や状態を指す言葉。アプリケーションコンテキストの他に、リクエストコンテキスト（特定のリクエストに関連する情報。具体的にはリクエストのヘッダーやパラメータ、セッション情報など。リクエストが処理される間だけ存在し、リクエストが完了すると破棄される）がある。
</details>

<details><summary>⑦ エンドポイント</summary>

ハード面的な意味で使うのか、プログラミング的な意味で使うのかで意味が異なる。

<h4>（プログラミング的な意味での）エンドポイント</h4>

ソフトウェアが外部に公開している機能や、APIなどの所在を示す識別名、ネットワーク上でのアドレス、またはURL/URIなどを指す

<h4>（ハード面的な意味の）エンドポイント</h4>

コンピューター ネットワークに接続してそのネットワークとの間で情報を交換する物理的なデバイス。例としては、モバイル デバイス、デスクトップ コンピューター、仮想マシン、組み込みデバイス、サーバーなど
</details>

<details><summary>⑧エントリー</summary>

プログラムを実行する際に指定される開始アドレス
</details>


# 参考にさせていただいた資料

※冒頭の [学習に至った経緯](#学習に至った経緯) に戻る

①FlaskとMYSQLとSQLite3について @akeyi2018 さん Qiita
https://qiita.com/akeyi2018/items/42467fd87c3603d1681e

②Flask + SQLAlchemyプロジェクトを始める手順　@shirakiya(Yoshihiko Shiraki) さん　Qiita
https://qiita.com/shirakiya/items/0114d51e9c189658002e

③Flask-SQLAlchemyについて色々 @tkr709 さん Qiita
https://qiita.com/tkr709/items/a95a635035a7e312da61

④flask 　SQL Alchemy簡単なまとめ　@trash(あ あ)　さん Qiita
https://qiita.com/trash/items/69a77262ccf56ed7aade

⑤ORMの概念理解　@minimabot さん in 株式会社スピードリンクジャパン　Qiita
https://qiita.com/minimabot/items/0a3fcc41fd7140dfdc41

⑥SQLインジェクションとは？手口や被害リスク・5つの対策までわかりやすく解説　エムオーテックス株式会社（MOTEX Inc.）
https://www.lanscope.jp/blogs/cyber_attack_pfs_blog/20230913_33347/#:~:text=SQL%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E6%94%BB%E6%92%83%E3%81%A8%E3%81%AF,%E8%A1%8C%E3%81%86%E3%82%B5%E3%82%A4%E3%83%90%E3%83%BC%E6%94%BB%E6%92%83%E6%89%8B%E6%B3%95%E3%81%A7%E3%81%99%E3%80%82

⑦図解Django超入門ハンズオン　@myasuda220780 さん in 株式会社スカイウイル Qiita
https://qiita.com/myasuda220780/items/c0c80742e62939a6eede

⑧【PythonでWebアプリ作成】Flask入門 ！この動画１本でWebアプリが作れちゃう！ 〜 Pythonプログラミング初心者用 〜　Vtuberサプーさんyoutube動画
https://www.youtube.com/watch?v=EQIAzH0HvzQ

⑨さまざまなO/R Mapperパターンとその問題点 @CostlierRain464
(荻原 開発)さん Qiita
https://qiita.com/CostlierRain464/items/5be3c1860bb5137db3d1

⑩いまにゅのプログラミング（vol.032 データベース操作がめっちゃ楽になる！PythonのORM SQLAlchemyとは？ | 中学生でもわかるPython入門シリーズ）
https://www.youtube.com/watch?v=inEqP234Dpc

⑪PythonフレームワークFlask エラーハンドリング メモ　@KWS_0901 さん Qiita
https://qiita.com/KWS_0901/items/72d50dbd1a5caf7b73a1

⑫Flaskについてまとめ7 アプリケーションコンテキスト リクエストコンテキスト@TaichiEndoh(遠藤 太一) さん Qiita
https://qiita.com/TaichiEndoh/items/be649037de0733e2887e