ユーザーの行動履歴をMongoDBに保存する


Railsのアプリケーションを作っているのですが、ユーザーの使用履歴とかを一覧表示したい、という仕様がありまして。
ユーザーがどのページを表示したとか、何を更新したとか等の情報を逐一保存しておきたいと。

DBはMySQLを使っているのですが、行動履歴みたいな情報をRDBにいちいち格納するのはパフォーマンス的にもちょっと心配だし、
削除もしない、どんどん溜めてくデータなので、KVSに入れるのはどうかな、と思いつきました。

**で、やってみました**

## 環境
* MacOSX Lion
* Ruby 2.0
* Rails 4.0


## MongoDBのインストール

手元のMacOSにMongoDBをインストールします。
外部のサーバーにインストールする場合は、こちらの記事をどうぞ。

[CentOSにMongoDBをインストールして外部から接続できるようにする](http://qiita.com/jacoyutorius/items/3e2cf2c098029e8d4a06)


MacOSにはHomebrewでインストールします。

```shell
brew install mongodb

# インストール完了後のメッセージに従って、以下のコマンドを実行
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
mongo --version
 => MongoDB shell version: 2.6.4
```


## RailsアプリからMongoDBに更新する

### RailsにMongoDB用gemを追加

bson_extを入れないと、後の"config.mongoid.logger"でundefined methodエラーが出ました。

```ruby:Gemifle
gem "mongoid"
gem "bson_ext"
```

インストール

```bash
bundle install --path .bundle
```

設定ファイルを作成。中身はそのままでOKです。

```shell
rails g mongoid:config
 =>create  config/mongoid.yml
```

### 設定保存用モデルを定義

デフォルトのDBがRDBになっている場合、MongoDBへ接続するmodelをgenerateするには下記のようにします。

```shell
rails g mongoid:model action_log
```

modelを編集して必要なfieldを定義します。

```ruby:app/model/action_log.rb
class ActionLog
  include Mongoid::Document

  field :datetime, type: DateTime
  field :user_id, type: String
  field :user_name, type: String
  field :controller, type: String
  field :method, type: String
  field :body, type: String
  field :delete, type: Boolean
end
```


mongoDBだとdb:migrateする必要が無いのが嬉しいですね。
早速更新してみましょー。
まずはrails consoleからテスト。

```shell
irb(main):002:0> ActionLog.create!(datetime:Time.now, user_id:0, user_name:"jacoyutorius", body:"test")

=> #<ActionLog _id: 543df2987975746196000000, datetime: 2014-10-15 04:05:43 UTC, user_id: "0", user_name: "jacoyutorius", controller: nil, method: nil, body: "test", delete: nil>
```

無事更新できました。


## ユーザーの各アクションを残したい

さて。
元々の仕様を思い出すと、「ユーザーの行動履歴を採りたい！」というのが始まりでした。
今回はcontrollerの各アクションが呼ばれる際のパラメータやログインユーザーをMongoDBに保存することで対応しようと思います。

各コントローラにずらずらと同じコードを書くのはDRYでないので、concernsを使います。

```ruby:app/controllers/concerns/log_monger.rb
module LogMonger
  extend ActiveSupport::Concern

  included do 
      before_action :save_log

	  def save_log
	    ActionLog.create!(
	      datetime: Time.now,
	      user_id: 1,
	      user_name: "jacoyutorius",
	      method: params[:method],
	      controller: params[:controller],
	      body: params,
	      delete: false
	      )
	  end

	end
end
```

controllerに飛ばされてくるparamsもまるまるbodyに格納してます。
あとは各コントローラでincludeします。

```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
  include LogMonger

# -(省略)-
end
```

あとはRailsアプリを起動して適当にいじってみましょう。
MongoDBにはこんな感じで保存されます。

```shell
irb(main):005:0> ActionLog.first
=> #<ActionLog _id: 543deed77975745d73000000, datetime: 2014-10-15 03:49:43 UTC, user_id: "1", user_name: "jacoyutorius", controller: "admin/top", method: nil, body: "{\"controller\"=>\"top\", \"action\"=>\"index\"}", delete: false>
```

### できました！

![](http://www.lgtm.in/p/EGX9JzoNp)


### (2014.10.17 追記)

ActiveRecordとMongoidを併用している場合、ActiveRecordでmigrationコマンドを実行した場合に、"error  mongoid [not found]"というエラーが出るようになりました。
この場合は、

```bash
rails g active_record:migration AddHoge
```

ActiveRecordを使うよう明示的に指定してやるとmigrationが作成できます。
こちらの記事を参考に解決しました。

[mongoidとactiverecordが併存する場合のmigration](http://kan-high.hatenablog.com/entry/2013/02/24/120628)


#### (さらに追記)
上記の対応だと、scaffoldの時に同じくmongoidが使われてしまう。
最終的にはapplication.rbでgeneratorの設定を定義しておくのが良いみたい。

```ruby:application.rb
class Application < Rails::Application
  config.generators do |g|
    g.orm :active_record
  end
end
```


## 参考

[mongoidバッドノウハウ集](http://qiita.com/masahikoofjoyto/items/df7b0dc7290d389432f2)


バッドノウハウとありますが、特定の用途（今回でいえばログ取得）に絞った使い方であれば問題はないかなーと思っていますねー。