Django/Related objects(add, create, remove, clear, set)まとめ

## 書くこと
add, create, remove, clear, setってどう使うんだっけというのを何度か調べてしまっていました。
その度英語を読むのは面倒なので、日本語でまとめておきます。
[Django Documentation / Related objects reference](https://docs.djangoproject.com/en/3.0/ref/models/relations/)


### 用意する Model,データ
```python

from django.db import models

class Reporter(models.Model):
    pass


class Article(models.Model):
    title = models.CharField(max_length=255, null=True)
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
```

```python

r1 = Reporter.objects.create(name='reporter1')
r2 = Reporter.objects.create(name='reporter2')

a1 = Article.objects.create(title='article1', reporter=r1)
a2 = Article.objects.create(title='article2', reporter=r1)
a3 = Article.objects.create(title='article3', reporter=r2)
```

## 各メソッドについて
### add(*objs, bulk=True, through_defaluts=None)
addメソッドを使うことで親子関係における子テーブルの外部キーを更新することが出来ます。

__使用例__

```python

>>> r1.article_set.all()
<QuerySet [<Article: Article object (1)>, <Article: Article object (2)>]>
>>> r2.article_set.all()
<QuerySet [<Article: Article object (3)>]>
>>> r2.article_set.add(a1,a2)
>>> r1.article_set.all()
<QuerySet []>
>>> r2.article_set.all()
<QuerySet [<Article: Article object (1)>, <Article: Article object (2)>, <Article: Article object (3)>]>
```


__bulk=False__

`bulk=False`を使うことでまだsaveされていないobjectを引数として渡すことができます。
ただし、`bulk=False`となっている通り、addするobjectの数だけクエリが発行されます。

```python

>>> r1.article_set.all()
<QuerySet [<Article: Article object (1)>, <Article: Article object (2)>]>
>>> new_a1 = Article()
>>> new_a2 = Article()
>>> r1.article_set.add(new_a1, new_a2, bulk=False)
>>> r1.article_set.all()
<QuerySet [<Article: Article object (1)>, <Article: Article object (2)>, <Article: Article object (4)>, <Article: Article object (5)>]
```

```
INSERT INTO `article` (`reporter_id`) VALUES (1)  # new_a1
INSERT INTO `article` (`reporter_id`) VALUES (1)  # new_a2
```

### create(through_defaluts=None, **kwargs)
createメソッドを使うことで、少ない記述量で新しくレコードを作成することが出来ます。

```python

>>> r1.article_set.create()
>>> r1.article_set.all()
<QuerySet [<Article: Article object (1)>, <Article: Article object (2)>, <Article: Article object (3)>]>
```

これは以下と同様の結果になります。

```python

>>> new_a = Article(reporter=r1)
>>> new_a.save()
>>> r1.article_set.all()
```

### remove(*objs, bulk=True)
`remove（）`はForeignKeyをNullに更新してくれます。
`ForeignKey(null=True)`の場合のみ使うことが出来ます。
上で用意したModelで`ForeignKey(null=True)`としたとすると、以下のように使うことが出来ます。

```python

>>> r1.article_set.remove(a1, a2)
```

__bulk=False__
add()と同様に`bulk=False`を渡すことで、レコードを一件ずつ更新することも出来ます。

```python

>>> r1.article_set.remove(a1, a2, bulk=False)
>>> r1.article_set.all()
<QuerySet []>
```
`bulk=True`の場合、`QuerySet.update()`が呼ばれますが、`bulk=False`の場合、`save()`が複数回呼ばれます。


### clear(bulk=True)
`clear()`は全ての子のForeignKeyをNullに更新してくれます。
`clear()`同様に`ForeignKey(null=True)`の場合のみ使うことが出来ます。

```python

>>> r1.article_set.clear()
>>> r1.article_set.all()
<QuerySet []>
```


__bulk=False__

```python

>>> r1.article_set.clear(bulk=False)
>>> r1.article_set.all()
<QuerySet []>
```

これもremove()同様、`save()`が複数回呼ばれます。



### set(objs, bulk=True, clear=False, through_defaults=None)
[setのGitHubリンク(OneToMany)](https://github.com/django/django/blob/master/django/db/models/fields/related_descriptors.py#L728-L751)

`set()`は中で`add()`, `remove()`, `clear()`が動いています。
`bulk=True/False`を引数として渡すと、中で動いているこれらのメソッドの引数に`bulk`が入ります。


__clear=False__

```python
>>> r1.article_set.set([a1, a3], bulk=False)
```

は以下と同じです。

```python

>>> r1.article_set.remove(a2, bulk=False)
>>> r1.article_set.add(a1, a3, bulk=False)
```

__clear=True__

```python

>>> r1.article_set.set([a1, a3], clear=True)
```

は以下と同じです。

```python
>>> r1.article_set.clear()
>>> r1.article_set.add(a1, a3)
```

## まとめ
以上DjangoのRelated objectsについて改めてじっくりドキュメントを読んでみました。
特に引数で渡すオプションについての理解は曖昧だったので、同じような誰かの役に立つことを願っています。

## 参照

- [Related objects reference/Django documentation](https://docs.djangoproject.com/en/3.0/ref/models/relations/#related-objects-reference)