Rails3でCAPTCHAを使う〜難読画像による入力認証

フォームなどでスパムを防止するための画像表示。
タイトルでは「難読画像による入力認証」と書いたアレです。
captcha」と言うらしいのですが、Rails3で使う方法を調べましたのでまとめてみます。
レッツ・ハンズオン!

1,reCAPTCHAのアカウント作成

reCAPTCHA

こちらのサービスで画像の生成などをしてくれるようです。これを使えばRMagickなどのライブラリを使わないで認証ができるため、今回はこちらを使ってみます。

ドメインを持っていないとアカウントは取得できないもののようです。
もし、ドメインを持っていなければ、無料でドメインを発行してくれるところがありますので、
そちらで取得してみて下さい。(例えば、cz.ccとか。)

ドメインを登録して、global-keyとしてアカウントを取得しました。
これでお勉強に使えます。本番はドメイン限定した方が良いかもしれません。

画面には

Domain Name:   XXXXXXXXXXXXX
This is a global key. It will work across all domains.
Public Key: -----------------------------------------------------------------------
Use this in the JavaScript code that is served to your users
Private Key: ----------------------------------------------------------------------
Use this when communicating between your server and our server. Be sure to keep it a secret.

と表示されていると思います。
2つのキーをコピペして保存しておいて下さい。後ほどアプリに埋め込みます。

2,プラグイン導入&サンプルプロジェクトの準備

Railsではプラグインを使って、このサービスを利用してみます。
プラグインはこちら。
http://ambethia.com/recaptcha/

ダウンロード回数など見た感じではこれがデファクトな印象でした。

まずは、プラグインのドキュメントを読みます。
http://ambethia.com/recaptcha/rdoc/index.html:titel

  • Keyを保存する方法がいくつかあるので、お好きにどうぞ
  • recaptcha_tagsでヤツが表示される。
  • verify_recaptchaでチェックができる。

という感じ。
Ajaxを使ってその場でチェックなど色々とできるようです。今回は一番オーソドックスな方法でサンプルアプリを作ってみます。

以下、アプリ作成のコマンド。私はRails3.0.5/Ruby1.9.2でやっています。

rails new test_recaptcha
cd test_recaptcha
rails plugin install git://github.com/ambethia/recaptcha.git 

次にファイルの新規作成

config/initializers/recaotcha.rb

Recaptcha.configure do |config|
    config.public_key  = '----------------------------------------'
    config.private_key = '----------------------------------------'
end

上記のハイフン連打のところに、1で保存したキーを書いて下さい。

では、Scaffoldでスカッとモデル・ビュー・コントローラーを作ります。

rails g scaffold Users name:string email:string 
rake db:migrate
rails s

最後に、ここで一度動作確認。

http://localhost:3000/users

で、一人登録してみて動作するか確認して下さい。

3,プラグインの利用例

では、プラグインを使ってみましょう。

  • newのときだけ表示させて、入力チェック
  • editは普通のフォーム
  • エラー時はRailsの標準的なエラー表示を利用する

という要件でテストしてみます。


Rails3からは、form部分が切り出されているので部分テンプレートを調整。

app/view/users/_form.html.erb

<% if captcha %>
  <div class="field">
    <%= recaptcha_tags %>
 </div>
<% end %>

をsubmitの上辺りに加えました。
local変数のcaptchaにtrueが設定されたら表示、falseが設定されたら非表示とする感じです。


次に、呼び出し元の調整。以下のように書き換えます。

app/view/users/new.html.erb

変更前: <%= render 'form' %>
変更後: <%= render :partial =>'form', :locals => {:captcha => true} %>
app/view/users/edit.html.erb

変更前: <%= render 'form' %>
変更後:<%= render :partial => 'form', :locals => {:captcha => false }%>


最後に、newのフォームのフォーム受け取り口になるコントローラーに修正を加えます。
「app/controllers/users_controller.rb」の中のcreate内にあるif文を以下のように書き換えます。

app/controllers/users_controller.rb

変更前:
 if @user.save

変更後:
 if verify_recaptcha(:model => @user, :message => 'reCAPTCHA error.') && @user.save


これでnewの画面ではreCAPTCHAが表示され、フォームの受け取り口になるcreateにて入力チェックが行われることになりました。入力が間違っている場合は、:messageに書かれているメッセージが使われ元のnewの画面がエラーメッセージと共に表示されます。

4,ちょっとTips :部分テンプレートについて

Rails3のScaffoldで生成されるnewとeditのテンプレートには、フォームを表示する「部分テンプレート」の呼び出しが標準で書かれています。

<%= render 'form' %>

という部分です。これが書かかれると、そのviewと同じフォルダにある「_form.html.erb」が呼ばれます。
もし、呼び出し元から他のテンプレートの中で使われる変数へ値を渡したいときは

<%= render :partial=> "form", :locals => { :変数 => 値 } %>

と書きます。
呼び出されるテンプレートでは変数(@を付けないローカル変数)で値を取り出せます。

Layouts and Rendering in Rails — Ruby on Rails Guides
こちらの「3.4.3 Partial Layouts」「3.4.4 Passing Local Variables」に説明が書かれています。

5、もうちょっと何とか出来ないものか。

さて、ここまでで表示はされているものの、わざわざ呼ぶか呼ばないかをローカル変数を設定して挙動を変えるのも何ともダサいなと思ったわけです。

そこで、気になったのがsubmitボタン。newとeditでSubmitボタンの名前が自動で変わっています。新規にサンプルでプロジェクトをつくり、適当なモデルをScaffoldで作ってみて下さい。これは、どうやっているのだろうか。。。


以下、探索した流れです。

  • http://railsapi.com/doc/rails-v3.0.4_ruby-v1.9.2/でform_forを調べる。
  • どうやらFormbuilderというのがあるらしい。
  • Formbuilderのsubmitの項を読むと、i18nで設定をするとちゃんと変わるということが解る。
  • さらにGitHubへのリンクを押してソースを見てみる。
  • ソースを見ると、設定がされてない場合は「submit_default_value」が入るようになっている。
  • privateメソッドに「submit_default_value」発見。
object.persisted? ? :update : :create

と書いて判定している。。。persisted?って何だ?

  • persisted?を検索してみると、ActiveResource::Baseにあった。

Returns true if this object has been saved, otherwise returns false.

  • 同じくActiveRecord::Persistanceにも記述があった。

フォームにてpersisted?を使うと、既にレコードとして存在しているのか、新規なのかを判定できることが解りました。
そして、Formbuilderでは新規の場合はcreate、既存の場合はupdateに切り替わり、国際化対応のファイルでボタン名を設定すると切り替わるようになっていることも解りました。

ということで、この調査を生かし、以下のようにリファクタリングしてみました。

edit.html.erbとnew.html.erbは元に戻して

<%= render 'form' %>

にします。ローカル変数への宣言が無くなりすっきり。

次に、

app/views/users/_form.html.erb

<% unless @user.persisted? %>
  <div class="field">
    <%= recaptcha_tags %>
 </div>
<% end %>

にします。

controllerのメソッドへの作業はそのまま。
これで、新規(new)の時にreCAPCHEが表示され、createの時だけチェックが行われることになりました。

以上!