2010-05-20

rails2.3.5でマルチプルinsertをする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
mysqlのinsertのパフォーマンスをあげる方法として、マルチプルinsertを使うという方法があります。

以下のようなVALUESをいっぱい書いた感じのSQLです。
INSERT INTO <表名>
[ <列名> [ , <列名> ... ] ]
VALUES ( <値> [ , <値> ... ] ) ,
VALUES ( <値> [ , <値> ... ] ) ,
・・・


railsでは通常、普通のinsertが発行されてマルチプルinsertは使えません。

しかし、
ActiveRecord::Extensions
を使えばできちゃいます。

インストールは、
gem install ar-extensions
でOK。

そしてenvironment.rbに
config.gem 'ar-extensions'
を追加します。

そうすれば、importというメソッドが利用できるようになります。
使い方は、以下のような感じです。
columns = [ :author, :title ]
values = [ [ 'araki', 'jojo' ],[ 'oda', 'onep' ] ]
Book.import columns, values

ActiveRecord::Extensions
はマルチプルinsertだけでなく他にもいろいろ拡張されています。
詳しくは以下のサイトが参考になります。
http://d.hatena.ne.jp/yoshitetsu/20080129/1201610369
ちなみにrails3では、ActiveRecord::Extensions利用できなそうな気配なのですが、importだけならば
activerecord-import
というので、できる感じです。

activerecord-importは以下をご覧ください。
http://github.com/zdennis/activerecord-import/

ちなみに、このマルチプルinsertを使ってみた結果です。
500件まとめてinsertしてみました。
処理の前半で余計な前処理を結構しているので単純にinsert性能だけではないのですが、ご参考までに。

仕様前
 件数 約3500件  Completed in 198245ms (DB: 10444)
 件数 約10000件 Completed in 492626ms (DB: 21144)
使用後
 件数 約3500件  Completed in 169225ms (DB: 8538)
 件数 約10000件 Completed in 415683ms (DB: 16482)

2010-05-19

railsのmigrateでmysqlのbigintを使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
railsのmigrateでintegerを指定した場合は、mysqlではint型になっています。

たまにbigintが使いたくなるケースがあります。
そんな時は、
:limit=>8
をつけましょう。

他のintシリーズを使いたい時は、
http://ariejan.net/2009/08/20/once-and-for-all-rails-migrations-integer-limit-option/
が参考になります。

2010-05-13

Googleドキュメントにあるドキュメントをランダムで取得してメールをする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Googleドキュメントにいろいろメモを残しているのに全然見返すことをしていないない・・・
そんな私のためにGoogle App EngineでGoogleドキュメントの内容をランダムで取得してメール送信するものを作ってみました。

機能としては、
・文章のみをメール送信します。スプレッドシートなどは送信しません。
・取得対象はランダムです。
・文章を送信対象外にするリストを持ち、リストに追加したドキュメントは送信しません。
・oAuthを利用します
てな感じです。

エラー処理が少々大雑把なため
ドキュメントが一つもなかったり、すごく大量にあった場合にどうなるかはわかりません。

Djangoのアプリケーションになっています。
http://docs.google.com/leaf?id=0BydytAINVs9VM2E5NWRlMGItNWM1Yy00OTZhLWI5MTctYjQ0MDliNWJhMjI0&hl=ja
にアプリ部分だけzipしたものを置いてみましたので、もしご興味があればご利用ください。
zipを解凍してできたディレクトリをDjangoプロジェクトのルートに配置して利用できるように設定します。
ディレクトリ内のsettings.pyは編集する必要があります。

実行には、gdataが必要です。
以下から取得できます。
http://code.google.com/p/gdata-python-client/

Google App Engine patchも必要な気がします。
こちらで利用しているモジュールも使っているような気がします。
以下から取得できます。
http://code.google.com/p/app-engine-patch

app.yamlには
- url: /gdoc_sender/.*
  script: common/appenginepatch/main.py
  login: admin
みたいなエントリを追加する必要があります。

あと、queue.yamlを以下のような感じで。
queue:
- name: default
  rate: 5/m

- name: gdoc-sender
  rate: 5/m

そして、cron.yamlを以下のような感じで。
cron:
- description: daily mail send
  url: /gdoc_sender/set_queue/
  schedule: every day of month 09:00
  timezone: Asia/Tokyo

こんなメモを残していたのか・・・
と毎日一つは残したメモを見返すようになりましたとさ。

2010-05-12

Bloggerでソースコードをきれいに表示する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ソースコードをよく貼り付けているのですが、他の方のサイトでソースコードがきれいになっていることがよくあるので真似っこしたくなったのです。

Bloggerで利用するには大きく2つあるようです。
・Syntax Highlighter
以下が参考になります。
http://u3q.blogspot.com/2010/01/bloggercom-syntax-highlighter-21-widget.html
http://www.kuribo.info/2008/06/blogger-syntax-highlighter.html
・google-code-prettify
以下が参考になります。
http://www.kuribo.info/2008/04/code-prettify.html

この2つを比較してみると
Syntax Highlighterの方がこっていて
google-code-prettifyの方はシンプルという感じです。
というわけで
Syntax Highlighter
の導入を目指します。

Syntax Highlighter
の参考サイトを見るとウィジェットをさくっと導入できそうなのですが、私の環境ではなぜかダウンロードが始まってしまいうまく導入ができませんでした。
原因はよくわからないのですが、利用しているテンプレートとかがいけないのかもしれないです。

というわけで違う方法を探したところ以下のサイトの方法でうまくできました。
http://www.craftyfella.com/2010/01/syntax-highlighting-with-blogger-engine.html
英語のページですが、簡単に言ってしまうと、このページに書いてあるコードをテンプレートの直接編集で</head>の直前に入れるだけです。
後は、以下のページの使い方で使えばよいだけです。
http://u3q.blogspot.com/2010/01/bloggercom-syntax-highlighter-21-widget.html

こんな感じになります。

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

2010-05-09

Google App Engineのタスクキューのメモ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App Engineのタスクキューを利用したときのメモです。
Pythonさんの場合です。

defaultのキューではなくて、独自名称のキューを用意して利用する方法は、以下のリンク先を参考になります。
http://mitsukuni.org/blog/2010/01/07/appengineでqueueを分ける/
ちなみに独自で定義したキュー名にはアンダーバー(_)とかは使えません。
使えるのは英数字とハイフン(-)だけっぽいです。
使えない名前で定義してもエラーで教えてくれますけどね。

あと、ローカルの開発環境でテストするには一度アプリケーションでadminユーザとしてログインしておかないと403エラーになってキューのテストができません。
テスト環境でユーザログインの際に
Sign in as Administrator
にチェックを入れ忘れて、しばらく悩んでみました。
app.yamlでキューのurlをadmin指定でなくせばダイジョブなのかもしれませんが。

ちなみにcronの設定もadminユーザでログインしていないとダメでした。

2010-05-07

jQueryを使ってテーブルに行を追加したり削除したり

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
jQueryを使ってテーブルに行を追加したり削除したりしてみました。
行の追加は、ヘッダー行の次に追加されるようにして、削除は各行に削除リンクが用意する感じです。

以下のような感じです。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML lang="ja">
<head>
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="Content-Type" content="text/html;charset=shift_jis">

<title>test</title>
<script src="jquery.js" type="text/javascript"></script>
<script type="text/javascript">
$(function(){
$(".addadd").click(function(){
var row_data = "<tr><td>ccc</td><td>" +  Math.floor( Math.random() * 100 )
row_data = row_data + '</td><td><a href="#" class="deldel">削除</a></td></tr>'
$("#tabtab tr:first").after(row_data);
return false;
});
$(".deldel").live('click',function(event) { 
$(this).parent().parent().remove();
return false;
});
});
</script>
</head>
<body>
<a href="#" class="addadd">行を追加</a>
<table border=1 id="tabtab">
<tr><th>d1</th><th>d2</th><th></th></tr>
<tr><td>aaa</td><td>bbb</td><td><a href="#" class="deldel">削除</a></td></tr>
<tr><td>xxx</td><td>yyy</td><td><a href="#" class="deldel">削除</a></td></tr>
</table>
</body>
</html>

最近jqueryに関する本も増えてきましたね。


2010-05-04

googleドキュメントで作成した文章をAPIを利用してテキストで取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
googleドキュメントで作成した文章をGoogle App Engineで取り出したくなりました。
Google App Engineで簡単に利用したいのでtext形式でダウンロードがしかくなったのです。

以下のような感じで取れました。
Djangoでの例です。
とりあえず前件取得してランダムで取得してみます。
# -*- coding: utf-8 -*-
import random
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from ragendja.template import render_to_response
from django.conf import settings
import gdata.gauth
import gdata.docs.client
import gdata.docs.data

APP_NAME = 'googleDocumentTextGetTest'
def index(request):
  payload = dict()
  client = gdata.docs.client.DocsClient(source=APP_NAME)
  client.ssl = True
  client.ClientLogin('googleアカウントのメールアドレス', 'そのパスワード', client.source)
  payload['feed'] = client.get_everything()
  feed_len = len(payload['feed'])
  while True:
    get_doc = random.randint(0, feed_len-1)
    payload['selection'] = payload['feed'][get_doc]
    #取れたものがスプレッドシートとかだったらもう一回取り直す
    doc_type = payload['selection'].GetDocumentType()
    if doc_type == "document":
      break
  doc_uri = '/feeds/download/documents/Export?docID=' + payload['selection'].resource_id.text + '&exportFormat=txt'
  payload['content'] = client.get_file_content(doc_uri)
  return render_to_response(request, 'index.html',payload)


参考までにテンプレートは以下のような感じです。
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
lang="{% firstof LANGUAGE_CODE 'en' %}">
<head>
<title>test</title>
</head>
<body>
{{ selection.title.text }}<br>
<pre>
{{ content }}
</pre>
<ul>
{% for entry in feed.entry %}
<li>{{ entry.title.text }}</li>
{% endfor %}
</ul>
<body>
<html>  


本当だったら、全件取得でなく以下のような感じで
/feeds/default/private/full/-/document
documentファイルだけを取得したかったのですが、なぜかできませんでした。

2010-05-02

GAEでgoogleさんにOAuth

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google App EngineでgoogleさんにOAuthしてみました。
Djangoを使ったpythonで実験しています。

まずは準備としてGoogleさんにサイトを登録して
Consumer Key

Consumer Secret
を手に入れる必要があります。

https://www.google.com/accounts/ManageDomains
で利用ドメインを登録します。すると
Manage registration
に登録したドメインが出るのでクリックします。
最初に登録したドメインのページにメタタグを埋め込んでサイトの確認を実行します。
すると
Target URL path prefix:
を入れる欄が出てくるようになるので
とりあえず
サイトでOAuthを利用するパスを入れてセーブします。
例:
 http://xxxx.xxxx.xxxx/oauth_test/index.html
 とかでOAuthを使ったもろもろをoauth_test配下に入れて置くならば、
 http://xxxx.xxxx.xxxx/oauth_test
 を登録します。
(実際は、どんなものを入れるのがよいのかいまいちよく分かっていません)

これで
Consumer Key

Consumer Secret
が発行されるのでメモしておきましょう。

今回OAuth関連の処理をするものとしてgdataを利用します。
http://code.google.com/p/gdata-python-client/
gdataは、OAuth用というよりもGoogleカレンダーとかドキュメントとかのAPIを扱う便利クライアントです。
OAuthしたらなんらかのgoogleのAPIを利用するのでこれを利用します。
取得したら、
Djangoプロジェクトディレクトリ直下に
atom
gdata
をコピーします。

今回、OAuthを試すアプリとして
oauth_test
を作ります。
これはgoogleドキュメントに登録してあるデータ一覧を取得するものにしてみます。
なのでsettings.pyに使えるように設定しておきます。
またoauth_testでは認証がかかるようにapp.yamlに以下を追加します。

- url: /oauth_test/.*
script: common/appenginepatch/main.py
login: required

なので今回用意したものは以下のような感じです
Djangoルート
|-atom
|-gdata
|-oauth_test
|-template
| |-index.html
|-settings.py
|-urls.py
|-urlsauto.py
|-views.py

oauth_test配下のものはざっと以下のような感じです。

settigns.py

from ragendja.settings_post import settings
settings.CONSUMER_KEY = '取得したConsumer Keyを入れます'
settings.CONSUMER_SECRET = '取得したConsumer Secretを入れます'


urlsauto.py

# -*- coding: utf-8 -*-
from django.conf.urls.defaults import *
rootpatterns = patterns('',
(r'^oauth_test/', include('oauth_test.urls')),)


urls.py

# -*- coding: utf-8 -*-
from django.conf.urls.defaults import *
urlpatterns = patterns('oauth_test.views',
(r'^$', 'index'),
(r'^oauth_set/$', 'oauth_set'),
(r'^oauth_callback/$', 'oauth_callback'),
(r'^oauth_revork/$', 'oauth_revork'),
)


view.py

# -*- coding: utf-8 -*-
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _
from ragendja.template import render_to_response
from django.conf import settings
from google.appengine.api import users

import gdata.gauth
import gdata.docs.client
import gdata.docs.data

APP_NAME = 'oauthMyTest'
def index(request):
payload = dict()
login = users.get_current_user()
#すでに登録済みかどうかを確認します
#gdata.gauth.Ae*を利用することでGoogle App Engineのデータストアに
#取得したOAuthのトークンとシークレットをオブジェクトして格納して出し入れできます。
#memcacheのようにキーとtokenがセットになっています。
#ここではログインユーザのuser_idをキーにしています。
token = gdata.gauth.AeLoad(login.user_id())
if token == None:
payload['have_token'] = False
else:
payload['have_token'] = True
#ドキュメント一覧を取得します
#client.ssl = Trueを必ずつけましょう
#付け忘れるとInvalid Tokenとか言われて悩みます
client = gdata.docs.client.DocsClient(source=APP_NAME)
client.ssl = True
client.auth_token = gdata.gauth.AeLoad(login.user_id())
payload['feed'] = client.get_doclist()
return render_to_response(request, 'oauth_test/index.html',payload)

def oauth_set(request):
#googleにリダイレクト処理をします
SCOPES = ['https://docs.google.com/feeds/']
callback_url = 'http://%s/oauth_test/oauth_callback/' % request.get_host()
client = gdata.docs.client.DocsClient(source=APP_NAME)
request_token = client.GetOAuthToken(SCOPES, callback_url, settings.CONSUMER_KEY, consumer_secret=settings.CONSUMER_SECRET)
login = users.get_current_user()
#callbackしてきたときに利用できるように一時的にtokenを格納します。
gdata.gauth.AeSave(request_token, "tmp_" + login.user_id())
return HttpResponseRedirect(request_token.generate_authorization_url())

def oauth_callback(request):
#アクセス用のトークンとトークンシークレットを格納します
login = users.get_current_user()
#一時的に格納したtokenを取り出し削除しておきます。
saved_request_token = gdata.gauth.AeLoad("tmp_" + login.user_id())
gdata.gauth.AeDelete("tmp_" + login.user_id())
request_token = gdata.gauth.AuthorizeRequestToken(saved_request_token, request.build_absolute_uri())
client = gdata.docs.client.DocsClient(source=APP_NAME)
try:
#ここでアクセストークンを取得して格納
access_token = client.GetAccessToken(request_token)
gdata.gauth.AeSave(access_token, login.user_id())
except:
logging.info('cant get access token')
return HttpResponseRedirect('/oauth_test/')

def oauth_revork(request):
#取得したtokenを破棄します
login = users.get_current_user()
gdata.gauth.AeDelete(login.user_id())
return HttpResponseRedirect('/oauth_test/')


template/index.html

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
lang="{% firstof LANGUAGE_CODE 'en' %}">
<head>
<title>oauth_test</title>
</head>
<body>
{% if have_token %}
<a href="/oauth_test/oauth_set/">登録する</a>
{% else %}
<ul>
{% for entry in feed.entry %}
<li>{{ entry.title.text }}</li>
{% endfor %}
</ul>
<hr>
<a href="/oauth_test/oauth_revork/">登録を解除する</a>
{% endif %}
</body>
</html>


こんな感じです。
TwitterのOAuthと比べるとちょっとめんどっちい感じです。