dalli_captcha 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +127 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/dalli_captcha/application.js +15 -0
  5. data/app/assets/stylesheets/dalli_captcha/application.css +13 -0
  6. data/app/controllers/dalli_captcha/application_controller.rb +4 -0
  7. data/app/controllers/dalli_captcha/captcha_controller.rb +13 -0
  8. data/app/helpers/dalli_captcha/application_helper.rb +4 -0
  9. data/app/views/layouts/dalli_captcha/application.html.erb +14 -0
  10. data/config/routes.rb +3 -0
  11. data/lib/dalli_captcha/controller.rb +14 -0
  12. data/lib/dalli_captcha/engine.rb +5 -0
  13. data/lib/dalli_captcha/image.rb +94 -0
  14. data/lib/dalli_captcha/key_handler.rb +41 -0
  15. data/lib/dalli_captcha/model.rb +45 -0
  16. data/lib/dalli_captcha/version.rb +3 -0
  17. data/lib/dalli_captcha.rb +120 -0
  18. data/lib/generators/install_generator.rb +21 -0
  19. data/lib/generators/templates/README +29 -0
  20. data/lib/generators/templates/dalli_captcha.rb +73 -0
  21. data/lib/tasks/dalli_captcha_tasks.rake +4 -0
  22. data/test/dalli_captcha_test.rb +7 -0
  23. data/test/dummy/README.rdoc +261 -0
  24. data/test/dummy/Rakefile +7 -0
  25. data/test/dummy/app/assets/javascripts/application.js +15 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  27. data/test/dummy/app/controllers/application_controller.rb +3 -0
  28. data/test/dummy/app/helpers/application_helper.rb +2 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/test/dummy/config/application.rb +64 -0
  31. data/test/dummy/config/boot.rb +10 -0
  32. data/test/dummy/config/database.yml +25 -0
  33. data/test/dummy/config/environment.rb +5 -0
  34. data/test/dummy/config/environments/development.rb +37 -0
  35. data/test/dummy/config/environments/production.rb +67 -0
  36. data/test/dummy/config/environments/test.rb +37 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/inflections.rb +15 -0
  39. data/test/dummy/config/initializers/mime_types.rb +5 -0
  40. data/test/dummy/config/initializers/secret_token.rb +7 -0
  41. data/test/dummy/config/initializers/session_store.rb +8 -0
  42. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/test/dummy/config/locales/en.yml +5 -0
  44. data/test/dummy/config/routes.rb +4 -0
  45. data/test/dummy/config.ru +4 -0
  46. data/test/dummy/public/404.html +26 -0
  47. data/test/dummy/public/422.html +26 -0
  48. data/test/dummy/public/500.html +25 -0
  49. data/test/dummy/public/favicon.ico +0 -0
  50. data/test/dummy/script/rails +6 -0
  51. data/test/integration/navigation_test.rb +10 -0
  52. data/test/test_helper.rb +15 -0
  53. metadata +192 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ dalli_captcha
2
+ =============
3
+
4
+ DalliCaptcha is a captcha engine base on Dalli for Rails 3.2. It provides simple captcha that can be read by human.
5
+
6
+
7
+ ## Features
8
+
9
+ * DalliCaptcha is easy to setup, easy to use.
10
+ * DalliCaptcha provides simple captcha that can be read by human.
11
+ * You don't have to manage image files because they will be removed by Tempfile.
12
+ * You can set option `locked_times` and `locked_time` to avoid someone who want to attack.
13
+ * The Captcha will be Expired automatically by setting `expired_time`.
14
+ * ......
15
+
16
+
17
+ ## Requirements
18
+
19
+ * Ruby >= 1.9
20
+ * Rails > 3.2
21
+ * Dalli > 2.4
22
+ * ImageMagick
23
+
24
+ *I haven't tried lower version yet.*
25
+
26
+ ## Getting started
27
+
28
+ **1. Add DalliCaptcha to your gemfile**
29
+
30
+ ```ruby
31
+ gem 'dalli_captcha'
32
+ or
33
+ gem 'dalli_captcha',:git =>"git://github.com/javyliu/dalli_captcha.git"
34
+ ```
35
+
36
+ **2. Generate initializer**
37
+
38
+ rails g dalli_captcha:install
39
+
40
+ It'll generate `config/initializers/dalli_captcha.rb`, and you can configure all options here.
41
+
42
+ **3. Mount DalliCaptcha::Engine in your router.rb**
43
+
44
+ ```ruby
45
+ mount DalliCaptcha::Engine => '/captcha', :as => :captcha
46
+ ```
47
+
48
+
49
+ **4. Add before_filter to your controller**
50
+
51
+ ```ruby
52
+ class PostsController < ApplicationController
53
+ before_filter :generate_captcha_key, :only => [:new, :edit]
54
+ ......
55
+ end
56
+ ```
57
+
58
+ **4. Add module to your model**
59
+
60
+ ```ruby
61
+ class Post < ActiveRecord::Base
62
+ include DalliCaptcha::Model
63
+ end
64
+ ```
65
+
66
+ if you set `config.active_record.whitelist_attributes = true` in `config/application.rb`, remember to add attr_accessible to your model:
67
+
68
+ ```ruby
69
+ attr_accessible :captcha, :captcha_key
70
+ ```
71
+
72
+ to your model.
73
+
74
+ **5. Use DalliCaptcha in your view**
75
+
76
+ ```erb
77
+ <%= image_tag(captcha_path(:timestamp => Time.now.to_i), :alt => "captcha") %>
78
+ <%= f.text_field :captcha %>
79
+ <%= f.hidden_field :captcha_key, :value => session[:captcha_key] %>
80
+ ```
81
+
82
+ **6. Use DalliCaptcha in your controller**
83
+
84
+ ```ruby
85
+ # Initialize a post
86
+ @post = Post.new(:title => "test")
87
+
88
+ # valid with captcha
89
+ @post.valid?
90
+
91
+ # save with captcha
92
+ @post.save
93
+
94
+ # valid without captcha
95
+ @post.valid_without_captcha?
96
+
97
+ # save without captcha
98
+ @post.save_without_captcha
99
+ ```
100
+
101
+
102
+ Enjoy it!
103
+
104
+
105
+ ## Todo list
106
+
107
+ * To add a simple site
108
+ * To write tests
109
+
110
+
111
+ ## History
112
+
113
+ **0.0.1**
114
+
115
+ First Version
116
+
117
+
118
+ ## Author
119
+
120
+ * javyquan@gmail.com
121
+ * http://hi.baidu.com/javyquan
122
+
123
+
124
+ ## License
125
+
126
+ This project rocks and uses MIT-LICENSE.
127
+
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'DalliCaptcha'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,4 @@
1
+ module DalliCaptcha
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module DalliCaptcha
2
+ class CaptchaController < ApplicationController
3
+ def show
4
+ captcha = DalliCaptcha::Image.new(session[:captcha_key])
5
+ tempfile = captcha.generate
6
+ if tempfile
7
+ send_file(tempfile.path, :type => captcha.content_type, :disposition => 'inline', :filename => captcha.filename)
8
+ else
9
+ render :nothing => true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module DalliCaptcha
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>DalliCaptcha</title>
5
+ <%= stylesheet_link_tag "dalli_captcha/application", :media => "all" %>
6
+ <%= javascript_include_tag "dalli_captcha/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ DalliCaptcha::Engine.routes.draw do
2
+ root :to => 'captcha#show'
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'uuid'
2
+
3
+ module DalliCaptcha
4
+ module Controller
5
+ def generate_captcha_key
6
+ if session[:captcha_key].blank?
7
+ uuid = UUID.new
8
+ session[:captcha_key] = uuid.generate.gsub("-", "").hex
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ ActionController::Base.send :include, DalliCaptcha::Controller
@@ -0,0 +1,5 @@
1
+ module DalliCaptcha
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DalliCaptcha
4
+ end
5
+ end
@@ -0,0 +1,94 @@
1
+ module DalliCaptcha
2
+ class Image
3
+ #user the session[:captcha_key] to set the memcache key
4
+ def initialize(captcha_key)
5
+ @key_handler = DalliCaptcha::KeyHandler.new(captcha_key)
6
+ @options = DalliCaptcha.options
7
+ end
8
+
9
+ def generate
10
+ #unless @key_handler.locked?
11
+ @string = get_string
12
+ @key_handler.set(@string)
13
+
14
+ tempfile = Tempfile.new([@options[:tempfile_name], @options[:tempfile_type]], @options[:tempfile_path])
15
+
16
+ params = ""
17
+ params << get_line_param
18
+ params << get_image_size_param
19
+ params << get_background_param
20
+ params << get_string_param
21
+ params << get_swirl_param
22
+
23
+ command = `#{@options[:image_magick_path]}convert #{params} #{tempfile.path}`
24
+ puts "#{@options[:image_magick_path]}convert #{params} #{tempfile.path}"
25
+
26
+ tempfile
27
+ #else
28
+ #nil
29
+ #end
30
+ end
31
+
32
+ def filename
33
+ "#{@options[:tempfile_name]}#{@options[:tempfile_type]}"
34
+ end
35
+
36
+ def content_type
37
+ @options[:content_type]
38
+ end
39
+
40
+ protected
41
+
42
+ def get_string
43
+ chars = @options[:chars]
44
+ max = @options[:string_length][:max]
45
+ min = @options[:string_length][:min]
46
+ range = max - min
47
+ length = ((range > 0) ? rand(range + 1) : 0) + min
48
+
49
+ string = ""
50
+ length.times { string << chars[rand(chars.length)] }
51
+
52
+ string
53
+ end
54
+
55
+ def get_line_param
56
+ max = @options[:line][:max]
57
+ min = @options[:line][:min]
58
+ range = max - min
59
+ amount = ((range > 0) ? rand(range + 1) : 0) + min
60
+
61
+ params = ""
62
+
63
+ amount.times do
64
+ params << "-fill #{@options[:line_color]} "
65
+ params << "-draw \"line #{rand(10)},#{rand(@options[:height])} #{rand(10) + @options[:width] - 10},#{rand(@options[:height])}\" "
66
+ end
67
+
68
+ params
69
+ end
70
+
71
+ def get_image_size_param
72
+ "-size #{@options[:width]}X#{@options[:height]} "
73
+ end
74
+
75
+ def get_background_param
76
+ "-background #{@options[:background]} "
77
+ end
78
+
79
+ def get_string_param
80
+ "-gravity center -fill #{@options[:font_color]} label:\"#{@string}\" "
81
+ end
82
+
83
+ def get_swirl_param
84
+ max = @options[:swirl_range][:max]
85
+ min = @options[:swirl_range][:min]
86
+
87
+ "-swirl #{(min..max).to_a.sample} "
88
+ end
89
+
90
+ def get_charcoal
91
+ rand(3)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,41 @@
1
+ module DalliCaptcha
2
+ class KeyHandler
3
+ def initialize(captcha_key)
4
+ @dalli = DalliCaptcha.dalli
5
+ @captcha_key = captcha_key
6
+ @options = DalliCaptcha.options
7
+
8
+ @string_key = "#{@options[:dalli_scope]}:#{@captcha_key}:string"
9
+ @locked_times_key = "#{@options[:dalli_scope]}:#{@captcha_key}:locked_times"
10
+ end
11
+
12
+ #set string in memcache
13
+ def set(string)
14
+ string = @options[:case_sensitive] ? string : string.downcase
15
+ @dalli.set(@string_key,string,@options[:expired_time])
16
+
17
+ #locked_times = @dalli.get(@locked_times_key).to_i
18
+ #@dalli.incrby(@locked_times_key, 1)
19
+ #@dalli.expire(@locked_times_key, @options[:locked_time]) if locked_times == 0
20
+ end
21
+
22
+ def delete
23
+ @dalli.delete(@string_key)
24
+ #@dalli.del(@locked_times_key)
25
+ end
26
+
27
+ def locked?
28
+ locked_times = @dalli.get(@locked_times_key).to_i
29
+ @captcha_key.blank? || (locked_times >= @options[:locked_times])
30
+ end
31
+
32
+ def valid?(captcha)
33
+ string = @dalli.get(@string_key)
34
+ if captcha.blank? || string.blank?
35
+ return false
36
+ else
37
+ string == (@options[:case_sensitive] ? captcha : captcha.downcase)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ module DalliCaptcha
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.send :validate, :check_captcha
8
+ base.send :after_save, :delete_captcha
9
+ base.send :attr_accessor, :captcha, :captcha_key
10
+ end
11
+
12
+ module ClassMethods
13
+ end
14
+
15
+ def key_handler
16
+ @key_handler ||= DalliCaptcha::KeyHandler.new(captcha_key)
17
+ end
18
+
19
+ def check_captcha
20
+ if without_captcha?
21
+ @skip_captcha = false
22
+ else
23
+ errors.add(:captcha, DalliCaptcha::options[:error_message]) unless key_handler.valid?(captcha)
24
+ end
25
+ end
26
+
27
+ def delete_captcha
28
+ key_handler.delete
29
+ end
30
+
31
+ def without_captcha?
32
+ !!@skip_captcha
33
+ end
34
+
35
+ def valid_without_captcha?
36
+ @skip_captcha = true
37
+ self.valid?
38
+ end
39
+
40
+ def save_without_captcha
41
+ @skip_captcha = true
42
+ self.save
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module DalliCaptcha
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,120 @@
1
+ require 'dalli'
2
+ require "dalli_captcha/engine"
3
+ require 'dalli_captcha/controller'
4
+ require 'generators/install_generator'
5
+
6
+ module DalliCaptcha
7
+
8
+ autoload :Image, "dalli_captcha/image"
9
+ autoload :KeyHandler, "dalli_captcha/key_handler"
10
+ autoload :Model, "dalli_captcha/model"
11
+
12
+ mattr_accessor :dalli_config
13
+ @@dalli_config = {
14
+ :host => "localhost:11211",
15
+ :compress => true,
16
+ :namespace => "captcha"
17
+ }
18
+
19
+ mattr_accessor :dalli_scope
20
+ @@dalli_scope = "DalliCaptcha"
21
+
22
+ mattr_accessor :expired_time
23
+ @@expired_time = 1800
24
+
25
+ mattr_accessor :locked_times
26
+ @@locked_times = 30
27
+
28
+ mattr_accessor :locked_time
29
+ @@locked_time = 600
30
+
31
+ mattr_accessor :image_magick_path
32
+ @@image_magick_path = ""
33
+
34
+ mattr_accessor :tempfile_path
35
+ @@tempfile_path = "/tmp"
36
+
37
+ mattr_accessor :tempfile_name
38
+ @@tempfile_name = "dalli_captcha"
39
+
40
+ mattr_accessor :tempfile_type
41
+ @@tempfile_type = ".png"
42
+
43
+ mattr_accessor :content_type
44
+ @@content_type = "image/png"
45
+
46
+ mattr_accessor :width
47
+ @@width = 120
48
+
49
+ mattr_accessor :height
50
+ @@height = 30
51
+
52
+ mattr_accessor :chars
53
+ @@chars = %w(2 3 4 5 6 7 9 a b c d e f g h j k m n p q r s t w x y z A C D E F G H J K L M N P Q R S T X Y Z)
54
+
55
+ mattr_accessor :string_length
56
+ @@string_length = {
57
+ :max => 6,
58
+ :min => 4
59
+ }
60
+
61
+ mattr_accessor :font_color
62
+ @@font_color = "gray"
63
+
64
+ mattr_accessor :background
65
+ @@background = "white"
66
+
67
+ mattr_accessor :line
68
+ @@line = {
69
+ :max => 4,
70
+ :min => 2
71
+ }
72
+
73
+ mattr_accessor :line_color
74
+ @@line_color = "gray"
75
+
76
+ mattr_accessor :swirl_range
77
+ @@swirl_range = {
78
+ :max => 20,
79
+ :min => -20
80
+ }
81
+
82
+ mattr_accessor :case_sensitive
83
+ @@case_sensitive = false
84
+
85
+ mattr_accessor :error_message
86
+ @@error_message = "is invalid"
87
+
88
+ def self.setup
89
+ yield self
90
+ end
91
+
92
+ def self.dalli
93
+ @@dalli ||= Dalli::Client.new(dalli_config.delete(:host),dalli_config)
94
+ end
95
+
96
+ def self.options
97
+ @@options ||= {
98
+ :dalli_scope => dalli_scope,
99
+ :expired_time => expired_time,
100
+ :locked_times => locked_times,
101
+ :locked_time => locked_time,
102
+ :image_magick_path => image_magick_path,
103
+ :tempfile_path => tempfile_path,
104
+ :tempfile_name => tempfile_name,
105
+ :tempfile_type => tempfile_type,
106
+ :content_type => content_type,
107
+ :width => width,
108
+ :height => height,
109
+ :chars => chars,
110
+ :string_length => string_length,
111
+ :font_color => font_color,
112
+ :background => background,
113
+ :line => line,
114
+ :line_color => line_color,
115
+ :swirl_range => swirl_range,
116
+ :case_sensitive => case_sensitive,
117
+ :error_message => error_message
118
+ }
119
+ end
120
+ end
@@ -0,0 +1,21 @@
1
+
2
+ require 'rails/generators'
3
+
4
+ module DalliCaptcha
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ desc "Copy DalliCaptcha default files"
10
+
11
+ def copy_initializer
12
+ copy_file "redis_captcha.rb", "config/initializers/redis_captcha.rb"
13
+ end
14
+
15
+ def show_readme
16
+ readme "README"
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ ===============================================================================
2
+
3
+ 1. Setup "config/initializers/dalli_captcha.rb"
4
+
5
+ 2. Mount RedisCaptcha::Engine in your router.rb
6
+
7
+ mount RedisCaptcha::Engine => '/captcha', :as => :captcha
8
+
9
+ 3. Add to your controller
10
+
11
+ before_filter :generate_captcha_key
12
+
13
+ 4. Add to your model
14
+
15
+ include RedisCaptcha::Model
16
+
17
+ 5. If you set "config.active_record.whitelist_attributes = true" in "config/application.rb", remember to add attr_accessible to your model
18
+
19
+ attr_accessible :captcha, :captcha_key
20
+
21
+ 6. Add to your view
22
+
23
+ <%= image_tag(captcha_path(:timestamp => Time.now.to_i), :alt => "captcha") %>
24
+ <%= f.text_field :captcha %>
25
+ <%= f.hidden_field :captcha_key, :value => session[:captcha_key] %>
26
+
27
+ Enjoy it :)
28
+
29
+ ===============================================================================