MongoEngineでMongoDBを触ってみる基礎編


この記事は [MongoDB Advent Calendar 2013](http://qiita.com/advent-calendar/2013/mongodb) の三日目です。

# MongoEngineってなんですか？

[MongoEngine](http://mongoengine.org/)は **ODM == Object Document Mapper** というやつでMongoDB用のドキュメントをオブジェクト風に操作出来るようにするライブラリ。ドキュメントスキーマ定義、取得、保存を抽象化してくれます。

利用方法は[Django](https://www.djangoproject.com)のORMによく似ており、DjangoユーザであればMongoDB独特のクエリを学習しなくてもMongoDBを使用することが出来るでしょう。たぶん。

ただし、突っ込んだことをしようとするとMongoDBの知識が必要になります。


# MongoEngineは何が出来て、何が出来ないのか

## できること

* ドキュメントスキーマの定義、制約の定義
* ドキュメントの保存
* ドキュメントの取得
* カスタムクエリの発行

## できないこと or めんどうくさいこと

* バックアップ＆リストアなどシステムレベルのコマンド実行
* MongoDB固有のコマンド発行（出来なくも無いけど、とても面倒）

# さわってみよう

## インストール

pypiに登録されているのでpipでインストールします。

```bash
$ pip install mongoengine
```

## 接続先の設定

MongoDBへの接続は`mongoengine.connect`を使用します。 *mongotest* というデータベースに接続するには次のようにします。

```python
from mongoengine import connect

connect('mongotest')
```

ホストやポートを指定する場合は引数を足してください。

```python
connect('mongotest', host='192.168.0.10', port=999)
```


## ドキュメントクラスを作る

`mongoengine.document.Document`クラスを継承したサブクラスを定義します。各フィールドには`mongoengine.fields.*`を割り当てます。フィールドクラスはIntFieldやStringFieldなど、pythonのオブジェクト名に似た名前で定義されています。

```python:documents.py
from mongoengine.document import Document
from mongoengine import fields

class Athlete(Document):
    name = fields.StringField()
    age = fields.IntField()

    def __unicode__(self):
        return self.name
```

MongoDBはスキーマレスなのが良いところですが、個人的にはドキュメントクラス == スキーマを作って操作するほうが楽なように思います。余談ですが、1つのコレクションで複数のドキュメントクラスを操作することが出来るので、スキーマレスであるメリットはスポイルされません。


## ドキュメントの追加

Athleteクラスをインスタンス化して`save()`メソッドを呼び出します。

```python
from documents import Athlete

# taro yamadaとhanako yamadaを追加する
athlete = Athlete(name=u"taro yamada", age=30)
athlete.save()
athlete = Athlete(name=u"hanako yamada", age=28)
athlete.save()
```

mongoコマンドで見てみると…

```
$ mongo mongotest
MongoDB shell version: 2.4.6
connecting to: mongotest

> show dbs
local	0.078125GB
mongotest	0.203125GB
```

データベースが自動的に作成されています。コレクションはというと、

```
> show collections
athlete
system.indexes

> db.athlete.find()
{ "_id" : ObjectId("529db366bdbf568299123013"), "name" : "taro yamada", "age" : 30 }
{ "_id" : ObjectId("529db458bdbf568299123014"), "name" : "hanako yamada", "age" : 28 }
```

というように、athleteコレクションにドキュメントが保存されています。

## ドキュメントの取得

ドキュメント取得もAthleteクラスから操作を行います。`objects`プロパティ経由で`all()`メソッドにより全件取得してみましょう（Djangoユーザにはおなじみですね）。

```python
>>> Athlete.objects.all()
[<Athlete: taro yamada>, <Athlete: hanako yamada>]
```

取得条件を追加するには`filter()`メソッドを使います。

```python
>>> Athlete.objects.filter(age=28)
[<Athlete: hanako yamada>]
```

ドキュメントを一つだけ取得する場合には`get()`メソッドが使えます。

```python
>>> Athlete.objects.get(age=28)
<Athlete: hanako yamada>
```

`get()`メソッド実行時にドキュメントが存在しないと例外が投げられます。

```python
>>> Athlete.objects.get(name=u"inoki")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/key/.virtualenvs/mongoenginetest/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 186, in get
    raise queryset._document.DoesNotExist(msg)
documents.DoesNotExist: Athlete matching query does not exist.
```

ドキュメント数を数えるために`count()`メソッドも使えます。

```python
>>> Athlete.objects.all().count()
2
```

## ドキュメントの更新

ドキュメントの更新は、インスタンスの`save()`メソッドを実行します。ドキュメントがなければ追加されるし、あれば更新されるという具合ですね。

```python
>>> athlete = Athlete.objects.get(name=u"taro yamada")
>>> athlete.age = 50
>>> athlete.save()
<Athlete: taro yamada>
>>> athlete.age
50
```


## ドキュメントの削除

Athleteクラスのインスタンスに対して`delete()`メソッドを発行します。

```python
>>> hanako = Athlete.objects.get(age=28)
>>> hanako.delete()
>>> Athlete.objects.all()
[<Athlete: taro yamada>]
```

hanakoが削除されました:)

うっかり`all()`メソッドに続けて`delete()`メソッドをチェインすると、全て消えてしまいます（`filter()`を使用した際も同様）。

```python
>>> Athlete.objects.all().delete()
>>> Athlete.objects.all()
[]
```

taroも削除されています:)