acts_as_textcaptcha 4.3.0 → 4.4.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.
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)