Forms are everywhere on the web. I am writing this post in a form, you will comment in the form below, and adding this post to your rss reader will require a form. The problem is, spammers love forms. The silver bullet for their attacks is the unchecked form. Here I will example one method I think will keep our application clean moving forward with multiple Recaptcha validated forms.

Ruby on Rails form helpers and validators make it really easy to create forms quickly, and in the past there seemed to be a lot people writing about it. Lately, there is a lack of clean examples for enterprise applications with the Recaptcha validator.

1) Create a form and a Recaptcha view


  <%= form_for @partner, :url => '/users/partnership_request' do |f| %>
    <% if @partner.errors.any? %>
      <% @partner.errors.full_messages.each do |msg| %>
        <%= msg %>
      <% end %>
    <% end %>
    <%= t('.contact_information') %>
    <%= f.label :name, t('.name') %>
    <%= f.text_field :name %>
    <%= f.label :email, t('.email') %>
    <%= f.text_field :email %>
    <%= f.label :phone, t('.phone') %>
    <%= f.text_field :phone %>
      var RecaptchaOptions = {theme:'clean'};
    <%= render '/shared/recaptcha' %>
    <%= f.submit t('.submit') %>
  <% end %>

In the above example I am using the i18t internationalization methods. This is not required although a good habit for your application.

  <%= f.label :name, t('.name') %>


<%= javascript_include_tag "" %>
  <iframe src="" height="300" width="500" frameborder="0"></iframe>
  <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
  <input type="hidden" name="recaptcha_response_field" value="manual_challenge">

2) Create a Recaptcha account

Goto and create an account, register your domain and save the private and public key information Recaptcha provides.

3) Create base Recaptcha and Validator models

In your app/models directory of your application create a file named recaptcha.rb and validator.rb


class Validator
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend  ActiveModel::Naming
  def initialize(attributes = {})  
    attributes.each do |name, value|  
      send("#{name}=", value)  
  def persisted?  


class Recaptcha
  def self.verify(args)
    url = ''
    query_parts = []
    query_parts << "privatekey=#{RECAPTCHA_PRIVATE_KEY}"
    query_parts << "remoteip=#{args[:ip_address]}"
    query_parts << "challenge=#{args[:recaptcha_challenge_field]}"
    query_parts << "response=#{args[:recaptcha_response_field]}".gsub(' ', '+')
      response = Curl::Easy.http_get("#{url}?#{query_parts.join('&')}") do |curl|
        curl.connect_timeout = 2
        curl.timeout = 5
    rescue Exception
      return false
    result = response.body_str.split("\n")
    return result[0]

4) Use a namespaced directory to store all specific model validations.

This will keep the application looking clean and readable for new developers and remind us where to put new validation when they are required.


class Validator::RecaptchaValidator < Validator
  def validate(record)
    unless record.recaptcha == 'true'
      record.errors[:base] << I18n.t('activerecord.errors.messages.recaptcha')


class Validator::PartnerValidator < Validator
  validates_with Validator::RecaptchaValidator
    :presence => true

I like this setup for validators. It keeps the validators in one place and leaves the base classes untouched in the future. Our app should be set up to handle the spammers and future Rails programmers.

5 thoughts on “Ruby on Rails 3.1 Recaptcha Form with Validation

  1. There seem to be some typos in this post.


    Should probably contain the public, not private, key I believe.

  2. Brett says:

    Thanks Valerie. I updated the post – really updated this time :P

  3. Still have the private key in the top URL for the javascript_include_tag.

  4. Linds says:

    Successfully installed gems but tried can’t find this page. app/views/shared/recaptcha.html.erb

    or do I have to create one?

  5. Brett says:

    Yes. You can create any missing files with the code provided and it should work. :) Please note this post is rather outdated, so things might be a little different today.

Leave a Reply

Your email address will not be published. Required fields are marked *