MongoDBにTwitter Timelineを保存してごにょごにょする(その1:MongoDBを扱うためmongo gemをインストール)

きっかけ

Qiitaでこの記事を読みました。

  • twitterからのデータを保存する

tweetstreamがその機能を持っている…また、stasut.to_hでハッシュ化させることができるので、mongodbにはその状態をそのまま保存します。

  • mongodbに保存する

mongodbには以下のように書くことで接続できます。

  • twitterからのデータをmongodbに保存する

以上の二つを組み合わすと、以下のようになります。
rubyでtwitterのツイートをmongodbに流し込む - Qiita

疑問が生じました。
…保存した後、どう扱うの?

MongoDBって?

MongoDBはJSONのデータをそのままデータベースに投げ込むことができます。
 :
MongoDBはカラム定義をしないと言っても、雑多にデータを保持しているわけではなく、ちゃんと取り出せる状態で持っています。JSONのような形で、データの検索ができます。
MongoDBってなんだ? | 三度の飯とエレクトロン

便利ですよね。JSONでぶち込めば良いんですから。

んでKVSとは違って、クエリ書けばちゃんと適切な情報が返ってくる。

Mongoクエリ言語には以下の特徴があります。

  • コレクションの各メソッドを用いてCRUD操作をします
  • メソッドの引数にはJSON形式のデータを渡します
  • 変数が使えます
  • 制御構造が使えます
  • カーソルが使えます
  • ドキュメントの要素に簡単にアクセスができます

第3回 MongoDBのクエリを使いこなそう:MongoDBでゆるふわDB体験|gihyo.jp … 技術評論社

おまけ:SQLとの対応 SQL脳に優しいMongoDBクエリー入門 - taka512's blog

JSONでMongoDBにデータ入れてるひと発見^^;;

と、ここまで書いたときに、先人が居られましたw

TwitterのStreaming APIを使用するにあたって、取得したツイートすべてをmongoDBに保存するにはtwitter/json-streamを使うのが簡単そうだったので…
rubyでtwitter/json-streamを使用し、Streaming APIからmongoDBに保存 - Qiita

twitter/json-streamというかtwitter-stream gemって初めて見ました
File: README — Documentation for twitter-stream (0.1.16)
けど、Streaming API取得専用みたいですね。

方針変更

先人が居たので、Twitter gemを使ってStreaming APIをたたくだけでは面白くない。TLだけじゃなくListsも取得するように方針変更しましょうw もちろん取得後のログをごにょごにょするところもやります。

まずはheroku app作成&MongoLab設定作成

もちろんローカルに環境を作っても良いのですが、最近なんでもherokuで作っているし、heroku add-onにMongoDBがいくつかあるのでそれを使うことにします。今回はMongoLabを。
なお、当然ですがkeyやsecretはニセモノです。

$ heroku apps:create riocampos-timeline
Creating riocampos-timeline... done, stack is cedar
http://riocampos-timeline.herokuapp.com/ | git@heroku.com:riocampos-timeline.git
Git remote heroku added
$ heroku addons:add mongolab
Adding mongolab on riocampos-timeline... done, v3 (free)
Welcome to MongoLab.  Your new subscription is being created and will be available shortly.  Please consult the MongoLab Add-on Admin UI to check on its progress.
Use `heroku addons:docs mongolab` to view documentation.

当然ながらherokuのlog viewerが必要なのでheroku add-onにPapertrailを入れます。
あとでPapertrailの設定画面にてタイムゾーンを設定します。

$ heroku addons:add papertrail
Adding papertrail on riocampos-timeline... done, v4 (free)
Welcome to Papertrail. Questions and ideas are welcome (support@papertrailapp.com). Happy logging!
Use `heroku addons:docs papertrail` to view documentation.

そしてherokuのタイムゾーンも設定しておきます。

$ heroku config:add TZ=Asia/Tokyo
Setting config vars and restarting riocampos-timeline... done, v5
TZ: Asia/Tokyo

この時点でのheroku config。

$ heroku config
=== riocampos-timeline Config Vars
MONGOLAB_URI:         mongodb://heroku_app2x19xxxx:zkgac9ierai4fjrd960ddob37t@dbh70.mongolab.com:27707/heroku_app2x19xxxx
PAPERTRAIL_API_TOKEN: xN10sz5g7sGxUjhbivzP
TZ:                   Asia/Tokyo

必要なgemをインストール

まずmongo gemが必要ですね。
File: README — Documentation for mongo (2.6.2)
もう一つ、bson_ext gemも必要なようです。

$ gem install mongo
Fetching: bson-1.9.2.gem (100%)
Fetching: mongo-1.9.2.gem (100%)
Successfully installed bson-1.9.2
Successfully installed mongo-1.9.2
2 gems installed
$ gem install bson_ext
Fetching: bson_ext-1.9.2.gem (100%)
Building native extensions.  This could take a while...
Successfully installed bson_ext-1.9.2
1 gem installed

接続クライアント作成

一つ目のチュートリアル

を参考にしながらやっていきます。
以下の作業はpryで。まず
Mongo::MongoClient.from_uri(mongodb_uri)
で接続クライアントを作成します。mongodb_uriはheroku configで確認したMONGOLAB_URIです。

pry(main)> require 'mongo'
=> true
pry(main)> mongodb_uri = 'mongodb://heroku_app2x19xxxx:zkgac9ierai4fjrd960ddob37t@dbh70.mongolab.com:27707/heroku_app2x19xxxx'
=> "mongodb://heroku_app2x19xxxx:zkgac9ierai4fjrd960ddob37t@dbh70.mongolab.com:27707/heroku_app2x19xxxx"
pry(main)> client = Mongo::MongoClient.from_uri(mongodb_uri)
=> #<Mongo::MongoClient:0x007f82d42c9288
 @acceptable_latency=15,
 @auths=
  [{:db_name=>"heroku_app2x19xxxx",
    :username=>"heroku_app2x19xxxx",
    :password=>"zkgac9ierai4fjrd960ddob37t"}],
 @connect_timeout=30,
 @default_db="heroku_app2x19xxxx",
 @host="dbh70.mongolab.com",
 @id_lock=#<Mutex:0x007f82d42c91e8>,
 @logger=nil,
 @max_bson_size=16777216,
 @max_message_size=48000000,
 @mongos=false,
 @op_timeout=nil,
 @pool_size=1,
 @pool_timeout=5.0,
 @port=27707,
 @primary=["dbh70.mongolab.com", 27707],
 @primary_pool=
  #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_time= …
 @read=:primary,
 @read_primary=true,
 @slave_ok=nil,
 @socket_class=Mongo::TCPSocket,
 @socket_opts={},
 @ssl=nil,
 @tag_sets=[],
 @unix=false,
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>
pry(main)> client.class
=> Mongo::MongoClient

クラスはMongo::MongoClient。

データベースインスタンス作成

接続クライアントインスタンスclientから
client.db(db_name)
でデータベースを作成。

pry(main)> db_name = mongodb_uri[%r{/([^/\?]+)(\?|$)}, 1]
=> "heroku_app2x19xxxx"
pry(main)> db = client.db(db_name)
=> #<Mongo::DB:0x007f82d2a4fc98
 @acceptable_latency=15,
 @cache_time=300,
 @connection=
  #<Mongo::MongoClient:0x007f82d42c9288
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app2x19xxxx",
      :username=>"heroku_app2x19xxxx",
      :password=>"zkgac9ierai4fjrd960ddob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app2x19xxxx",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007f82d42c91e8>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_time…
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @name="heroku_app2x19xxxx",
 @pk_factory=nil,
 @read=:primary,
 @strict=nil,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>
pry(main)> db.class
=> Mongo::DB

データベースインスタンスdbのクラスはMongo::DB。

このあとは、いま作成したデータベースインスタンスから、ユーザが扱うデータである「ドキュメント」を入れる「コレクション」を作成する、のですかね。

データベースに関する注意点

なお、データベース名db_nameはMongoLabのユーザ名(この例だとheroku_app2x19xxxx)にしておかないとエラーになります。
例えばデータベース名をdb_testにしてみます。

pry(main)> db_test = client.db('db_test')
=> #<Mongo::DB:0x007faaaa090658
 @acceptable_latency=15,
 @cache_time=300,
 @connection=
  #<Mongo::MongoClient:0x007faaaa4aec58
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app23195407",
      :username=>"heroku_app23195407",
      :password=>"ukguc9iarai4fjbd9b0d2ob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app23195407",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007faaaa4aeb90>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fd5552554f8 @host=dbh70.mongolab.com @port=27707 @ping_time…
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @name="db_test",
 @pk_factory=nil,
 @read=:primary,
 @strict=nil,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>

では(後ほどさらに扱いますが)コレクションを作成します。
Mongo::DB#collectionまたはMongo::DB#[]で生成。

pry(main)> test = db_test.collection("test")
=> #<Mongo::Collection:0x007faaaaa0db18
 @acceptable_latency=15,
 @cache={},
 @cache_time=300,
 @capped=nil,
 @connection=
  #<Mongo::MongoClient:0x007faaaa4aec58
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app23195407",
      :username=>"heroku_app23195407",
      :password=>"ukguc9iarai4fjbd9b0d2ob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app23195407",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007faaaa4aeb90>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fd5552554f8 @host=dbh70.mongolab.com @port=27707 @ping_time…
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @db=
  #<Mongo::DB:0x007faaaa090658
   @acceptable_latency=15,
   @cache_time=300,
   @connection=
    #<Mongo::MongoClient:0x007faaaa4aec58
     @acceptable_latency=15,
     @auths=
      [{:db_name=>"heroku_app23195407",
        :username=>"heroku_app23195407",
        :password=>"ukguc9iarai4fjbd9b0d2ob37t"}],
     @connect_timeout=30,
     @default_db="heroku_app23195407",
     @host="dbh70.mongolab.com",
     @id_lock=#<Mutex:0x007faaaa4aeb90>,
     @logger=nil,
     @max_bson_size=16777216,
     @max_message_size=48000000,
     @mongos=false,
     @op_timeout=nil,
     @pool_size=1,
     @pool_timeout=5.0,
     @port=27707,
     @primary=["dbh70.mongolab.com", 27707],
     @primary_pool=
      #<Mongo::Pool:0x3fd5552554f8 @host=dbh70.mongolab.com @port=27707 @ping_ti…
     @read=:primary,
     @read_primary=true,
     @slave_ok=nil,
     @socket_class=Mongo::TCPSocket,
     @socket_opts={},
     @ssl=nil,
     @tag_sets=[],
     @unix=false,
     @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
   @name="db_test",
   @pk_factory=nil,
   @read=:primary,
   @strict=nil,
   @tag_sets=[],
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @hint=nil,
 @logger=nil,
 @name="test",
 @pk_factory=BSON::ObjectId,
 @read=:primary,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>

このコレクションにドキュメントとしてハッシュを登録してみます。

pry(main)> test_data = {a: 'a'}
=> {:a=>"a"}
pry(main)> test.insert(test_data)
Mongo::OperationFailure: 16544: not authorized for insert on db_test.test
from /Users/riocampos/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/mongo-1.9.2/lib/mongo/networking.rb:103:in `send_message_with_gle'

怒られましたw
データベース名をMongoLabのユーザにしておかないとMongo::OperationFailureが返されます。
つまり、MongoLabから自由に扱えるよう許可されているのは、MongoDBの構造
MongoDB > database > collection > document
のcollection以下ということになりますね。

チュートリアルその2のデータ登録

先走りましたが次のチュートリアルに。

まずJSONが扱えるようにjsonをrequire。(最終的にはJSON使わなかったですw)

pry(main)> require "json"
=> true
pry(main)> db.collection_names.each { |name| puts name }
system.indexes
system.users
system.profile
=> ["system.indexes", "system.users", "system.profile"]

何も登録していない状態でも3つのコレクションが登録されています。
(後日、別のアカウントでMongoLabを新規登録したときには、system.indexesとsystem.usersの二つだけで、system.profileがありませんでした。なぜだろ…。)
曲のデータを3つ登録します。データの元はこのハッシュ要素の配列。

pry(main)> seed_data = [{'decade' => '1970s', 'artist' => 'Debby Boone', 'song' => 'You Light Up My Life', 'weeksAtOne' => 10 }, {'decade' => '1980s', 'artist' => 'Olivia Newton-John', 'song' => 'Physical', 'weeksAtOne' => 10 }, {'decade' => '1990s', 'artist' => 'Mariah Carey', 'song' => 'One Sweet Day', 'weeksAtOne' => 16  }]
=> [{"decade"=>"1970s",
  "artist"=>"Debby Boone",
  "song"=>"You Light Up My Life",
  "weeksAtOne"=>10},
 {"decade"=>"1980s",
  "artist"=>"Olivia Newton-John",
  "song"=>"Physical",
  "weeksAtOne"=>10},
 {"decade"=>"1990s",
  "artist"=>"Mariah Carey",
  "song"=>"One Sweet Day",
  "weeksAtOne"=>16}]

データを入れる「コレクション」を用意します。
コレクションはMongo::DB#collectionまたはMongo::DB#[]で生成。

pry(main)> songs = db.collection("songs")
=> #<Mongo::Collection:0x007f82d41beb68
 @acceptable_latency=15,
 @cache={},
 @cache_time=300,
 @capped=nil,
 @connection=
  #<Mongo::MongoClient:0x007f82d42c9288
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app2x19xxxx",
      :username=>"heroku_app2x19xxxx",
      :password=>"zkgac9ierai4fjrd960ddob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app2x19xxxx",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007f82d42c91e8>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_time…
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @db=
  #<Mongo::DB:0x007f82d2a4fc98
   @acceptable_latency=15,
   @cache_time=300,
   @connection=
    #<Mongo::MongoClient:0x007f82d42c9288
     @acceptable_latency=15,
     @auths=
      [{:db_name=>"heroku_app2x19xxxx",
        :username=>"heroku_app2x19xxxx",
        :password=>"zkgac9ierai4fjrd960ddob37t"}],
     @connect_timeout=30,
     @default_db="heroku_app2x19xxxx",
     @host="dbh70.mongolab.com",
     @id_lock=#<Mutex:0x007f82d42c91e8>,
     @logger=nil,
     @max_bson_size=16777216,
     @max_message_size=48000000,
     @mongos=false,
     @op_timeout=nil,
     @pool_size=1,
     @pool_timeout=5.0,
     @port=27707,
     @primary=["dbh70.mongolab.com", 27707],
     @primary_pool=
      #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_ti…
     @read=:primary,
     @read_primary=true,
     @slave_ok=nil,
     @socket_class=Mongo::TCPSocket,
     @socket_opts={},
     @ssl=nil,
     @tag_sets=[],
     @unix=false,
     @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
   @name="heroku_app2x19xxxx",
   @pk_factory=nil,
   @read=:primary,
   @strict=nil,
   @tag_sets=[],
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @hint=nil,
 @logger=nil,
 @name="songs",
 @pk_factory=BSON::ObjectId,
 @read=:primary,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>

songsコレクションへ入れてみます。

pry(main)> songs.insert(seed_data)
=> [BSON::ObjectId('532ae2c828f29012cc000001'),
 BSON::ObjectId('532ae2c828f29012cc000002'),
 BSON::ObjectId('532ae2c828f29012cc000003')]

BSONというもので入ったようです。

検索など

ではsongsコレクションを対象として、検索を行います。
まずはドキュメント指定&データ書き替え。
'song'キーが'One Sweet Day'であるドキュメントを指定し、そのデータの'artist'キーを'Mariah Carey ft. Boyz II Men'にセットします。

pry(main)> query = { 'song' => 'One Sweet Day' }
=> {"song"=>"One Sweet Day"}
pry(main)> songs.update(query, { '$set' => { 'artist' => 'Mariah Carey ft. Boyz II Men' } })
=> {"updatedExisting"=>true,
 "n"=>1,
 "lastOp"=>seconds: 1395319668, increment: 1,
 "connectionId"=>13070,
 "err"=>nil,
 "ok"=>1.0}

Mongo::Collection#findメソッドで検索します。さらに検索結果を'decade'キーでソートします。
返値はMongo::Cursor。

pry(main)> cursor = songs.find({ 'weeksAtOne' => { '$gte' => 10 } }).sort('decade', 1)
=> <Mongo::Cursor:0x3fc16a0f3c44 namespace='heroku_app2x19xxxx.songs' @selector={"weeksAtOne"=>{"$gte"=>10}} @cursor_id=>

ここで
{ 'weeksAtOne' => { '$gte' => 10 } }
というのは式で書くと
weeksAtOne >= 10
の意味です。つまり"greater than or equal to"。
このあたりの演算子

で確認できます。

脱線しました。
検索結果に対して、そのカーソル以降のデータでイテレートします。

pry(main)> cursor.each{ |doc| puts "In the #{ doc['decade'] }," +
pry(main)* " #{ doc['song'] } by #{ doc['artist'] }" +                     
pry(main)* " topped the charts for #{ doc['weeksAtOne'] }" +               
pry(main)* " straight weeks." }                        
In the 1970s, You Light Up My Life by Debby Boone topped the charts for 10 straight weeks.
In the 1980s, Physical by Olivia Newton-John topped the charts for 10 straight weeks.
In the 1990s, One Sweet Day by Mariah Carey ft. Boyz II Men topped the charts for 16 straight weeks.
=> nil

これをlambdaを使って少し書き替えます。もちろんprocでも同じでしょうけど、個人的にlambdaが好きなのですw

pry(main)> message = lambda do |decade, song, artist, weeks_at_one|
pry(main)*   "In the #{decade}, #{song} by #{artist} topped the charts for #{weeks_at_one} straight weeks."  
pry(main)* end  
=> #<Proc:0x007f82d42a19e0@(pry):28 (lambda)>

では先ほどと同じように検索結果をイテレートして出力します。

pry(main)> cursor.each{ |doc| puts message.call(doc['decade'], doc['song'], doc['artist'], doc['weeksAtOne']) }
=> nil

先に処理してカーソルがコレクション末尾にあるためか、nilが返ってきてしまいました。(カーソル分かってなかったw)
ということでカーソルを戻してみます。とはいえよく分かんないので、先ほどと同じfindメソッドを使って検索カーソルを設定します。

pry(main)> cursor = songs.find({ 'weeksAtOne' => { '$gte' => 10 } }).sort('decade', 1)
=> <Mongo::Cursor:0x3fc16a1067b8 namespace='heroku_app2x19xxxx.songs' @selector={"weeksAtOne"=>{"$gte"=>10}} @cursor_id=>

やり直してみます。

pry(main)> cursor.each{ |doc| puts message.call(doc['decade'], doc['song'], doc['artist'], doc['weeksAtOne']) }
In the 1970s, You Light Up My Life by Debby Boone topped the charts for 10 straight weeks.
In the 1980s, Physical by Olivia Newton-John topped the charts for 10 straight weeks.
In the 1990s, One Sweet Day by Mariah Carey ft. Boyz II Men topped the charts for 16 straight weeks.
=> nil

うまく返ってきました\o/
念のため、ここでeachメソッドを使ってドキュメントが返ってくるか確認します。

pry(main)> cursor.each{ |doc| puts message.call(doc['decade'], doc['song'], doc['artist'], doc['weeksAtOne']) }
=> nil

予想通りダメでしたw 一度イテレートしたのでカーソルが末尾に移動した、と理解すれば良いようです。
(後で説明しますが、Mongo::Collection#findの返値Mongo::CursorはEnumerableをIncludeしていますので、#findでの検索結果はEnumerable#to_aで全て取得できます。)

コレクションのこと

次に、コレクションインスタンスsongsのことを探ってみます。
クラスはMongo::Collectionです。

pry(main)> songs.class
=> Mongo::Collection
pry(main)> songs.size
=> 3
pry(main)> songs.name
=> "songs"
pry(main)> songs.stats
=> {"ns"=>"heroku_app2x19xxxx.songs",
 "count"=>3,
 "size"=>352,
 "avgObjSize"=>117.33333333333333,
 "storageSize"=>8192,
 "numExtents"=>1,
 "nindexes"=>1,
 "lastExtentSize"=>8192,
 "paddingFactor"=>1.0,
 "systemFlags"=>1,
 "userFlags"=>0,
 "totalIndexSize"=>8176,
 "indexSizes"=>{"_id_"=>8176},
 "ok"=>1.0}
pry(main)> songs.index_information
=> {"_id_"=>
  {"v"=>1,
   "key"=>{"_id"=>1},
   "ns"=>"heroku_app2x19xxxx.songs",
   "name"=>"_id_"}}
pry(main)> songs.db
=> #<Mongo::DB:0x007f82d2a4fc98
 @acceptable_latency=15,
 @cache_time=300,
 @connection=
  #<Mongo::MongoClient:0x007f82d42c9288
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app2x19xxxx",
      :username=>"heroku_app2x19xxxx",
      :password=>"zkgac9ierai4fjrd960ddob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app2x19xxxx",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007f82d42c91e8>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_time…
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @name="heroku_app2x19xxxx",
 @pk_factory=nil,
 @read=:primary,
 @strict=nil,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>
pry(main)> db.collection_names
=> ["system.indexes", "system.users", "system.profile", "songs"]

コレクションにsongsが増えていますね。

チュートリアルその3

では

をやってみます。
まず'testCollection'コレクションを用意します。今回はMongo::DB#[]表記で作ってみましょう。

pry(main)> coll = db["testCollection"]
=> #<Mongo::Collection:0x007f82d41dd838
 @acceptable_latency=15,
 @cache={},
 @cache_time=300,
 @capped=nil,
 @connection=
  #<Mongo::MongoClient:0x007f82d42c9288
   @acceptable_latency=15,
   @auths=
    [{:db_name=>"heroku_app2x19xxxx",
      :username=>"heroku_app2x19xxxx",
      :password=>"zkgac9ierai4fjrd960ddob37t"}],
   @connect_timeout=30,
   @default_db="heroku_app2x19xxxx",
   @host="dbh70.mongolab.com",
   @id_lock=#<Mutex:0x007f82d42c91e8>,
   @logger=nil,
   @max_bson_size=16777216,
   @max_message_size=48000000,
   @mongos=false,
   @op_timeout=nil,
   @pool_size=1,
   @pool_timeout=5.0,
   @port=27707,
   @primary=["dbh70.mongolab.com", 27707],
   @primary_pool=
    #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_time
   @read=:primary,
   @read_primary=true,
   @slave_ok=nil,
   @socket_class=Mongo::TCPSocket,
   @socket_opts={},
   @ssl=nil,
   @tag_sets=[],
   @unix=false,
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @db=
  #<Mongo::DB:0x007f82d2a4fc98
   @acceptable_latency=15,
   @cache_time=300,
   @connection=
    #<Mongo::MongoClient:0x007f82d42c9288
     @acceptable_latency=15,
     @auths=
      [{:db_name=>"heroku_app2x19xxxx",
        :username=>"heroku_app2x19xxxx",
        :password=>"zkgac9ierai4fjrd960ddob37t"}],
     @connect_timeout=30,
     @default_db="heroku_app2x19xxxx",
     @host="dbh70.mongolab.com",
     @id_lock=#<Mutex:0x007f82d42c91e8>,
     @logger=nil,
     @max_bson_size=16777216,
     @max_message_size=48000000,
     @mongos=false,
     @op_timeout=nil,
     @pool_size=1,
     @pool_timeout=5.0,
     @port=27707,
     @primary=["dbh70.mongolab.com", 27707],
     @primary_pool=
      #<Mongo::Pool:0x3fc16a166bcc @host=dbh70.mongolab.com @port=27707 @ping_ti…
     @read=:primary,
     @read_primary=true,
     @slave_ok=nil,
     @socket_class=Mongo::TCPSocket,
     @socket_opts={},
     @ssl=nil,
     @tag_sets=[],
     @unix=false,
     @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
   @name="heroku_app2x19xxxx",
   @pk_factory=nil,
   @read=:primary,
   @strict=nil,
   @tag_sets=[],
   @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>,
 @hint=nil,
 @logger=nil,
 @name="testCollection",
 @pk_factory=BSON::ObjectId,
 @read=:primary,
 @tag_sets=[],
 @write_concern={:w=>1, :j=>nil, :fsync=>nil, :wtimeout=>nil}>

コレクションに登録するデータを用意します。

pry(main)> doc = {"name" => "MongoDB", "type" => "database", "count" => 1, "info" => {"x" => 203, "y" => '102'}}
=> {"name"=>"MongoDB",
 "type"=>"database",
 "count"=>1,
 "info"=>{"x"=>203, "y"=>"102"}}

collコレクションに上記データを入れます。
あとで使うので、データ/ドキュメントを入れたときの返値をidに保存しておきます。

pry(main)> id = coll.insert(doc)
=> BSON::ObjectId('532aec2528f29012cc000004')

コレクションに'testCollection'が増えています。

pry(main)> db.collection_names
=> ["system.indexes", "system.users", "system.profile", "songs", "testCollection"]

さらにデータを追加します。'i'キーに0..99の100個。

pry(main)> 100.times { |i| coll.insert("i" => i) }
=> 100

collコレクションの第1ドキュメントはMongo::Collection#find_oneで取ります。

pry(main)> coll.find_one
=> {"_id"=>BSON::ObjectId('532aec2528f29012cc000004'),
 "name"=>"MongoDB",
 "type"=>"database",
 "count"=>1,
 "info"=>{"x"=>203, "y"=>"102"}}

ちなみにドキュメントはBSON::OrderedHashで返されるようです。

pry(main)> coll.find_one.class
=> BSON::OrderedHash

Mongo::Collection#findの返値は(先にも見たように)Mongo::Cursorですが、これはEnumerableをIncludeしているので、Enumerable#to_a*1で全ドキュメントが返されます。
(なのでEnumerable#firstを使えば、上記のMongo::Collection#find_oneの代わりに(Mongo::Collection#find)#firstで取得出来ます。)

pry(main)> coll.find.to_a
=> [{"_id"=>BSON::ObjectId('532aec2528f29012cc000004'),
  "name"=>"MongoDB",
  "type"=>"database",
  "count"=>1,
  "info"=>{"x"=>203, "y"=>"102"}},
 {"_id"=>BSON::ObjectId('532aec4a28f29012cc000005'), "i"=>0},
 {"_id"=>BSON::ObjectId('532aec4a28f29012cc000006'), "i"=>1},
 {"_id"=>BSON::ObjectId('532aec4b28f29012cc000007'), "i"=>2},
 {"_id"=>BSON::ObjectId('532aec4b28f29012cc000008'), "i"=>3},
 {"_id"=>BSON::ObjectId('532aec4b28f29012cc000009'), "i"=>4},
 {"_id"=>BSON::ObjectId('532aec4b28f29012cc00000a'), "i"=>5},
 :(省略)

先に使っていたsongsコレクションも見てみます。

pry(main)> puts songs.find.to_a;
{"_id"=>BSON::ObjectId('532ae2c828f29012cc000001'), "decade"=>"1970s", "artist"=>"Debby Boone", "song"=>"You Light Up My Life", "weeksAtOne"=>10}
{"_id"=>BSON::ObjectId('532ae2c828f29012cc000002'), "decade"=>"1980s", "artist"=>"Olivia Newton-John", "song"=>"Physical", "weeksAtOne"=>10}
{"_id"=>BSON::ObjectId('532ae2c828f29012cc000003'), "artist"=>"Mariah Carey ft. Boyz II Men", "decade"=>"1990s", "song"=>"One Sweet Day", "weeksAtOne"=>16}

ここでまたMongo::Collection#findオブジェクトでいろいろやってみます。

pry(main)> coll.find("i" => 71).to_a
=> [{"_id"=>BSON::ObjectId('532aec5928f29012cc00004c'), "i"=>71}]
pry(main)> songs.find({'decade' => '1990s'}).to_a
=> [{"_id"=>BSON::ObjectId('532ae2c828f29012cc000003'),
  "artist"=>"Mariah Carey ft. Boyz II Men",
  "decade"=>"1990s",
  "song"=>"One Sweet Day",
  "weeksAtOne"=>16}]
pry(main)> songs.find('decade' => '1990s').to_a
=> [{"_id"=>BSON::ObjectId('532ae2c828f29012cc000003'),
  "artist"=>"Mariah Carey ft. Boyz II Men",
  "decade"=>"1990s",
  "song"=>"One Sweet Day",
  "weeksAtOne"=>16}]
pry(main)> songs.find('weeksAtOne' => 10).to_a
=> [{"_id"=>BSON::ObjectId('532ae2c828f29012cc000001'),
  "decade"=>"1970s",
  "artist"=>"Debby Boone",
  "song"=>"You Light Up My Life",
  "weeksAtOne"=>10},
 {"_id"=>BSON::ObjectId('532ae2c828f29012cc000002'),
  "decade"=>"1980s",
  "artist"=>"Olivia Newton-John",
  "song"=>"Physical",
  "weeksAtOne"=>10}]

条件を満たすドキュメントを抜き出して、Mongo::Cursor#to_aで全て出力してみましょう。

pry(main)> puts coll.find("i" => {"$gt" => 90}).to_a;
{"_id"=>BSON::ObjectId('532aec5e28f29012cc000060'), "i"=>91}
{"_id"=>BSON::ObjectId('532aec5e28f29012cc000061'), "i"=>92}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000062'), "i"=>93}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000063'), "i"=>94}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000064'), "i"=>95}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000065'), "i"=>96}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000066'), "i"=>97}
{"_id"=>BSON::ObjectId('532aec6028f29012cc000067'), "i"=>98}
{"_id"=>BSON::ObjectId('532aec6028f29012cc000068'), "i"=>99}
pry(main)> puts coll.find("i" => {"$gte" => 90}).to_a;
{"_id"=>BSON::ObjectId('532aec5e28f29012cc00005f'), "i"=>90}
{"_id"=>BSON::ObjectId('532aec5e28f29012cc000060'), "i"=>91}
{"_id"=>BSON::ObjectId('532aec5e28f29012cc000061'), "i"=>92}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000062'), "i"=>93}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000063'), "i"=>94}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000064'), "i"=>95}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000065'), "i"=>96}
{"_id"=>BSON::ObjectId('532aec5f28f29012cc000066'), "i"=>97}
{"_id"=>BSON::ObjectId('532aec6028f29012cc000067'), "i"=>98}
{"_id"=>BSON::ObjectId('532aec6028f29012cc000068'), "i"=>99}
pry(main)> puts coll.find("i" => {"$gt" => 20, "$lte" => 30}).to_a;
{"_id"=>BSON::ObjectId('532aec4f28f29012cc00001a'), "i"=>21}
{"_id"=>BSON::ObjectId('532aec4f28f29012cc00001b'), "i"=>22}
{"_id"=>BSON::ObjectId('532aec4f28f29012cc00001c'), "i"=>23}
{"_id"=>BSON::ObjectId('532aec4f28f29012cc00001d'), "i"=>24}
{"_id"=>BSON::ObjectId('532aec4f28f29012cc00001e'), "i"=>25}
{"_id"=>BSON::ObjectId('532aec5028f29012cc00001f'), "i"=>26}
{"_id"=>BSON::ObjectId('532aec5028f29012cc000020'), "i"=>27}
{"_id"=>BSON::ObjectId('532aec5028f29012cc000021'), "i"=>28}
{"_id"=>BSON::ObjectId('532aec5028f29012cc000022'), "i"=>29}
{"_id"=>BSON::ObjectId('532aec5028f29012cc000023'), "i"=>30}
pry(main)> puts coll.find({"name" => /^M/}).to_a;
{"_id"=>BSON::ObjectId('532aec2528f29012cc000004'), "name"=>"MongoDB", "type"=>"database", "count"=>1, "info"=>{"x"=>203, "y"=>"102"}}

ドキュメント更新

チュートリアル3で最初にコレクションへ入れたデータ/ドキュメントの"name"を書き替えます。
Mongo::Collection#updateで更新しますが、更新データの指定として、このデータをコレクションに追加したときの返値idを使います。

pry(main)> doc["name"] = "MongoDB Ruby"
=> "MongoDB Ruby"
pry(main)> id
=> BSON::ObjectId('532aec2528f29012cc000004')
pry(main)> coll.update({"_id" => id}, doc)
=> {"updatedExisting"=>true,
 "n"=>1,
 "lastOp"=>seconds: 1395323090, increment: 1,
 "connectionId"=>13070,
 "err"=>nil,
 "ok"=>1.0}
pry(main)> puts coll.find("_id" => id).to_a;
{"_id"=>BSON::ObjectId('532aec2528f29012cc000004'), "name"=>"MongoDB Ruby", "type"=>"database", "count"=>1, "info"=>{"x"=>203, "y"=>"102"}}

コレクションのドキュメント数は1 + 100で101個。

pry(main)> coll.count
=> 101

ドキュメント削除

コレクションから指定したドキュメントを削除します。Mongo::Collection#removeメソッドに引数を指定すれば該当するドキュメントが削除されます。

pry(main)> coll.remove("i" => 98)
=> {"n"=>1,
 "lastOp"=>seconds: 1395323338, increment: 1,
 "connectionId"=>13070,
 "err"=>nil,
 "ok"=>1.0}
pry(main)> coll.count
=> 100
pry(main)> puts coll.find("i" => 98).to_a
=> nil

全ドキュメント削除

Mongo::Collection#removeメソッドに引数を指定しなければ全て削除されます。

pry(main)> coll.remove
=> {"n"=>100,
 "lastOp"=>seconds: 1395323378, increment: 100,
 "connectionId"=>13070,
 "err"=>nil,
 "ok"=>1.0}
pry(main)> coll.count
=> 0

collコレクションの中身が全て消えました。

コレクション自体の削除

続いてcollコレクション自体を削除します。Mongo::Collection#dropメソッドを使います。

pry(main)> coll.drop
=> true

インデックス

データベースに関してはほとんど無知なので分かってなかったのですが、RDBMSでは必須でNoSQLだと珍しいのがインデックス利用の可否のようですね。MongoDBなら使えるのがポイント。

  • インデックスを設定する

Index - 索引
検索をするときにIndexが設定してあると動作が高速になる。
張りすぎると挿入時に動作が遅くなるので注意。
http://qiita.com/ANTON072/items/e0534daa4b2fb0f553eb#2-9

Indexを作成するのはdb.coll.ensureIndex()を利用
Indexを確認するのはdb.coll.getIndexes()を利用
その他Indexの統計情報を確認するにはdb.coll.stats()やdb.system.indexes.find()を利用
実際にIndexが利用されているかはdb.coll.find().explain()を利用
http://kozy4324.github.io/blog/2012/06/19/mongodb-index/

ではさっきコレクションを消してしまったので、もう一度作ってみます。

pry(main)> coll = db["testCollection"];
pry(main)> coll.create_index("i")
=> "i_1"
pry(main)> coll.create_index(:i => Mongo::ASCENDING)
=> "i_1"
pry(main)> id
=> BSON::ObjectId('532aec2528f29012cc000004')

実際にIndexが利用されているかを確認する(らしい)Mongo::Cursor#explainメソッド。
Mongo::Collection#findでIndexが使われたかどうかを確認するんでしょうね。(DB知識ないので勘違いしてるかも)

pry(main)> coll.find("_id" => id).explain
=> {"cursor"=>"BtreeCursor _id_",
 "isMultiKey"=>false,
 "n"=>0,
 "nscannedObjects"=>0,
 "nscanned"=>0,
 "nscannedObjectsAllPlans"=>0,
 "nscannedAllPlans"=>0,
 "scanAndOrder"=>false,
 "indexOnly"=>false,
 "nYields"=>0,
 "nChunkSkips"=>0,
 "millis"=>16,
 "indexBounds"=>
  {"start"=>{"_id"=>BSON::ObjectId('532aec2528f29012cc000004')},
   "end"=>{"_id"=>BSON::ObjectId('532aec2528f29012cc000004')}},
 "allPlans"=>
  [{"cursor"=>"BtreeCursor _id_",
    "n"=>0,
    "nscannedObjects"=>0,
    "nscanned"=>0,
    "indexBounds"=>
     {"start"=>{"_id"=>BSON::ObjectId('532aec2528f29012cc000004')},
      "end"=>{"_id"=>BSON::ObjectId('532aec2528f29012cc000004')}}}],
 "server"=>"h000215.mongolab.com:27707"}
pry(main)> coll.find("i" => 71)
=> <Mongo::Cursor:0x3fc16a1b95e8 namespace='heroku_app2x19xxxx.testCollection' @selector={"i"=>71} @cursor_id=>
pry(main)> puts coll.find("i" => 71).explain
{"cursor"=>"BtreeCursor i_1", "isMultiKey"=>false, "n"=>0, "nscannedObjects"=>0, "nscanned"=>0, "nscannedObjectsAllPlans"=>0, "nscannedAllPlans"=>0, "scanAndOrder"=>false, "indexOnly"=>false, "nYields"=>0, "nChunkSkips"=>0, "millis"=>0, "indexBounds"=>{"i"=>[[71, 71]]}, "allPlans"=>[{"cursor"=>"BtreeCursor i_1", "n"=>0, "nscannedObjects"=>0, "nscanned"=>0, "indexBounds"=>{"i"=>[[71, 71]]}}], "server"=>"h000215.mongolab.com:27707"}
=> nil

インデックスを削除します。
Mongo::Collection#drop_indexで引数のインデックスを削除します。
Mongo::Collection#drop_indexesだとコレクションの全てのインデックスを削除するようです。(試してないです)

pry(main)> coll.drop_index("i_1")
=> true
pry(main)> puts coll.find("i" => 71)
#<Mongo::Cursor:0x007f82d4256120>
=> nil

コレクション削除

Mongo::DB#drop_collectionで引数にて名称指定したコレクションを削除します。

pry(main)> db.drop_collection("testCollection")
=> true

先に使ったMongo::Collection#dropメソッドと何が違うのだろう、と思いましたが、

Mongo::Collection#drop

def drop
  @db.drop_collection(@name)
end

Method: Mongo::Collection#drop — Documentation for mongo (2.6.2)

とあるので、削除するコレクションの指定方法が引数なのかレシーバなのかの違いだけですね。

データベース自体の削除

Mongo::MongoClient#drop_databaseで削除します。

pry(main)> client.drop_database("heroku_app2x19xxxx")
=> {"dropped"=>"heroku_app2x19xxxx", "ok"=>1.0}

なお、上記しましたが

データベース名をMongoLabのユーザにしておかないとMongo::OperationFailureが返されます。
つまり、MongoLabから自由に扱えるよう許可されているのは、MongoDBの構造
MongoDB > database > collection > document
のcollection以下ということになりますね。

なので、heroku_app2x19xxxxデータベースを消してしまったらMongoLabを使えませんw
仕方ないのでheroku dashboardのMongoLabリンクからの設定画面で、既存パスワードにてheroku_app2x19xxxxデータベースを作成しましょう。
私は全く分かって無くて、使えなくなってしまって困りましたw