Ruby on RailsでBULK INSERT

やりたいこと

RailsでBULK INSERTしたい。

方法

Gem「activerecord-import」を使う。
$ vi Gemfile
gem 'activerecord-import'
$ sudo bundle install
  • importメソッドを使用してBULK INSERT
reg_tweets = []
tweets.each do |tweet|
  # DB登録項目
  user = tweet['user']
  tweet_id = tweet['id_str']
  name = user['name']
  screen_name = user['screen_name']
  text = tweet['text']
  profile_image_url = user['profile_image_url']
  tweeted_at = Time.parse(tweet['created_at'])

  reg_tweets << Tweet.new(
                    :tweet_id => tweet_id,
                    :screen_name => screen_name,
                    :name => name,
                    :text => text,
                    :profile_image_url => profile_image_url,
                    :tweeted_at => tweeted_at,
                    :chapter_id => @chapter_id
                )
end
# BULK INSERT実行
Tweets.import(reg_tweets)
速度検証(軽〜くだけど)
  • なお、BULK INSERTとDBドライバを直接使用した場合の速度は下記の通り。
    DBドライバ直接使用の方が速い。
■DBドライバ直接使用
[2013/07/16 17:10:27.895040] (pid=7986) INFO -- : AnimeTweetsRegister#register_tweets start
[2013/07/16 17:11:25.173947] (pid=7986) INFO -- : 合計登録ツイート数:[25615]
[2013/07/16 17:11:25.174424] (pid=7986) INFO -- : AnimeTweetsRegister#register_tweets end

■BULK INSERT使用
[2013/07/16 18:05:18.457076] (pid=9073) INFO -- : AnimeTweetsRegister#register_tweets start
[2013/07/16 18:07:01.408476] (pid=9073) INFO -- : 合計登録ツイート数:[25624]
[2013/07/16 18:07:01.411327] (pid=9073) INFO -- : AnimeTweetsRegister#register_tweets end
  • 全パターンは計測してないけど、一般的には下記の順で速いと思われる。
    DBドライバ直接使用 > BULK INSERT > createメソッド
その他
  • ちなみに、DBがMySQLの場合はBULK INSERTを行う際にPKが重複してた場合にUPDATEを行う
    「ON DUPLICATE KEY UPDATE」が働くとのでPK制約違反発生で登録できないとか無くせるみたい。便利。

参考サイト

Ruby on RailsでURLにid以外の任意の項目を指定したい

やりたいこと

Rails機械的に振られたidではなく、モデル的に意味のある値(ユーザ名とか話数とか)でアクセスできるようにしたい。

方法

URLを変更したいモデルのto_paramメソッドをオーバーライド
  • to_paramメソッドをオーバーライド
$ vi app/models/chapter.rb 
class Chapter < ActiveRecord::Base
 ・
 ・
 ・
  # 話数によるURLでのアクセスを可能とする
  def to_param
    # URLで指定する項目を指定
    chapter
  end
end
params[:id]で渡ってくる値が変わるのでそれに合わせてControllerを修正
  • 今まではChapterを一意に指定するchapter_idが渡ってきていたが、話数が渡ってくるようになったので対応。
$ vi app/models/chapter.rb 
@chapter = Chapter.find(params[:id])
↓
@chapter = Chapter.find_by_anime_id_and_chapter(params[:anime_id], params[:id])
  • ちなみに、下記のようにwhereを使ってChapterを取得しようとするとChapterは取れるのにView生成時にChapterの項目が取得できずエラーとなった。なんでだろう?
$ vi app/models/chapter.rb 
@chapter = Chapter.where('anime_id', params[:anime_id]).where('chapter',params[:id])

参考サイト

Ruby on Railsでバッチ処理

やりたいこと

Rails上でバッチ処理を実行したい。

方法

rails runnerコマンドを使う。
  • rails runnerコマンドで実行するプログラムの作成
$  vi lib/tasks/anime_tweets_collect_task.rb
# coding: utf-8
require 'anime_tweets_searcher'
require 'anime_tweets_register'

class Tasks::AnimeTweetsCollectTask
  # メイン処理
  def self.execute
    # Active Recordを使用したDBアクセスが可能
    chapters = Chapter.all
    ・
    ・
    ・
  end
end
  • 実行プログラムからrequireするクラスはlib配下に格納
$ vi lib/anime_tweets_searcher.rb
$ vi lib/anime_tweets_register.rb
  • lib配下をロードするための設定を追加
    ※本設定は実行プログラムからrequireするクラスの有無に関わらず必要なので注意。
$ vi config/application.rb
    # lib配下をロードするための設定
    config.autoload_paths += %W(#{config.root}/lib)
    config.autoload_paths += Dir["#{config.root}/lib/**/"]
  • rails runnerコマンドで起動
$ rails runner Tasks::AnimeTweetsCollectTask.execute

参考サイト

Ruby on RailsでログにタイムスタンプとプロセスIDを表示

やりたいこと

ログ解析のためにタイムスタンプとプロセスIDを表示したい。

方法

タイムスタンプとプロセスIDを表示するためのログフォーマットクラスを定義
$ vi config/environment.rb
# Load the rails application
require File.expand_path('../application', __FILE__)

#「FormatWithTime」という名前で新規ログフォーマットクラスを作成
class Logger::FormatWithTime < Logger::Formatter
  # 日付フォーマット指定
  cattr_accessor(:datetime_format) { "%Y/%m/%d %H:%M:%S" }

  def call(severity, timestamp, progname, msg)
    #「$$」は自分のPID
    "[#{timestamp.strftime(datetime_format)}.#{'%06d' % timestamp.usec.to_s}] (pid=#{$$}) #{severity} -- : #{String === msg ? msg : msg.inspect}\n"
  end
end

# Initialize the rails application
TimeshiftAnimeLive::Application.initialize!
環境定義ファイルにロガーの設定を追加
$ vi config/environments/development.rb
  # デフォルトのログのパスを取得してロガー生成
  config.logger = Logger.new(config.paths["log"].first)
  # 新規作成したログフォーマットクラスを指定
  config.logger.formatter = Logger::FormatWithTime.new
  # ログレベルを設定
  config.logger.level = Logger::DEBUG

参考サイト

Ruby on Railsでasset pipeline関連のログを出力させない方法

やりたいこと

ログが見辛くなるので下記の様なasset pipeline関連のログを出力させないようにしたい。

Started GET "/assets/scaffolds.css?body=1" for 219.118.179.251 at 2013-04-24 16:28:05 +0900
Served asset /scaffolds.css - 304 Not Modified (2ms)

方法

設定ファイルでasset pipeline関連のログを出力しないために下記設定を追加
$ vi config/environments/development.rb
config.assets.logger = false
ログ出力制御のための設定ファイル作成
$ vi config/initializers/quiet_assets.rb
Rails.application.assets.logger = Logger.new('/dev/null') 
Rails::Rack::Logger.class_eval do
  def call_with_quiet_assets(env)
    previous_level = Rails.logger.level
    Rails.logger.level = Logger::ERROR if env['PATH_INFO'].index("/assets/") == 0
    call_without_quiet_assets(env).tap do
      Rails.logger.level = previous_level
    end
  end
  alias_method_chain :call, :quiet_assets
end

参考サイト

Ruby on RailsでJSONをオブジェクトのままJavaScriptに渡す方法

やりたいこと

Ajaxの非同期通信により取得したJSONデータをオブジェクトのままViewのJavaScriptに渡したい。

方法

rawメソッドを使用する。
  • ControllerにてDBから取得したデータを変数に格納。
$ vi app/controllers/chapters_controller.rb

  def tweets
    @tweets = Tweet.find_all_by_chapter_id(params[:id])
  end
  • Ajax用のerbファイルにて、Controllerで取得したデータをrawメソッドを使用してJavaScriptの変数にJSONオブジェクトとして渡す。
$ vi app/views/chapters/tweets.js.erb

window.tweets = <%= raw @tweets.to_json %>;
  • View側で取得したJSONオブジェクトからデータを参照
    • 一時的にerb内に書いてしまっているが、本来はJavaScriptファイルに外出しするべき。
    • なお、Ajaxで取得したレスポンスデータをコールバック関数外で使うには、グローバル変数に格納するしか方法は無い。
$ vi app/views/chapters/show.html.erb

<script>
  window.tweets = null;

  $('#show_tweets').click(function(){
    alert(window.tweets.length);
    alert(window.tweets);
    alert(window.tweets[0].text);
  });
</script>

参考サイト

jQueryでAjaxの通信開始/終了のイベントをハンドリングする方法

やりたいこと

jQueryAjaxの通信開始/終了のイベントをハンドリングし、Ajaxによる通信中であることがユーザに分かるようにしたい。

方法

jQueryのajaxStart/ajaxCompleteメソッドを使用する。
  • JSファイルにAjaxの通信開始/終了時の処理を記載。
    ※application.jsに記載するとアプリ全体に有効になる。
$ vi app/assets/javascripts/application.js

$(function() {
    // なぜかサンプルにあった$('*')では要素の取得ができなかったのでこちらで。
    $(document)
    .ajaxStart(function() { $('#processing').html('通信中...') })
    .ajaxComplete(function() { $('#processing').html('') })
});
  • JSファイルで指定している要素をViewに追加
$ vi app/views/chapters/show.html.erb

<div id='processing'>
</div>

参考サイト

  • 特に無し