acts_as_textcaptcha 4.3.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,287 +1,258 @@
1
1
  ## ActAsTextcaptcha
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/acts_as_textcaptcha.svg?style=flat)](http://rubygems.org/gems/acts_as_textcaptcha)
4
- [![Travis Build Status](https://travis-ci.org/matthutchinson/acts_as_textcaptcha.svg?branch=master)](https://travis-ci.org/matthutchinson/acts_as_textcaptcha)
5
- [![Maintainability](https://img.shields.io/codeclimate/maintainability/matthutchinson/acts_as_textcaptcha.svg)](https://codeclimate.com/github/matthutchinson/acts_as_textcaptcha/maintainability)
6
- [![Gem Dependency Status](https://gemnasium.com/badges/github.com/matthutchinson/acts_as_textcaptcha.svg)](https://gemnasium.com/github.com/matthutchinson/acts_as_textcaptcha)
7
-
8
- ActsAsTextcaptcha provides spam protection for your Rails models using logic
9
- questions from the excellent [TextCaptcha](http://textcaptcha.com/) web service
10
- (by [Rob Tuley](https://twitter.com/robtuley). It is also possible to configure your
11
- own captcha questions (instead, or as a fallback in the event of any API
12
- issues).
13
-
14
- This gem is actively maintained, has good test coverage and is compatible with
15
- Rails >= 3 (including Rails 4 & 5) and Ruby >= 2.1. If you have issues
16
- please report them
17
- [here](https://github.com/matthutchinson/acts_as_textcaptcha/issues/new).
18
-
19
- Logic questions from the web service are aimed at a child's age of 7, so they
20
- can be solved easily by even the most cognitively impaired users. As they
21
- involve human logic, questions cannot be solved by a robot. There are both
22
- advantages and disadvantages in using logic questions over image based
23
- captchas, find out more at [TextCaptcha](http://textcaptcha.com/).
3
+ [![Gem](https://img.shields.io/gem/v/acts_as_textcaptcha.svg?style=flat)](http://rubygems.org/gems/acts_as_textcaptcha)
4
+ [![Travis](https://img.shields.io/travis/matthutchinson/acts_as_textcaptcha/master.svg?style=flat)](https://travis-ci.org/matthutchinson/acts_as_textcaptcha)
5
+ [![Depfu](https://img.shields.io/depfu/matthutchinson/acts_as_textcaptcha.svg?style=flat)](https://depfu.com/github/matthutchinson/acts_as_textcaptcha)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c67969dd7b921477bdcc/maintainability)](https://codeclimate.com/github/matthutchinson/acts_as_textcaptcha/maintainability)
7
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/c67969dd7b921477bdcc/test_coverage)](https://codeclimate.com/github/matthutchinson/acts_as_textcaptcha/test_coverage)
8
+
9
+ ActsAsTextcaptcha provides spam protection for Rails models with a text-based
10
+ logic question captcha. Questions are fetched from [Rob
11
+ Tuley's](https://twitter.com/robtuley)
12
+ [textcaptcha.com](http://textcaptcha.com/). They can be solved easily by humans
13
+ but are tough for robots to crack.
14
+
15
+ The gem can also be configured with your own questions; as an alternative, or as
16
+ a fallback to handle any API issues. For reasons on why logic based captchas
17
+ are a good idea visit [textcaptcha.com](http://textcaptcha.com).
18
+
19
+ ## Requirements
20
+
21
+ * [Ruby](http://ruby-lang.org/) >= 2.1.0
22
+ * [Rails](http://github.com/rails/rails) >= 3
23
+ * [Rails.cache](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores)
24
24
 
25
25
  ## Demo
26
26
 
27
27
  Try a [working demo here](https://acts-as-textcaptcha-demo.herokuapp.com)!
28
-
29
- **Or** click below to deploy your own example Rails app to Heroku (already
30
- configured with acts_as_textcaptcha). See
31
- [here](https://github.com/matthutchinson/acts_as_textcaptcha_demo) for more
32
- details.
28
+ **Or** one-click deploy your own demo app at Heroku. See
29
+ [here](https://github.com/matthutchinson/acts_as_textcaptcha_demo) for details.
33
30
 
34
31
  [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/matthutchinson/acts_as_textcaptcha_demo/tree/master)
35
32
 
36
- ## Installing
33
+ ## Installation
37
34
 
38
- First add the following to your Gemfile, then `bundle install`;
35
+ Add this line to your Gemfile and run `bundle install`:
39
36
 
40
- gem 'acts_as_textcaptcha'
37
+ ```ruby
38
+ gem 'acts_as_textcaptcha'
39
+ ```
41
40
 
42
- Add the following code to models you would like to protect;
41
+ Add this to models you'd like to protect:
43
42
 
44
- class Comment < ApplicationRecord
45
- # (this is the simplest way to configure the gem)
46
- acts_as_textcaptcha :api_key => 'TEXTCAPTCHA_API_IDENTITY'
47
- end
43
+ ```ruby
44
+ class Comment < ApplicationRecord
45
+ acts_as_textcaptcha api_key: 'TEXTCAPTCHA_API_IDENTITY'
46
+ # see below for more config options
47
+ end
48
+ ```
48
49
 
49
- Your `TEXTCAPTCHA_API_IDENTITY` should be some reference to yourself (e.g. an
50
- email address, domain or similar where if there are problems with your usage you
51
- can be contacted).
50
+ [Rob](https://twitter.com/robtuley) requests that your
51
+ `TEXTCAPTCHA_API_IDENTITY` be some reference to yourself (e.g. an email address,
52
+ domain or similar) so you can be contacted should any usage problem occur.
52
53
 
53
- Next, in your controller's *new* action generate and assign the logic question
54
- for the record, like so;
54
+ In your controller's `new` action call the `textcaptcha` method:
55
55
 
56
- def new
57
- @comment = Comment.new
58
- @comment.textcaptcha
59
- end
56
+ ```ruby
57
+ def new
58
+ @comment = Comment.new
59
+ @comment.textcaptcha  
60
+ end
61
+ ```
60
62
 
61
- Finally, in the view add the textcaptcha question and answer fields to your form
62
- using the `textcaptcha_fields` helper. Feel free to arrange the HTML within this
63
- block as you like;
63
+ Finally add the question and answer fields to your form using the
64
+ `textcaptcha_fields` helper. Arrange the HTML within this block as you like.
64
65
 
65
- <%= textcaptcha_fields(f) do %>
66
- <div class="field">
67
- <%= f.label :textcaptcha_answer, @comment.textcaptcha_question %><br/>
68
- <%= f.text_field :textcaptcha_answer, :value => '' %>
69
- </div>
70
- <% end %>
66
+ ```ruby
67
+ <%= textcaptcha_fields(f) do %>
68
+ <div class="field">
69
+ <%= f.label :textcaptcha_answer, @comment.textcaptcha_question %><br/>
70
+ <%= f.text_field :textcaptcha_answer, :value => '' %>
71
+ </div>
72
+ <% end %>
73
+ ```
71
74
 
72
- *NOTE:* If you'd rather NOT use this helper and prefer to write your own form
73
- elements, take a look at the HTML produced from this helper method
75
+ If you'd prefer to construct your own form elements, take a look at the HTML
76
+ produced
74
77
  [here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_helper.rb).
75
78
 
76
- *NOTE:* The defaults for [cache
77
- configuration](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores)
78
- changed with Rails 5 and this gem **requires** a working Rails.cache store to
79
- exist.
79
+ ## Configuration
80
80
 
81
- *NOTE:* These installation steps changed with v4.0.0 of this gem. If you are
82
- having problems please refer to the 3.0 [upgrade
83
- guide](https://github.com/matthutchinson/acts_as_textcaptcha/wiki/Upgrading-from-3.0.10).
81
+ The following options are available (only `api_key` is required):
84
82
 
85
- ### Toggling TextCaptcha
83
+ * **api_key** (_required_) - a reference to yourself (e.g. your email or domain).
84
+ * **questions** (_optional_) - array of your own questions and answers (see below).
85
+ * **cache_expiry_minutes** (_optional_) - how long valid answers are cached for (default 10 minutes).
86
+ * **raise_errors** (_optional_) - if true, API or network errors are raised (default false, errors logged).
87
+ * **api_endpoint** (_optional_) - set your own JSON API endpoint to fetch from (see below).
86
88
 
87
- You can toggle textcaptcha on/off for your models by overriding the
88
- `perform_textcaptcha?` method. If it returns false, no questions will be fetched
89
- from the web service and captcha validation is disabled.
89
+ For example:
90
90
 
91
- This is useful for writing your own custom logic for toggling spam protection
92
- on/off e.g. for logged in users. By default the `perform_textcaptcha?` method
93
- [checks if the object is a new (unsaved)
94
- record](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha.rb#L54).
91
+ ```ruby
92
+ class Comment < ApplicationRecord
93
+ acts_as_textcaptcha api_key: 'TEXTCAPTCHA_API_IDENTITY_KEY',
94
+ raise_errors: false,
95
+ cache_expiry_minutes: 10,
96
+ questions: [
97
+ { 'question' => '1+1', 'answers' => '2,two' },
98
+ { 'question' => 'The green hat is what color?', 'answers' => 'green' }
99
+ ]
100
+ end
101
+ ```
95
102
 
96
- So out of the box, spam protection is only enabled for creating new records (not
97
- updating). Here is a typical example showing how to overwrite the
98
- `perform_textcaptcha?` method, while maintaining the new record check.
103
+ ### YAML config
99
104
 
100
- class Comment < ApplicationRecord
101
- acts_as_textcaptcha :api_key => 'TEXTCAPTCHA_API_IDENTITY'
105
+ You can apply an app wide config with a `config/textcaptcha.yml` file. Use this
106
+ rake task to add one from a
107
+ [template](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_config.rb):
102
108
 
103
- def perform_textcaptcha?
104
- super && user.admin?
105
- end
106
- end
109
+ $ bundle exec rake textcaptcha:config
107
110
 
108
- ### Configuration options
111
+ **NOTE**: Any options set in models take preference over this config.
109
112
 
110
- You can configure captchas with the following options;
113
+ ### Config without the TextCaptcha service
111
114
 
112
- * *api_key* (_required_) - reference to yourself (e.g. your email - to identify calls to the textcaptcha.com API)
113
- * *questions* (_optional_) - array of question and answer hashes (see below) A random question from this array will be asked if the web service fails OR if no `api_key` has been set. Multiple answers to the same question are comma separated (e.g. 2,two). Don't use commas in your answers!
114
- * *cache_expiry_minutes* (_optional_) - minutes for answers to persist in the cache (default 10 minutes), see [below for details](https://github.com/matthutchinson/acts_as_textcaptcha#what-does-the-code-do)
115
- * *http_read_timeout* (_optional_) - Net::HTTP option, seconds to wait for one block to be read from the remote API
116
- * *http_open_timeout* (_optional_) - Net::HTTP option, seconds to wait for the connection to open to the remote API
115
+ To use __only__ your own logic questions, omit the `api_key` and set them in the
116
+ config (see above). Multiple answers to the same question must be comma
117
+ separated e.g. `2,two` (so do not include commas in answers).
117
118
 
118
- For example;
119
+ You can optionally set your own `api_endpoint` to fetch questions and answers
120
+ from. The URL must respond with a JSON object like this:
119
121
 
120
- class Comment < ApplicationRecord
121
- acts_as_textcaptcha :api_key => 'TEXTCAPTCHA_API_IDENTITY',
122
- :http_read_timeout => 60,
123
- :http_read_timeout => 10,
124
- :cache_expiry_minutes => 10,
125
- :questions => [{ 'question' => '1+1', 'answers' => '2,two' },
126
- { 'question' => 'The green hat is what color?', 'answers' => 'green' }]
127
- end
122
+ ```ruby
123
+ {
124
+ "q": "What number is 4th in the series 39, 11, 31 and nineteen?",
125
+ "a": ["1f0e3dad99908345f7439f8ffabdffc4","1d56cec552bf111de57687e4b5f8c795"]
126
+ }
127
+ ```
128
128
 
129
- #### YAML config
129
+ With `"a"` being an array of answer strings, MD5'd, and lower-cased. The
130
+ `api_key` option is ignored if an `api_endpoint` is set.
130
131
 
131
- The gem can be configured for models individually (as shown above) or with a
132
- config/textcaptcha.yml file. The config file must have an `api_key` defined
133
- and/or an array of questions and answers. Any options defined inline in model
134
- classes take preference over the global configuration in textcaptcha.yml.
132
+ ### Toggling TextCaptcha
135
133
 
136
- The gem comes with a handy rake task to copy over a
137
- [textcaptcha.yml](http://github.com/matthutchinson/acts_as_textcaptcha/raw/master/config/textcaptcha.yml)
138
- template to your config directory;
134
+ Enable or disable captchas by overriding the `perform_textcaptcha?` method (in
135
+ models). By default the method checks if the object is a new (unsaved) record.
136
+ So spam protection is __only__ enabled for creating new records (not updating).
139
137
 
140
- rake textcaptcha:config
138
+ Here's an example overriding the default behaviour but maintaining the new
139
+ record check.
141
140
 
142
- #### Configuring _without_ the TextCaptcha web service
141
+ ```ruby
142
+ class Comment < ApplicationRecord
143
+ acts_as_textcaptcha :api_key => 'TEXTCAPTCHA_API_IDENTITY'
143
144
 
144
- To use only your own logic questions, simply omit the `api_key` from the
145
- configuration and define at least one logic question and answer (see above).
145
+ def perform_textcaptcha?
146
+ super && user.admin?
147
+ end
148
+ end
149
+ ```
146
150
 
147
151
  ## Translations
148
152
 
149
- The gem uses the standard Rails I18n translation approach (with a fall-back to
150
- English). Unfortunately at present, the TextCaptcha web service only provides
151
- logic questions in English.
152
-
153
- en:
154
- activerecord:
155
- errors:
156
- models:
157
- comment:
158
- attributes:
159
- textcaptcha_answer:
160
- incorrect: "is incorrect, try another question instead"
161
- expired: "was not submitted quickly enough, try another question instead"
162
- activemodel:
163
- attributes:
164
- comment:
165
- textcaptcha_answer: "TextCaptcha answer"
166
-
167
- ## Without ActiveRecord
168
-
169
- It is possible to use this gem without ActiveRecord. As an example, take a look at the
170
- [Contact](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/test/test_models.rb#L44)
171
- model used in the test suite
172
- [here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/test/test_models.rb#L44).
173
-
174
- ## Testing and docs
175
-
176
- In development you can run the tests and rdoc tasks like so;
177
-
178
- * `rake test` (all tests)
179
- * `appraisal rake test` (all tests with all gemfile variations)
180
- * `appraisal rails-3 rake test` (all tests using a specific gemfile)
181
- * `rake rdoc` (generate docs)
182
-
183
- This gem uses [appraisal](https://github.com/thoughtbot/appraisal) to run the
184
- test suite with multiple versions of Rails.
185
-
186
- ## What does the code do?
187
-
188
- The gem contains two parts, a module for your ActiveRecord models, and a single
189
- view helper method. The ActiveRecord module makes use of two futher classes,
190
- [TextcaptchaApi](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_api.rb)
191
- and
192
- [TextcaptchaCache](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_cache.rb).
193
-
194
- A call to `@model.textcaptcha` in your controller will query the TextCaptcha web
195
- service. A GET request is made with Net::HTTP and parsed using the default Rails
196
- `ActiveSupport::XMLMini` backend. A textcaptcha_question and a random cache key
197
- are assigned to the record. An array of possible answers is also stored in the
198
- TextcaptchaCache with this random key. The cached answers have (by default) a 10
199
- minute TTL in your cache. If your forms take more than 10 minutes to be
200
- completed you can adjust this value setting the `cache_expiry_minutes` option.
201
- Internally TextcaptchaCache wraps
202
- [Rails.cache](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html)
203
- and all cache keys are name spaced.
204
-
205
- On saving, `validate_textcaptcha` is called on @model.validate checking that the
206
- `@model.textcaptcha_answer` matches one of the possible answers (retrieved from
207
- the cache). By default, this validation is _only_ carried out on new records,
208
- i.e. never on update, only on create. All attempted answers are case-insensitive
209
- and have trailing/leading white-space removed.
210
-
211
- Regardless of a correct, or incorrect answer the possible answers are cleared
212
- from the cache and a new random key is generated and assigned. An incorrect
213
- answer will cause a new question to be prompted. After one correct answer, the
214
- answer and a mutating key are sent on further form requests, and no question is
215
- presented in the form.
216
-
217
- If an error or timeout occurs during API fetching, ActsAsTextcaptcha will fall
218
- back to choose a random logic question defined in your options (see above). If
219
- the web service fails or no API key is specified AND no alternate questions are
220
- configured, the @model will not require textcaptcha checking and will pass as
221
- valid.
222
-
223
- For more details on the code please check the
224
- [documentation](http://rdoc.info/projects/matthutchinson/acts_as_textcaptcha).
225
- Tests are written with [MiniTest](https://rubygems.org/gems/minitest). Pull
226
- requests and bug reports are welcome.
153
+ The following strings are translatable (with Rails I18n translations):
227
154
 
228
- ## Requirements
155
+ ```yaml
156
+ en:
157
+ activerecord:
158
+ errors:
159
+ models:
160
+ comment:
161
+ attributes:
162
+ textcaptcha_answer:
163
+ incorrect: "is incorrect, try another question instead"
164
+ expired: "was not submitted quickly enough, try another question instead"
165
+ activemodel:
166
+ attributes:
167
+ comment:
168
+ textcaptcha_answer: "TextCaptcha answer"
169
+ ```
170
+
171
+ **NOTE**: The textcaptcha.com API only provides logic questions in English.
172
+
173
+ ## Handling Errors
174
+
175
+ The API may be unresponsive or return an unexpected response. If you've set
176
+ `raise_errors: true`, consider handling these errors:
177
+
178
+ * `ActsAsTextcaptcha::ResponseError`
179
+ * `ActsAsTextcaptcha::ParseError`
180
+ * `ActsAsTextcaptcha::ApiKeyError`
181
+
182
+ ## Development
183
+
184
+ Check out this repo and run `bin/setup`, this will install gem dependencies and
185
+ generate docs. Use `bundle exec rake` to run tests and generate a coverage
186
+ report.
187
+
188
+ You can also run `bin/console` for an interactive prompt to experiment with the
189
+ code.
190
+
191
+ ## Tests
192
+
193
+ MiniTest is used for testing. Run the test suite with:
194
+
195
+ $ rake test
196
+
197
+ This gem uses [appraisal](https://github.com/thoughtbot/appraisal) to test
198
+ against multiple versions of Rails.
229
199
 
230
- What do you need?
200
+ * `appraisal rake test` (run tests against all Gemfile variations)
201
+ * `appraisal rails-3 rake test` (run tests against a specific Gemfile)
231
202
 
232
- * [Rails](http://github.com/rails/rails) >= 3 (including Rails 4 & 5)
233
- * [Rails.cache](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores) - a basic cache configuration is necessary
234
- * [Ruby](http://ruby-lang.org/) >= 2.1
203
+ ## Docs
235
204
 
236
- *NOTE:* The built-in
237
- [TextcaptchaCache](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_cache.rb)
238
- class directly wraps the
239
- [Rails.cache](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
240
- An alternative TextcaptchaCache implementation will be necessary if
241
- `Rails.cache` is not available.
205
+ Generate docs for this gem with:
242
206
 
207
+ $ rake rdoc
243
208
 
244
- ## Rails 2 Support
209
+ ## Troubles?
245
210
 
246
- Support for Rails 2 was dropped with the release of `v4.1.0`. If you would like
247
- to continue to use this gem with an older version of Rails (>= 2.3.8), please
248
- lock your Gemfile with version `4.0.0`. Like so;
211
+ If you think something is broken or missing, please raise a new
212
+ [issue](https://github.com/matthutchinson/acts_as_textcaptcha/issues). Please
213
+ remember to check it hasn't already been raised.
249
214
 
250
- # in your Gemfile
251
- gem 'acts_as_textcaptcha', '=4.0.0'
215
+ ## Contributing
252
216
 
253
- # or in config/environment.rb
254
- config.gem 'acts_as_textcaptcha', :version => '=4.0.0'
217
+ Bug [reports](https://github.com/matthutchinson/acts_as_textcaptcha/issues) and
218
+ [pull requests](https://github.com/matthutchinson/acts_as_textcaptcha/pulls) are
219
+ welcome on GitHub. When submitting pull requests, remember to add tests covering
220
+ any new behaviour, and ensure all tests are passing on
221
+ [Travis](https://travis-ci.org/matthutchinson/acts_as_textcaptcha). Read the
222
+ [contributing
223
+ guidelines](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CONTRIBUTING.md)
224
+ for more details.
255
225
 
256
- Check out the
257
- [README](https://github.com/matthutchinson/acts_as_textcaptcha/tree/v4.0.0) for
258
- this release for further instructions.
226
+ This project is intended to be a safe, welcoming space for collaboration, and
227
+ contributors are expected to adhere to the [Contributor
228
+ Covenant](http://contributor-covenant.org) code of conduct. See
229
+ [here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CODE_OF_CONDUCT.md)
230
+ for more details.
231
+
232
+ ## Todo
233
+
234
+ * Allow translatable user supplied questions and answers in config
235
+ * Allow `Net::HTTP` to be swapped out for any another HTTP client.
236
+
237
+ ## License
238
+
239
+ The code is available as open source under the terms of
240
+ [LGPL-3](https://opensource.org/licenses/LGPL-3.0).
241
+
242
+ ## Who's who?
243
+
244
+ * [ActsAsTextcaptcha](http://github.com/matthutchinson/acts_as_textcaptcha) and [little robot drawing](http://www.flickr.com/photos/hiddenloop/4541195635/) by [Matthew Hutchinson](http://matthewhutchinson.net)
245
+ * [TextCaptcha](http://textcaptcha.com) API and service by [Rob Tuley](https://twitter.com/robtuley)
259
246
 
260
247
  ## Links
261
248
 
262
249
  * [Demo](https://acts-as-textcaptcha-demo.herokuapp.com)
263
250
  * [Travis CI](http://travis-ci.org/#!/matthutchinson/acts_as_textcaptcha)
264
251
  * [Maintainability](https://codeclimate.com/github/matthutchinson/acts_as_textcaptcha/maintainability)
252
+ * [Test Coverage](https://codeclimate.com/github/matthutchinson/acts_as_textcaptcha/test_coverage)
265
253
  * [RDoc](http://rdoc.info/projects/matthutchinson/acts_as_textcaptcha)
266
254
  * [Wiki](http://wiki.github.com/matthutchinson/acts_as_textcaptcha/)
267
255
  * [Issues](http://github.com/matthutchinson/acts_as_textcaptcha/issues)
268
256
  * [Report a bug](http://github.com/matthutchinson/acts_as_textcaptcha/issues/new)
269
257
  * [Gem](http://rubygems.org/gems/acts_as_textcaptcha)
270
258
  * [GitHub](http://github.com/matthutchinson/acts_as_textcaptcha)
271
-
272
- ## Who's who?
273
-
274
- * [ActsAsTextcaptcha](http://github.com/matthutchinson/acts_as_textcaptcha) and [little robot drawing](http://www.flickr.com/photos/hiddenloop/4541195635/) by [Matthew Hutchinson](http://matthewhutchinson.net)
275
- * [TextCaptcha](http://textcaptcha.com) API and service by [Rob Tuley](https://twitter.com/robtuley)
276
-
277
- ## Usage
278
-
279
- This gem is used in a number of production websites and apps. It was originally
280
- extracted from code developed for [Bugle](http://bugleblogs.com). If you're
281
- happily using acts_as_textcaptcha in production, please let me know and I'll add
282
- you to this list!
283
-
284
- * [matthewhutchinson.net](http://matthewhutchinson.net)
285
- * [pmFAQtory.com](http://pmfaqtory.com)
286
- * [The FAQtory](http://faqtoryapp.com)
287
- * [DPT Watch, San Francisco](http://www.dptwatch.com)