2012/02/07

PostgreSQL の interval (と reltime)

PostgreSQL にて n ヶ月後の初日や最終日を文字列で返す関数を登録しようとした。
イメージとしてはこんな感じ。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_date + interval '$1 months'), 'YYYY/MM/DD')$BODY$
language 'sql';
だがこれだとうまくいかなくて、引数に何を与えても 2012/03/01 が返ってくる。
'$1 months' が '1 months' とみなされている気がする。

で、次にやってみたのがこんな感じ。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_date + interval ($1 || ' months')), 'YYYY/MM/DD')$BODY$
language 'sql';
構文エラーで登録すらできない。

interval だとだめなのかなと思って、interval のいらない current_timestamp で挑戦してみる。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_timestamp + ($1 || ' months')), 'YYYY/MM/DD')$BODY$
language 'sql';
「ERROR: 演算子が存在しません: date + text」と言われる。

なるほど text だからキャストが必要なのかと言うことで varchar にキャストしてみる。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_timestamp + cast($1 || ' months' as varchar)), 'YYYY/MM/DD')$BODY$
language 'sql';
これまた演算子が存在しないと言われる。

そういや、そもそも interval ってなんなんだ
と思って調べてみるとどうやらデータ型らしい。
interval '1 months' みたいに書くから識別子とかかと思ってたらデータ型でキャストというか型変換を意味していたのか!
というわけでこうしてみた。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_timestamp + cast($1 || ' months' as interval)), 'YYYY/MM/DD')$BODY$
language 'sql';
登録できたよ。
select first_date_string_on_month(3)
--  → 2012/05/01
select first_date_string_on_month(-2)
--  → 2011/12/01
select first_date_string_on_month(0)
--  → 2012/02/01
ちゃんと動いてるよ。

ちなみに reltime というデータ型もあるみたい。
create or replace function first_date_string_on_month(integer)
returns varchar
as 
$BODY$select to_char(date_trunc('month', current_timestamp + cast($1 || ' months' as reltime)), 'YYYY/MM/DD')$BODY$
language 'sql';
とすると登録できて同じ結果が取得できた。
reltime型 を引数に取る interval 関数というのがあるので、それが関係していると思われるがよくわからない。
Google 先生で調べても PostgreSQL と realtime の検索結果が表示されるし。
とりあえず interval を使うのが正当だと思うのでそれでいいや。

2011/12/24

Nokogiri 使ったので軽くメモ

Nokogiri を使って HTML のソース解析を簡単にやってみたのでメモ。 まずはインストール。
# これがないと Nokogiri インストール時にエラーになる
$ sudo aptitude install libxml2-dev libxslt1-dev
# Nokogiri をインストール
$ sudo gem install nokogiri
そして、使い方。
require 'rubygems'
require 'open-uri'
require 'nokogiri'

# この open メソッドのために open-uri を require する
html = Nokogiri::HTML(open(url))
# XPath で指定する(css セレクタよりも個人的には XPath の方が好み。)
options = html.search("//div[@id='summary']/select[@class='prefectures']/option")
# 対応する要素がない場合、空が帰ってくる
if options.length.zero?
  puts "データがありません"
else
  options.each do |option|
    # 属性値の取得とテキストの取得
    puts "#{option.attribute('value')} : #{option.text}"
  end
end
DOM がわかってれば簡単だ。 Nokogiri の使い方についてはコチラが簡潔わかりやすいと思う。

2011/08/04

Object#try マジ便利

Rails 2.3 から導入された Object#try これ便利ですね。
とあるオブジェクト obj があって attr という属性を持っている場合、obj.try(:attr) とすると、obj が nil でもエラーがでないと。
# これが
value = obj ? obj.attr : nil

# こんな風にかける
value = obj.try(:attr)

# ほかにもこんなのが
if obj && obj.attr_1 && obj.attr_1.attr_2
  value = obj.attr_1.attr_2.attr3
end

# こうかける
if obj.try(:attr_1).try(:attr_2)
  value = obj.attr_1.attr_2.attr_3
end
nil かどうかを意識しなくてすむというのは非常に大きい。

2011/07/25

Android にモバイル Suica がやってきたよ

7/23 に Android 対応モバイル Suica サービスが開始されたと言うことでさっそくインストールした。
普通にマーケットからするするっとインストール。
寝かせておいた W53CA に SIM を移して、久々に立ち上げて機種変更の手続き。
また IS03 に SIM を入れ替えて移行の手続き。
ちょっとめんどくさかったけど、トラブルなく移行できた。
残ってた 1500 ほどのポイントも生き返った。
後は今使用中のカードタイプの定期券をモバイル Suica に移せるといいんだけど、できるんだっけ?

[2011-07-28_追記]
コールセンターに問い合わせてみたら、機種変更にて引き継いだ場合、別カードの定期情報は取り込めないらしい。
がっかり

2011/06/16

すべての submit に disable_with を

submit ボタンをダブルクリックとかされて、二重登録されてしまうことありますね。
その対策として、クライアント側でお手軽なのが、submit ボタンを押された瞬間に disabled にしてやるというのがあります。
サーバ側でのチェックをするには token を組み込むだのありますが、今回はクライアント側 JavaScript のお話のみということで。

Rails だと非常に簡単にこの機能を提供していて、f.submit :disable_with => '処理中' みたいな感じでできます。
submit_tag でも同様です。(f.submit は submit_tag を呼び出してるし)
ところが、全てのフォームにおいてこれを仕込んでいくのは非常にめんどくさい。
どうせなら一度の設定でアプリケーション全体に適応させましょう。
やってみると意外と簡単で、$RAILS_ROOT/config/initializers の下にこんなコードのファイルを配置するだけ。
module ActionView
  module Helpers
    module FormTagHelper
      alias_method :original_submit_tag, :submit_tag
      def submit_tag(value=nil, options={})
        options[:disable_with] = '処理中...' unless options[:disable_with]
        original_submit_tag(value, options)
      end
    end
  end
end

これで全ての submit は二度押し禁止になりました。
しかし、submit でダウンロードさせた場合、レスポンスがファイルにいってしまうので disabled のまま画面は止まってしまいます。
そんなときは、f.submit :disabl_with => '' のように空文字を渡すだけで大丈夫。

2011/04/27

devise でカスタム認証ロジックを組み込む

たとえば、退職したユーザにはログインさせたくないけど、表示等の関係でユーザ情報を削除できない場合、users テーブルに can_login 列などを作って、フラグ管理しますよね。
んで can_login 列の値でログインを制御したい場合、どう組み込めばいいかなーと悩んでたんですがとりあえずできたのでエントリにしてみます。
devise やコアとなってる warden にそんな機能はありそうなもんだけど、よくわからなかったので自前で仕込みました。
で、認証を行っているメソッドに追加処理をつっこんでやればいいわけです。
devise の lib/devise/controllers/helpers.rb を見ると、authenticate_user! は warden.authenticate! を、current_user は warden.authenticate を呼びだしてる模様。
warden のソースで lib/warden/proxy.rb を見ると、authenticate! も authenticate も _perform_authentication を呼び出しているので、ここで細工してやればよさそう。
んで、最終的には $RAILS_ROOT/config/initializers/extensions/warden_extension.rb を配置して、中身はこんな感じ。
# -*- coding: utf-8 -*-
module Warden
  class Proxy
    alias_method :original_perform_authentication, :_perform_authentication

    private
    def _perform_authentication(*args)
      user, opts = original_perform_authentication(*args)
      # このチェックを入れないとリダイレクトループになる
      unless winning_strategy && winning_strategy.user
        user = nil if user && !user.can_login?
      end
      [user, opts]
    end
  end
end
元メソッド呼び出したあとに、追加でカスタムロジックを追加している。
んで、その内容自体を元メソッドとして上書きってな感じ。
リダイレクトループにさえならなければ、もっとシンプルなソースになったのに、残念だ。

2011/04/10

eclipse galileo にPyDev をインストール

どうせ GAE やるなら Java よりも Python よね。
Java の方が慣れているし、コーディングしやすいんだけど、せっかくの勉強ということで。

というわけで、PyDev を Eclipse にインストールした。
まずは Eclipse を立ち上げて、[ヘルプ] > [新規ソフトウェアのインストール...]

作業対象に http://pydev.org/updates と入力し、[追加]ボタンで PyDev と名づけて [OK] ボタンで保存(上の状態になる)。

上の [Eclipse PyDev] にチェックを入れて [次へ]

インストールの確認なので何もせず [次へ]

ライセンスの確認。同意しますをチェックして [完了]

Now Installing...

証明書の信頼が必要らしい。とりあえず両方共チェックして [OK]

Eclipse の再起動を促されるので再起動。

パースペクティブに PyDev が登録されているから大丈夫だろう。

2011/04/09

Ubuntu 10.04 に Eclipse をインストール

今更ながら Google App Engine で遊ぶために、Eclipse をインストール。
まずは本体をインストール。面倒なので少々古くても apt で入れる。
% sudo aptitude install eclipse

次に日本語化のために、Merge Doc Projectから、Pleiades 本体をダウンロード。
解凍してインストールした Eclipse に上書き。
% unzip pleiades.zip
% cd pleiades
% sudo cp -R features/* /usr/lib/eclipse/features 
% sudo cp -R plugins/* /usr/lib/eclipse/plugins 

eclipse.ini を編集。(sudo が必要)
最後に下記を追加。
-javaagent:/usr/lib/eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar
(ついでに、-showsplash と org.eclipse.platform をコメントアウトしておけば、起動時の splash を変えられるそうな。)

端末で eclipse -clean とすれば完了。無事に Eclipse が日本語化されました。

2011/03/23

stylesheet_link_tag で media を指定する

Rails3 で作成した画面を印刷すると表示されているように印刷できない。
なんでかなーと調べてみると、デフォルトの stylesheet_link_tag は media="screen" を出力するから、印刷時にスタイルが適応されていない。
media を指定する場合、例えば media="all" で出力したい場合は
<%= stylesheet_link_tag :test, :media => 'all' %>
と指定してやればいいようだ。

2011/02/28

Rails3 で POP 受信したメールを DB に登録する

現在作成しているアプリケーションで、メールを取り込む必要が出てきたので、学習した。
まずは、何も考えず登録するだけのコード。
サンプルでよくある $STDIN.read で取得するデータを POP3 で取得するにはどうすればいいかだけがカギかな。
class MailClient
  Net::POP3.start('mail_server', 110, 'account', 'password') do |pop|
    pop.mails.each |m|
      UserMailer.receive(m.pop)
      m.delete
    end
  end
end

ところが今回の環境では、サーバからメールを削除できないという条件があった。
(正確には別の手段で10日以上たったメールを削除している)
このため、差分受信を自前で実装しなければならない。
すでに受信したメールを判別するには、一意なキーが必要だけど候補は二つ。
Net::POPMail#unique_id か Mail#message_id でも、Mail オブジェクトを作るためには、m.pop でメールを受信しなければならない。(pop を呼び出す前までは、LIST コマンドで取得した数値だけ持っている)
というわけで、Net::POPMail#unique_id で判別したい。すなわち、unique_id を DB に登録した、
しかし、ActionMailer#receive メソッドで自動的に Mail オブジェクトにパースされると、この unique_id の値がどっかに行くようだ。
なので、引数が一つしか渡せない ActionMailer#receive では unique_id を DB に登録するのは無理だ。
というわけで、ActionMailer#receive を使うのは諦めるしかない。
結局、下記のようなコードになった。

class MailClient
  Net::POP3.start('mail_server', 110, 'account', 'password') do |pop|
    registered_unique_id_list = UserMail.select(:unique_id).all.collect { |um| um.unique_id }
    pop.mails.each |m|
      unless registered_unique_id_list.include?(m.unique_id)
        mail = Mail.new(m.pop)
        save_mail(mail, m.unique_id)
      end
    end
  end
end
とりあえずは、これで動いている。
でも、そもそも差分受信のロジックを要勉強な気がする。
どっかのメーラのソースでも読むべきかな