読者です 読者をやめる 読者になる 読者になる

Shoken Startup Blog

KitchHike Founder/CTO

MongoDBへ様々なデータ型を保存/取得してみる。バイナリ(BINARY)編

MongoDBへ様々なデータ型を保存/取得してみる。オブジェクトマッピング編 - オープンソースよしみ視点
の続きです。
MongoDBへバイナリ(BINARY)、Boolean、Date、Timestamp、などをinsertして保存/取得する方法を紹介します。

サンプルコードはRubyを使いますが、便利なORMであるMongoidはあえて使わずに、プリミティブなドライバであるmongo-ruby-driverで実装してよりMongo Shellに近い操作を確認していきたいと思います。

今回はMongoDBのバイナリデータ保存/取得に関して書きます。

バイナリ(BINARY)型

Mongo ShellでBINARY型を扱うことは稀だと思いますので省略して、Rubyでの操作から入ります。

BINARY型をRubyで扱う上での注意点
■保存・・・BSON::Binaryクラスのインスタンスとしてinsert
■取得・・・to_sメソッドで文字列として扱う

バイナリ(BINARY)型をinsertする

Gmailから添付ファイルを取得して、MongoDBに画像データをバイナリで保存します。
下のソースコード中に出てくる## 省略 Gmailから添付ファイルを取得する処理 ##はruby-gmailを使ってRubyからGmailの添付ファイルを取得 - オープンソースよしみ視点を参考にしてください。

require 'mongo'

db_name='test'              # MongoDBのデータベース名
coll_name='image_test_ruby' # MongoDBのコレクション名

## 接続処理
db = Mongo::Connection.new.db(db_name)
coll = db.collection(coll_name)

## 省略 Gmailから添付ファイルを取得する処理 ##

if (attachment.content_type.start_with?('image/'))
  filename = attachment.filename
  image = attachment.body.decoded
  coll.insert({:filename => filename,
               :image => BSON::Binary.new(image.to_blob,
                                           BSON::Binary::SUBTYPE_BYTES)
              })
end

db.connection.close

Binaryクラスのコンストラクタは2つ引数があります。
第一引数はバイナリデータ、第二引数はSUBTYPEと呼ばれるものです。
SUBTYPEはbinary.rbで以下のように定義されています。
私の環境ではここにありました。
/usr/local/lib/ruby/gems/1.9.1/gems/bson-1.6.1/lib/bson/types/binary.rb

require 'bson/byte_buffer'

  class Binary < ByteBuffer

    SUBTYPE_SIMPLE = 0x00
    SUBTYPE_BYTES  = 0x02
    SUBTYPE_UUID   = 0x03
    SUBTYPE_MD5    = 0x05
    SUBTYPE_USER_DEFINED = 0x80

    # One of the SUBTYPE_* constants. Default is SUBTYPE_BYTES.
    attr_accessor :subtype

    # Create a buffer for storing binary data in MongoDB.
    #
    # @param [Array, String] data to story as BSON binary. If a string is given, the on
    #   Ruby 1.9 it will be forced to the binary encoding.
    # @param [Fixnum] one of four values specifying a BSON binary subtype. Possible values are
    #   SUBTYPE_BYTES, SUBTYPE_UUID, SUBTYPE_MD5, and SUBTYPE_USER_DEFINED.
    #
    # @see http://www.mongodb.org/display/DOCS/BSON#BSON-noteondatabinary BSON binary subtypes.

    def initialize(data=[], subtype=SUBTYPE_SIMPLE)
      super(data)
      @subtype = subtype
    end

    def inspect
      "<BSON::Binary:#{object_id}>"
    end

  end
end

BYTESやUUIDやMD5など選択できるようですが、どのような違いがあるかはわからず。。

Mongo Shellから見たときに、BinDataの一つ目の値となるようです。

> db.image_test.findOne();
{
  "_id" : ObjectId("4f7827683e91d82eca000001"),
  "filename" : "画像データ",
  "image" : BinData(2,"(画像のバイナリデータ)")
}

バイナリ(BINARY)型を画像データとして表示する

Rubyからの操作です。
ByteBufferオブジェクトが返ってくるので、to_sメソッドを使ってバイナリの文字列?にします。
参考

Class: BSON::ByteBuffer

— Documentation for bson (1.6.1)

これまた、プリミティブな動きを理解するためにCGIで動きを確認します。
Mongo DBからバイナリデータを取得して、画像を表示するCGIのサンプルです。

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
##
## Mongo DBからバイナリデータを取得して、画像を表示するCGI
##
require 'mongo'

db_name='test'         # MongoDBのデータベース名
coll_name='image_test' # MongoDBのコレクション名

## 接続処理
db = Mongo::Connection.new.db(db_name)
coll = db.collection(coll_name)

## データを取得
doc = db[coll_name].find_one()
image = doc['image'].to_s  # doc['image'] => ByteBufferオブジェクトが返ってくる

db.connection.close

puts "Content-type: image/png;"
puts "Content-length: #{image.size.to_s}"
puts ""               # ここに改行が必要
print image           # 画像を表示 to_blobはいらない

おまけ

Hello Worldを表示するRubyのCGI

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-

puts "Content-type: text/html; charset=UTF-8"
puts ""
puts 'Hello World'