Ruby on RailsでBULK INSERT
やりたいこと
RailsでBULK INSERTしたい。
方法
Gem「activerecord-import」を使う。
- 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以外の任意の項目を指定したい
方法
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 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のバッチ処理でActiveRecordを使ってモデルクラス経由でのDB接続方法
- Rails3 runnerを利用してバッチプログラムを実行するサンプル
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
参考サイト
- railsのログへ時刻/PID付与
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
参考サイト
- asset pipelineのログの非表示
- configにassetsのログを出さない設定ファイルを作る
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の通信開始/終了のイベントをハンドリングする方法
方法
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>
参考サイト
- 特に無し