A simple negative captcha for Rails.

A negative captcha flips the normal captcha on its head, rather than asking humans identify themselves, we trick the bots into identifying themselves. We do this by placing honey pots in a form that are invisible to a human, but visible to a bot. When the bot submits the form, we look for the honey pot entries and discard the form submission if we find any.

The source is MIT licensed and available on github.

Installing Bouncy Bots!

Bouncy Bots! is available as a gem from github. Install the gem with the following command:

% sudo gem install --source http://gems.github.com/ leshill-bouncy_bots

After you have the gem installed, add a config.gem line to your environment.rb file:

1 config.gem 'leshill-bouncy_bots', :lib => 'bouncy_bots', :version => 0.1.0

Using Bouncy Bots!

Controllers

Use the bounce_bots macro in your controllers to detect and bounce bots. The macro takes two parameters, the honey pot field name and the redirect path or url. For example, to check for the field :blog_url and redirect bots to the pages_path :

1 bounce_bots :blog_url, :pages_path

You can also pass the standard controller filter options such as :only or :except :

1 bounce_bots :blog_url, :pages_path, :only => [:create, :update]

Views

In your form views, add the honey pot field. If you are using form_tag, you can use any form element, for example (using haml):

1 = text_field_tag :blog_url, nil, :class => 'long_required'

In your stylesheet, add a rule to ‘hide’ the field:

1 input.long_required { display:none; }

If you use form_for, there are two helpers to simplify making the honey pot.

1 = f.bounce_label 'Blog Url', :class => 'long_required'
2 %br
3 = f.bounce_field :class => 'long_required'

And that’s it.

will_paginate using ul and li tags

will_paginate is the defacto standard plugin for the Rails community. Almost every app will need pagination and will_paginate does almost everything you need.

The one thing will_paginate does not do is generate semantic pagination markup. Semantic markup uses standard HTML tags to convey structure and meaning. For example using an h1 tag for a heading rather than styling a div tag to do the same.

The default will_paginate pagination is a series of anchors and spans strung together. As many a designer has pointed out, that series is really a list of pagination controls. If you are working with such a designer or you are writing semantic markup, try out my fork and enjoy will_paginate with semantic pagination.

How to install

As of this moment, you can install it as a plugin. I am considering releasing this as a gem; ideally the patch to will_paginate would be accepted (see below).

%script/plugin install git://github.com/leshill/will_paginate.git

How to use

Use the will_paginate view helper, passing the :semantic => true option. If you do not want the enclosing ul tag, pass :container => false.

<%= will_paginate @pages, :semantic => true %>

Which looks like:

 1 <ul class="pagination">
 2   <li class="disabled prev_page">&laquo; Previous</li>
 3   <li class="current">1</li>
 4   <li><a href="/pages?page=2" rel="next">2</a></li>
 5   <li><a href="/pages?page=3">3</a></li>
 6   <li><a href="/pages?page=4">4</a></li>
 7   <li><a href="/pages?page=5">5</a></li>
 8   <li><a href="/pages?page=6">6</a></li>
 9   <li><a href="/pages?page=7">7</a></li>
10   <li><a href="/pages?page=8">8</a></li>
11   <li><a href="/pages?page=9">9</a></li>
12   <li class="gap">&hellip;</li>
13   <li><a href="/pages?page=33">33</a></li>
14   <li><a href="/pages?page=34">34</a></li>
15   <li class="next_page"><a href="/pages?page=2" class="next_page" rel="next">Next &raquo;</a></li>
16 </ul>

Will this patch be accepted?

Signs point to ‘uncertain’. Mislav has in the past indicated that he was not convinced that list support was necessary in the plugin since you can replace the renderer1. Since semantic markup is commonly used, I have sent a pull request. In any event, the authors of will_paginate have my thanks for creating a very useful plugin.

1 So why the fork? In order to support the containing ul directly, the core of the helper had to be modified. See the change on github .

Ruby and the argument collecting hash syntax

Unlike other languages, ruby does not support keyword parameters. As an alternative, we use ruby’s syntactic shorthand to pass a hash as the final argument to a method using the key => value syntax:

1 some_method(first_arg, :key1 => value1, :key2 => value2)

some_method will receive a hash as its second argument with the specified key/value pairs. Unlike regular arguments, there is no way to provide default values for the individual key/value pairs (you can provide a default for the hash argument though):

1 def some_method(thing, options = {})
2   #...
3 end

There are a few ruby idioms for providing default options to an option hash, one of the most common is available in rails, using the core extension Hash#reverse_merge from ActiveSupport1:

1 def some_method(thing, options = {})
2   options.reverse_merge!({:key1 => 'default'})
3   #...
4 end

If you do not have access to ActiveSupport, you can use ruby’s Hash#merge2 as an alternative:

1 def some_method(thing, options = {})
2   options = {:key1 => 'default'}.merge(options)
3   #...
4 end

If that seems like too much code to be typing all the time, you can open up Hash and add a method. Careful! This is simple enough to get wrong. Here is some code3 from a very useful rails plugin:

1 def default!(defaults = {})
2   defaults.each do |key, value|
3     self[key] = value if self[key].nil?
4   end
5   self
6 end

This code works until you want to set a default value to nil. This can be fixed by replacing the conditional on line three with unless self.has_key?(key). Or you can simply rewrite this in terms of Hash#merge4:

1 def default!(defaults = {})
2   replace(defaults.merge(self))
3 end

1 Which builds on ruby’s Hash#merge.

2 Or merge-bang (merge!).

3 Slightly modified for readability.

4 ActiveSupport does something similar.

We recently needed to show a truncated version of existing HTML content. Although there are several issues1 when dealing with HTML content, our specific concern was maintaining the integrity of the HTML. Some quick googling led to a nice helper written by Henrik Nyh last year. We tweaked the original a bit to append the ellipsis within the tag at the truncation point and truncate at a word (or tag) boundary. Here it is, enjoy.

 1 # By Henrik Nyh <http://henrik.nyh.se> 2008-01-30.
 2 # Free to modify and redistribute with credit.
 3 # Word truncation and fixes by Les Hill <http://blog.leshill.org> 2009-06-02
 4 #
 5 
 6 require "rubygems"
 7 require "hpricot"
 8 
 9 module TextHelper
10 
11   # Like the Rails _truncate_ helper but doesn't break HTML tags or entities.
12   def truncate_html(text, max_length = 30, ellipsis = "...")
13     return if text.nil?
14     doc = Hpricot(text.to_s)
15     doc.inner_text.chars.length > max_length ? doc.truncate(max_length, ellipsis).inner_html : text.to_s
16   end
17 
18   def self.truncate_at_space(text, max_length, ellipsis = '...')
19     l = [max_length - ellipsis.length, 0].max
20     stop = text.rindex(' ', l) || 0
21     (text.length > max_length ? text[0...stop] + ellipsis : text).to_s
22   end
23 end
24 
25 module HpricotTruncator
26   module NodeWithChildren
27     def truncate(max_length, ellipsis)
28       return self if inner_text.chars.length <= max_length
29       truncated_node = dup
30       truncated_node.name = name
31       truncated_node.raw_attributes = raw_attributes
32       truncated_node.children = []
33       each_child do |node|
34         break if max_length <= 0
35         node_length = node.inner_text.chars.length
36         truncated_node.children << node.truncate(max_length, ellipsis)
37         max_length = max_length - node_length
38       end
39       truncated_node
40     end
41   end
42 
43   module TextNode
44     def truncate(max_length, ellipsis)
45       self.content = TextHelper.truncate_at_space(content, max_length, ellipsis)
46       self
47     end
48   end
49 
50   module IgnoredTag
51     def truncate(max_length, ellipsis)
52       self
53     end
54   end
55 end
56 
57 Hpricot::Doc.send(:include,       HpricotTruncator::NodeWithChildren)
58 Hpricot::Elem.send(:include,      HpricotTruncator::NodeWithChildren)
59 Hpricot::Text.send(:include,      HpricotTruncator::TextNode)
60 Hpricot::BogusETag.send(:include, HpricotTruncator::IgnoredTag)
61 Hpricot::Comment.send(:include,   HpricotTruncator::IgnoredTag)

1 For example: preventing XSS attacks, maintaining coherent styling.