まとめ(結論だけ知りたい人用)
下記リンク先のコードを参考にしてください。
modelにsetterとgetterを設定したコード
ただしprotectedなattributeにはアクセスしないという
共通認識が必要なコードとなっています。
コード内容を理解したい方は記事本文をどうぞ
概要
SQLAlchemyではmodelオブジェクトのattributeに直接値を代入することが可能です。
しかしmodelオブジェクトの内容変更がDBのレコード更新という動作に繋がりやすく、
単純なattributeの変更だと思っていた処理が、実はDB上のレコードの更新処理だった
というような動作内容への誤認識を招きやすくしています。
今回はattributeのsetterとgetterを実装することで、
attributeの読み出しは可能だが、代入はできないという性質を持つ
modelクラスの実装例を見ていきます。
定義するモデル
今回定義するモデルの内容は以下の通り
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
id | varchar(26) | NO | PRI | NULL | |
name | varchar(255) | NO | NULL | ||
varchar(255) | YES | NULL |
modelクラスのコード
以下の通り
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import synonym
from sqlalchemy.schema import Column
from sqlalchemy.types import VARCHAR
Base = declarative_base()
class User(Base):
__tablename__ = "user"
_user_id = Column(VARCHAR(26), name="id", primary_key=True)
_name = Column(VARCHAR(255), name="name", nullable=False)
_email = Column(VARCHAR(255), name="email")
def __init__(self, user_id=None, name=None, email=None) -> None:
self._user_id = user_id
self._name = name
self._email = email
@property
def user_id(self):
return self._user_id
@property
def name(self):
return self._name
@property
def email(self):
return self._email
def update_name(self, name): # 抽象的なデータ操作メソッドを定義可能
self._name = name
# setterを書く例として定義。
# 明示的に定義しなくてもAttributeErrorを発生してくれる。
@name.setter
def name(self, value):
raise AttributeError
name = synonym('_name', descriptor=name)
user_id = synonym('_user_id', descriptor=user_id)
email = synonym('_email', descriptor=email)
コード解説
-
Column
全てprotectedな変数としてアクセスを禁じることを明示します。
仕様的には参照可能ですが、言語上の共通認識としての普通はアクセスしないという思想に依存させます。ちなみにname manglingを利用すればいいと考えると思うのですが、
後述のsynonymが利用できなくなるため利用できません。 -
@property
getterを定義しています。 -
@[var].setter
setterを定義しています。 -
synonym
カラムのエイリアスを定義します。
getter/setterを使うだけであれば、synonymを使わないでも問題ありません。
しかし、以下のようにfilterを使う場合、attributeの参照で問題が発生します。
session.query(User).filter(User.name == "John").all()
この時に生成されるクエリは以下の通り
SELECT user.id AS user_id, user.name AS user_name, user.email AS user_email
FROM user
WHERE false = 1
明らかに意図していないクエリが発行されています。
次にsynonymを追加した場合、
SELECT user.id AS user_id, user.name AS user_name, user.email AS user_email
FROM user
WHERE user.name = 'John'
意図したとおりのクエリになっていることが分かります。
ちなみにsynonymを使用しなかった場合、以下の方法で意図したクエリを発行できます。
session.query(User).filter(User._name == "John").all()
しかし、protectedなattributeを指定するのはあまり良いコードとは言えないです。