RailsでURLのバリデーション
RailsのActiveRecordでは、バリデーションはデータベースへの書き出し、Model#saveのときに走るので、次のような場合に困る。
open-uriを使ってフォームで渡されたURLを開き、このURLにアクセスした情報とフォームから入力された情報を使ってレコードを生成したい。ところが、URLとして渡された不正な文字列、例えばローカルファイルシステムのディレクトリパスだったりすると、openしようとしてしまって困る。ということで、バリデーションはコントローラに書かざるを得ない、ような気がする。
class Item < ActiveRecord::Base belongs_to :user + # let us do the url validation in the contorller end
def create @user = User.find(params[:user_id]) @item = @user.items.create(params[:item]) + + if @item.url !~ /^(#{URI::regexp(%w(http https))})$/ then + flash[:notice] = "Invalid URL!!" + redirect_to hoge_path + return + end + populate @item if @item.save flash[:notice] = "Successfully created an item."
RailsのURLのバリデーションには、URI::regexpという正規表現が使える。URLの正規表現を検索する必要すらなくて、巨人の肩っぽい。以下のように、ちょっと必要以上に厳密で冗長な気がするけど、今の場合、正規表現ごとき長くても構わない。
% irb > require 'uri' => true > URI::regexp("http") => /(?=(?-mix:http):) ([a-zA-Z][-+.a-zA-Z\d]*): (?# 1: scheme) (?: ((?:[-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*) (?# 2: opaque) | (?:(?: \/\/(?: (?:(?:((?:[-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)? (?# 3: userinfo) (?:((?:(?:(?:[a-zA-Z\d](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.)*(?:[a-zA-Z](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port) | ((?:[-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+) (?# 6: registry) ) | (?!\/\/)) (?# XXX: '\/\/' is the mark for hostport) (\/(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)? (?# 7: path) )(?:\?((?:[-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))? (?# 8: query) ) (?:\#((?:[-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))? (?# 9: fragment) /x >