[Groovy] MongoDBを簡単に扱えるイケてる言語Groovy -Groovyの応用編-

これはG* Advent Calendar 2015の8日目の記事です。

# はじめに
[昨日の記事](http://qiita.com/saba1024/items/b973340a8973d7618010)でGroovyからMongoDBを扱う基本を示しました。
今回は、さらにGroovyの機能を使ってよりGroovyな感じを掴んでみましょう！
なお、一番最後にコード全体を記載していますので、時間のない方はそちらを見て頂いてもOKです。

# ドキュメントの生成をよりシンプルに
さて、[昨日の記事](http://qiita.com/saba1024/items/b973340a8973d7618010)でドキュメントを生成する際に、以下のコードを書きました。

```groovy
// 1件登録する
Document doc = new Document("name", "MongoDB")
                .append("type", "database")
                .append("count", 1)
                .append("info", new Document("x", 203).append("y", 102))
collection.insertOne(doc)
```

その結果生成されるドキュメント(JSON)は以下のようになります。

```json
{
    "name" : "MongoDB", 
    "type" : "database", 
    "count" : 1,
    "info" : {
        "x" : 203,
        "y" : 102 
    }
}
```

いいねー！シンプルシンプル！
でも、よく見ると毎回フィールドごとに`append`書かなきゃイケないのが冗長ですね。
さらに入れ子の場合にはまたDocumentオブジェクト生成しないといけないし。。。
**はい！あります！対応策あります！**

と言ってもそんな難しい話ではなくて、実は[DocumentクラスはコンストラクタでMapを受け付けてくれます。](http://api.mongodb.org/java/3.1/org/bson/Document.html)
さらに！Groovyの場合はMapがとてもお手軽に生成できます。
では、上記のコードと等価なGroovyのMapバージョンを見てみましょう！

```groovy
def doc2 = [
    "name": "MongoDB",
    "type": "database",
    "count": 1,
    "info": ["x": 203, "y": 102]
]
collection.insertOne(new Document(doc2))
```

これです！これだけです！このコードは最初の`append`だらけのコードと全く同じです！
新しいMongoDBの公式JavaDriverと、Groovyの機能を組み合わせるとこんなにコードがシンプルになります！

# JSON（テキスト）を保存する
GroovyのGStringを使えば、簡単にJSONをテキストとして表現できます。
GStringを利用するのには別に特別は宣言などは必要ありません。また、3つのダブルクォーテーションで囲んであげれば、改行やエスケープが簡単に記述できます。
そして、Documentクラスはスタティックメソッドとしてテキスト形式のJSONをパースする`parse`メソッドがあるので、JSONテキストを渡してあげれまこれまたお手軽にMongoDBに保存できます。

```groovy
String jsonAsText = """
{
    "name": "Koji", 
    "age": 30, 
    "address":{
        "city": "Tokyo", 
        "country": "Japan"
    },
    "married": true,
    "favorites": ["Groovy", "Grails", "MongoDB"]
}
"""
collection.insertOne(Document.parse(jsonAsText))
```

通常のJSONをテキスト（String）として変数`jsonAsText`に格納して、`Document.parse()`に渡しているだけです。
これで、Mapを使った`Document`インスタンスの生成と同じことが実現できています。

# JSON（オブジェクト）を保存する
さて、コレは一体なんのことでしょう？
基本的に上記のテキストでJSONを表現する方法で、Stringの中で変数も扱えるので特に問題はないのですが、GroovyにはJsonBuilderという簡単にJSONオブジェクトを生成するクラスがあります。今度はこのJsonBuilderを使って同じことをしてみましょう。

```groovy
// JsonBuilder経由
// Groovy上で、複雑なJSONを生成する場合、JsonBuilderがとっても便利。
def builder = new groovy.json.JsonBuilder()
builder {
        name 'Koji'
        age 30
        address(
            city: 'Tokyo',
            country: 'Japan'
        )
        married true
        favorites 'Groovy', 'Grails', 'MongoDB'
}
collection.insertOne(Document.parse(builder.toPrettyString()))
```

実行結果は上記の「JSON（テキスト）を保存する」と全く同じです。
JsonBuilderのインスタンスを生成して値をその中に記述していくだけです。
ちょっと書き方が独特で、通常JSONは "KEY: VALUE"の形式ですが、上記のJsonBuilderの書き方だとキーとバリューの間のコロンが必要なくなります。（が、`address`の部分のようにそうでもない例外もあります。。。）
そして`Document.parse`にtoStringした値を渡すだけでOKです。
ちなみに、JsonBuilderには`builder.toPrettyString`というメソッドが用意されていて、buiderの値を綺麗に表示することが出来ます。

```json
// println builder.toPrettyString()を実行した結果
{
    "name": "Koji",
    "age": 30,
    "address": {
        "city": "Tokyo",
        "country": "Japan"
    },
    "married": true,
    "favorites": [
        "Groovy",
        "Grails",
        "MongoDB"
    ]
}
```

# JSON(テキスト）を編集して保存する
さて、最初の「JSON（テキスト）を保存する」の応用編です。
すでにString型変数`jsonAsText`にJSONが格納されています。
この状態で値を変更するにはどうすればいいでしょうか？
テキストなので当然正規表現で置換することも出来ます。が、そんな面倒臭いことしなく無いですよね？
Groovyならそれを簡単に実現できます！
GroovyにはテキストのJSONをMapライクなオブジェクトに変換してくれる`JsonSlurper`というクラスがあるのでそれを利用して実現できます。
ではコードを見てみましょう。

```groovy
// JsonSlurper経由
// JsonSlurperを使うと、テキストで受け取ったJSONでも、通常のJSONオブジェクトのようにその中身を編集することができる
def slurper = new groovy.json.JsonSlurper().parseText(jsonAsText)
slurper.name = "JsonSlurper of Groovy!"
slurper.hoge = "piyo" // 存在しないフィールの追加もなんのその！
slurper.married = false
collection.insertOne(new Document(slurper))
```
`JsonSlurper`のインスタンスを生成して、`parseText`メソッドにすでに存在しているテキスト形式のJSONを渡すだけです。
あとは通常のGroovyのMapのように扱えます！
そしてこの`JsonSlurper`インスタンスを`new Document`に渡してあげればちゃんとMongoDBに保存されます。

# おまけ1（特定ドキュメントのUPDATE）
`updateOne`や`updateMany`では、MongoDB上で対象ドキュメントの検索と更新が行われます。
では、該当ドキュメントの情報をGroovy上で加工して、さらにそのドキュメンをUPDATEするにはどうすればいいのでしょうか？
実はまだよく理解できていません。。。
一応、以下のコードでドキュメントを検索して、それぞれのtypeフィールドの内容を、それぞれの`_id`フィールドの値を付加してUPDATEすることが出来ます。恐らく別の良いアプローチがあるとは思いますが、とりあえずコレでも問題はないはずです。

```groovy
collection.find(eq("name", "MongoDB")).collect {
    ["_id":it._id, "newType": "NoSQL(ObjectId = ${it._id})"]
}.each {
    collection.updateOne(eq("_id", it._id), set("type", it.newType.toString()))
}
```

# おまけ2（JsonBuilderのComposite）
どういうことか？単純にJsonBuilderインスタンスの中で、別のJsonBuilderインスタンスを扱う、というだけです。
しかし、ちょっとクセがあって、JsonBuilderの中で利用される別のJsonBuilderは、`.content`でその値を取得しなければならない、という点です。
なんの事だかワケ分かんないですよね。ソースを見てみましょう！

```groovy
def builder2 = new groovy.json.JsonBuilder()
builder2 {
    abc 'def'
    fromOtherJsonBuilder builder.content
}
println builder2.toPrettyString()
collection.insertOne(Document.parse(builder2.toPrettyString()))
```

これで、1件新しいドキュメントがMongoDBに保存されます。
`println builder2.toPrettyString()`で表示されるJSONは以下のとおりです。

```json
{
    "abc": "def",
    "fromOtherJsonBuilder": {
        "name": "Koji",
        "age": 30,
        "address": {
            "city": "Tokyo",
            "country": "Japan"
        },
        "married": true,
        "favorites": [
            "Groovy",
            "Grails",
            "MongoDB"
        ]
    }
}
```

特に難しいことはないですよね。元々あったJsonBuilderオブジェクトを、別のJsonBuilderで包んだ形です。
`fromOtherJsonBuilder builder.content`を見て分かるとおり、単純に別のJsonBuilderインスタンスから`.content`でその中身を持ってくるだけでOKです。
元々あるJSONを別のJSONで包む今回のような例だと、テキストベースでも当然実現できますがコードがゴチャゴチャしてしまいがちです。
そういった際に、JsonBuilderが力を発揮します。


# まとめ
いかがだったでしょうか。
Groovyを使えば、MongoDB用の公式Java用ドライバを使って、さらに便利、シンプルにMongoDBを扱えることがわかったのではないでしょうか。
最後に、今回のコードの全体を記述して終わりにさせていただきます。
ありがとうございました。

明日は@h1romas4さんです！


```groovy
@Grab(group='org.mongodb', module='mongo-java-driver', version='3.1.1')

import com.mongodb.MongoClient
import com.mongodb.client.MongoDatabase
import com.mongodb.client.MongoCollection
import org.bson.Document
import static com.mongodb.client.model.Filters.*
import static com.mongodb.client.model.Sorts.*
import static com.mongodb.client.model.Updates.*
import com.mongodb.client.result.UpdateResult

// MongoDB本体に接続
MongoClient mongoClient = new MongoClient("localhost", 27017)
// 利用するデータベースに接続（データベース名はgroovy）
MongoDatabase db = mongoClient.getDatabase("groovy")
// 利用するコレクションに接続（コレクション名はtest）
MongoCollection<Document> collection = db.getCollection("test")

// DELETE ALL EXISTING DOCUMENTS
if (collection.count() > 0) {
    collection.deleteMany(new Document())
}

// 1件登録する
Document doc = new Document("name", "MongoDB")
                .append("type", "database")
                .append("count", 1)
                .append("info", new Document("x", 203).append("y", 102))
collection.insertOne(doc)

// 上の例と等価
def doc2 = [
    "name": "MongoDB",
    "type": "database",
    "count": 1,
    "info": ["x": 203, "y": 102]
]
collection.insertOne(new Document(doc2))

// テキストなJSONを保存する
String jsonAsText = """
{
    "name": "Koji", 
    "age": 30, 
    "address":{
        "city": "Tokyo", 
        "country": "Japan"
    },
    "married": true,
    "favorites": ["Groovy", "Grails", "MongoDB"]
}
"""
collection.insertOne(Document.parse(jsonAsText))

// JsonBuilder経由
// Groovy上で、複雑なJSONを生成する場合、JsonBuilderがとっても便利。
// ちょっと書き方が独特で、通常JSONは "KEY: VALUE"の形式だけど、間のコロンが必要なくなる。
def builder = new groovy.json.JsonBuilder()
builder {
        name 'Koji'
        age 30
        address(
            city: 'Tokyo',
            country: 'Japan',
        )
        married true
        favorites 'Groovy', 'Grails', 'MongoDB'
}
collection.insertOne(Document.parse(builder.toString()))


// JsonSlurper経由
// JsonSlurperを使うと、テキストで受け取ったJSONでも、通常のJSONオブジェクトのようにその中編みを編集することができる
def slurper = new groovy.json.JsonSlurper().parseText(jsonAsText)
slurper.name = "JsonSlurper of Groovy!"
slurper.hoge = "piyo" // 存在しないフィールの追加もなんのその！
slurper.married = false
collection.insertOne(new Document(slurper))
```