token_action 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +143 -0
- data/Rakefile +16 -0
- data/app/controllers/token_action/application_controller.rb +4 -0
- data/app/controllers/token_action/tokens_controller.rb +73 -0
- data/config/locales/en.yml +10 -0
- data/config/routes.rb +3 -0
- data/lib/generators/active_record/templates/migration.rb +15 -0
- data/lib/generators/active_record/token_action_generator.rb +25 -0
- data/lib/generators/mongoid/token_action_generator.rb +7 -0
- data/lib/generators/templates/README +10 -0
- data/lib/generators/templates/token_action.rb +25 -0
- data/lib/generators/token_action_generator.rb +35 -0
- data/lib/token_action/engine.rb +5 -0
- data/lib/token_action/mixins/model.rb +24 -0
- data/lib/token_action/mixins/token_generator.rb +19 -0
- data/lib/token_action/orm/active_record.rb +10 -0
- data/lib/token_action/orm/mongoid.rb +34 -0
- data/lib/token_action/version.rb +3 -0
- data/lib/token_action.rb +71 -0
- data/spec/controllers/token_action/tokens_controller_spec.rb +172 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/active_record/cat.rb +18 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/hello_controller.rb +25 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mongoid/cat.rb +22 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +71 -0
- data/spec/dummy/config/boot.rb +15 -0
- data/spec/dummy/config/database.yml +18 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +41 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/token_action.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/locales/token_action.en.yml +26 -0
- data/spec/dummy/config/mongoid.yml +68 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130104151630_create_token_action_tokens.rb +15 -0
- data/spec/dummy/db/migrate/20130104201018_create_cats.rb +7 -0
- data/spec/dummy/log/development.log +16 -0
- data/spec/dummy/log/test.log +4739 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/factories.rb +6 -0
- data/spec/routing/token_action/tokens_routing_spec.rb +33 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/token_action/mixins/model_spec.rb +13 -0
- data/spec/token_action/mixins/token_generator_spec.rb +11 -0
- data/spec/token_action/orm/active_record_spec.rb +6 -0
- data/spec/token_action/orm/mongoid_spec.rb +6 -0
- data/spec/token_action_spec.rb +67 -0
- metadata +394 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Open North Inc.
|
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,143 @@
|
|
1
|
+
# TokenAction
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/opennorth/token_action.png)](http://travis-ci.org/opennorth/token_action)
|
4
|
+
[![Dependency Status](https://gemnasium.com/opennorth/token_action.png)](https://gemnasium.com/opennorth/token_action)
|
5
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/opennorth/token_action)
|
6
|
+
|
7
|
+
TokenAction lets you create tokens to be used to authenticate actions. For example:
|
8
|
+
|
9
|
+
* Create a token for a user to confirm an account
|
10
|
+
* Create a token for a subscriber to unsubscribe from notifications
|
11
|
+
* Create a token for a user to confirm a change of email address
|
12
|
+
|
13
|
+
## Getting Started
|
14
|
+
|
15
|
+
TokenAction works with Rails 3.1 onwards. Add it to your Gemfile:
|
16
|
+
|
17
|
+
gem 'token_action'
|
18
|
+
|
19
|
+
Install the gem and configure your application to use TokenAction with:
|
20
|
+
|
21
|
+
bundle
|
22
|
+
bundle exec rails generate token_action
|
23
|
+
|
24
|
+
If you are using ActiveRecord, run the migration:
|
25
|
+
|
26
|
+
bundle exec rake db:migrate
|
27
|
+
|
28
|
+
## Basic Usage
|
29
|
+
|
30
|
+
Create a shared secret to confirm a user account, for example:
|
31
|
+
|
32
|
+
token = TokenAction::Token.create! :kind => 'User', :args => [1, :confirm]
|
33
|
+
token.token # a 20-character alphanumeric string like "j7NtCaYfUpZXyDCseKG2"
|
34
|
+
|
35
|
+
You can then send the token with instructions to the user via email. When the user visits `/tokens/j7NtCaYfUpZXyDCseKG2/confirm`, TokenAction will look up the token. If not found, it will log an informational message to the Rails logger, set `flash[:alert]` and redirect to the `root_path`. If found, it will call the `redeem_token` method on the `User` class, passing in the arguments `1` and `:confirm`. You must implement the public `redeem_token` method. For example:
|
36
|
+
|
37
|
+
class User < ActiveRecord::Base
|
38
|
+
def self.redeem_token(id, meth)
|
39
|
+
User.find(id).send(meth)
|
40
|
+
end
|
41
|
+
|
42
|
+
def confirm
|
43
|
+
update_attribute :confirmed, true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
If an exception is raised, TokenAction will log a warning message to the Rails logger, set `flash[:alert]` and redirect to the `root_path`. If no exception is raised, TokenAction will set `flash[:notice]` and redirect to the `root_path`. That's it!
|
48
|
+
|
49
|
+
## Customization
|
50
|
+
|
51
|
+
You can customize the redirect URLs, the routes, the flash messages and the tokens controller.
|
52
|
+
|
53
|
+
### Redirect URLs
|
54
|
+
|
55
|
+
To change the default success and failure URLs from `root_path`, edit the `config/initializers/token_action.rb` file created by `rails generate token_action`. You may also set success and failure URLs for each token, by creating tokens with `:success_url` and `:failure_url` arguments:
|
56
|
+
|
57
|
+
token = TokenAction::Token.create! :kind => 'Cat', :success_url => cat_path(1), :failure_url => '/oops'
|
58
|
+
|
59
|
+
**Note:** If you change your URL structure after creating tokens, TokenAction may attempt to redirect to an unroutable path. If a path is unroutable, TokenAction will redirect to another URL in this order of precedence:
|
60
|
+
|
61
|
+
* `Token#success_url`
|
62
|
+
* `TokenAction.success_url`
|
63
|
+
* `root_path`
|
64
|
+
|
65
|
+
If an exception was raised and a path is unroutable, it will redirect in this order of precedence:
|
66
|
+
|
67
|
+
* `Token#failure_url`
|
68
|
+
* `TokenAction.failure_url`
|
69
|
+
* `root_path`
|
70
|
+
|
71
|
+
### Routes
|
72
|
+
|
73
|
+
The TokenAction generator will add `mount TokenAction::Engine => '/token_action'` to your routes, which defines:
|
74
|
+
|
75
|
+
get 'tokens/:token/*path', :to => 'tokens#redeem'
|
76
|
+
|
77
|
+
As such, you can write URLs like `tokens/xxx/confirm` or `tokens/xxx/unsubscribe` or `tokens/xxx/a/b/c`.
|
78
|
+
|
79
|
+
To customize the first part of the route, replace `mount TokenAction::Engine => '/token_action'` with something like:
|
80
|
+
|
81
|
+
get 'jetons/:token/*path', :to => 'token_action/tokens#redeem'
|
82
|
+
|
83
|
+
### Flash messages
|
84
|
+
|
85
|
+
The TokenAction generator will create a `config/locales/token_action.en.yml` file with the default messages for when a token is not found, an exception is raised, or the action is performed successfully. You will probably want to change these messages depending on the action taken and the exception raised.
|
86
|
+
|
87
|
+
* If a token was redeemed by accessing a URL like `tokens/xxx/confirm`, TokenAction will look for a message in the scope `token_action.tokens.confirm`. If not found, it will default to the scope `token_action.tokens.default`.
|
88
|
+
|
89
|
+
* If a token was redeemed by accessing a long URL like `tokens/xxx/a/b/c`, TokenAction will look for a message in the scope `token_action.tokens.a.b.c`.
|
90
|
+
|
91
|
+
You may want to raise an exception if an action has already been performed or if it is no longer valid, in which case you may want the failure message to change according to the exception raised.
|
92
|
+
|
93
|
+
* If an `AlreadyConfirmed` exception is raised, TokenAction will look for a message in the scope `token_action.tokens.default.already_confirmed`. If not found, it will default to `token_action.tokens.default.failure`.
|
94
|
+
|
95
|
+
* If a namespaced `MyModule::AlreadyConfirmed` exception is raised, TokenAction will look for a message in the scope `token_action.tokens.default.my_module.already_confirmed`.
|
96
|
+
|
97
|
+
Putting it all together, if an `AlreadyConfirmed` exception is raised accessing a URL like `tokens/xxx/confirm`, TokenAction will look for messages in this order of preference:
|
98
|
+
|
99
|
+
* `token_action.tokens.confirm.already_confirmed`
|
100
|
+
* `token_action.tokens.default.already_confirmed`
|
101
|
+
* `token_action.tokens.confirm.failure`
|
102
|
+
* `token_action.tokens.default.failure`
|
103
|
+
|
104
|
+
### Tokens controller
|
105
|
+
|
106
|
+
To customize the controller without monkey patching, create a new controller like:
|
107
|
+
|
108
|
+
class TokensController < TokenAction::TokensController
|
109
|
+
end
|
110
|
+
|
111
|
+
And replace the route with something like:
|
112
|
+
|
113
|
+
get 'tokens/:token/*path', :to => 'tokens#redeem'
|
114
|
+
|
115
|
+
You can then override the `redeem` method in your new controller.
|
116
|
+
|
117
|
+
## Supported ORMs
|
118
|
+
|
119
|
+
* [ActiveRecord](https://rubygems.org/gems/activerecord)
|
120
|
+
* [Mongoid](https://rubygems.org/gems/mongoid)
|
121
|
+
|
122
|
+
It should be easy to add adapters for [MongoMapper](https://rubygems.org/gems/mongo_mapper), DataMapper, Sequel and CouchPotato. If you are using DataMapper or Sequel, you must use an ActiveModel compliance plugin:
|
123
|
+
|
124
|
+
* [DataMapper plugin](https://github.com/datamapper/dm-active_model)
|
125
|
+
* [Sequel plugin](http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/ActiveModel.html)
|
126
|
+
|
127
|
+
If using Sequel, you will want to use the [orm_adapter-sequel](https://github.com/elskwid/orm_adapter-sequel) gem. If using CouchPotato, you will need to write your own adapter for ORM Adapter.
|
128
|
+
|
129
|
+
## Caveats
|
130
|
+
|
131
|
+
* If you change the name of a class, update the `kind` attribute on its tokens to avoid making them unprocessable.
|
132
|
+
* Be careful when changing the behavior of a `redeem_token` method, to avoid making tokens unprocessable.
|
133
|
+
|
134
|
+
## Roadmap
|
135
|
+
|
136
|
+
* The tokens controller only responds to HTML requests. Pull requests to add JSON and XML responders are welcome!
|
137
|
+
* The generators do not have specs.
|
138
|
+
|
139
|
+
## Bugs? Questions?
|
140
|
+
|
141
|
+
This engine's main repository is on GitHub: [http://github.com/opennorth/token_action](http://github.com/opennorth/token_action), where your contributions, forks, bug reports, feature requests, and feedback are greatly welcomed.
|
142
|
+
|
143
|
+
Copyright (c) 2012 Open North Inc., released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new
|
12
|
+
rescue LoadError
|
13
|
+
task :yard do
|
14
|
+
abort 'YARD is not available. In order to run yard, you must: gem install yard'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module TokenAction
|
2
|
+
class TokensController < ApplicationController
|
3
|
+
def redeem
|
4
|
+
@token = TokenAction::Token.to_adapter.find_first(:token => params[:token])
|
5
|
+
if @token
|
6
|
+
begin
|
7
|
+
# Do this in the controller's scope to allow access to request methods.
|
8
|
+
ActiveSupport::Inflector.constantize(@token.kind).redeem_token(*@token.args)
|
9
|
+
redirect_to success_url, :notice => translate(:success)
|
10
|
+
rescue => e
|
11
|
+
key = ActiveSupport::Inflector.underscore(e.class).gsub('/', I18n.default_separator)
|
12
|
+
logger.warn "TokenAction failed to perform the action for the token '#{params[:token]}': #{e.message}"
|
13
|
+
redirect_to failure_url, :alert => translate(key, :failure)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
logger.info "TokenAction failed to find the token '#{params[:token]}'"
|
17
|
+
redirect_to failure_url, :alert => translate(:not_found)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Returns a translation.
|
24
|
+
#
|
25
|
+
# @param [Array] args translation keys
|
26
|
+
# @return [String] a translation
|
27
|
+
def translate(*args)
|
28
|
+
path = params[:path].gsub('/', I18n.default_separator)
|
29
|
+
keys = []
|
30
|
+
|
31
|
+
# Respect the order of precedence described in the readme.
|
32
|
+
args.each do |key|
|
33
|
+
[path, :default].each do |scope|
|
34
|
+
keys << [:token_action, :tokens, scope, key]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
keys.map! do |key|
|
38
|
+
key.join(I18n.default_separator).to_sym
|
39
|
+
end
|
40
|
+
|
41
|
+
key, *default = *keys
|
42
|
+
I18n.translate(key, :default => default)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the success URL.
|
46
|
+
#
|
47
|
+
# @return [String] a URL
|
48
|
+
def success_url
|
49
|
+
[@token && @token.success_url, TokenAction.success_url].find do |url|
|
50
|
+
url.present? && routable?(url)
|
51
|
+
end || main_app.root_path
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the failure URL.
|
55
|
+
#
|
56
|
+
# @return [String] a URL
|
57
|
+
def failure_url
|
58
|
+
[@token && @token.failure_url, TokenAction.failure_url].find do |url|
|
59
|
+
url.present? && routable?(url)
|
60
|
+
end || main_app.root_path
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns whether a path is routable.
|
64
|
+
#
|
65
|
+
# @param [String] path a path
|
66
|
+
# @return [Boolean] whether the path is routable
|
67
|
+
def routable?(path)
|
68
|
+
!!Rails.application.routes.recognize_path(path, :method => :get)
|
69
|
+
rescue ActionController::RoutingError
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
en:
|
2
|
+
token_action:
|
3
|
+
tokens:
|
4
|
+
default:
|
5
|
+
# If the token is found and no exception is raised.
|
6
|
+
success: Your request was successfully processed.
|
7
|
+
# If the token is found and an exception is raised.
|
8
|
+
failure: An error occurred while processing your request.
|
9
|
+
# If the token cannot be found.
|
10
|
+
not_found: Sorry, your request could not be processed.
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateTokenActionTokens < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table(:token_action_tokens) do |t|
|
4
|
+
t.string :token
|
5
|
+
t.string :kind
|
6
|
+
t.text :args
|
7
|
+
t.string :success_url
|
8
|
+
t.string :failure_url
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :token_action_tokens, :token, :unique => true
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Generators
|
5
|
+
# This generator will run during `rails generate token_action`.
|
6
|
+
#
|
7
|
+
# @see ActiveRecord::Generators::DeviseGenerator
|
8
|
+
# @see Devise::Generators::OrmHelpers
|
9
|
+
class TokenActionGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
|
12
|
+
source_root File.expand_path('../templates', __FILE__)
|
13
|
+
|
14
|
+
def copy_migration_file
|
15
|
+
migration_template 'migration.rb', 'db/migrate/create_token_action_tokens'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.next_migration_number(number)
|
21
|
+
ActiveRecord::Generators::Base.next_migration_number(number)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
To complete the installation of TokenAction:
|
2
|
+
|
3
|
+
1. Define a root route in config/routes.rb. For example:
|
4
|
+
|
5
|
+
root :to => 'home#index'
|
6
|
+
|
7
|
+
2. Add flash messages to app/views/layouts/application.html.erb. For example:
|
8
|
+
|
9
|
+
<p class="alert alert-success"><%= notice %></p>
|
10
|
+
<p class="alert alert-error"><%= alert %></p>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
TokenAction.setup do |config|
|
2
|
+
# Load and configure the ORM. Supports "active_record" and "mongoid". You
|
3
|
+
# should not require more than one ORM for TokenAction.
|
4
|
+
require 'token_action/orm/<%= options[:orm] %>'
|
5
|
+
|
6
|
+
# If not overridden on a per-token basis, TokenAction will redirect to this
|
7
|
+
# URL after performing an action successfully. If not set, it will redirect to
|
8
|
+
# your application's root_path. Set it with:
|
9
|
+
#
|
10
|
+
# config.success_url = 'custom/redirect/url'
|
11
|
+
#
|
12
|
+
# If using a URL helper, wrap the helper in a lambda or proc:
|
13
|
+
#
|
14
|
+
# config.success_url = lambda { Rails.application.routes.url_helpers.root_url }
|
15
|
+
|
16
|
+
# If not overridden on a per-token basis, TokenAction will redirect to this
|
17
|
+
# URL when an exception is raised while performing an action, or when a token
|
18
|
+
# cannot be found. If not set, it will redirect to your app's root_path.
|
19
|
+
#
|
20
|
+
# config.failure_url = 'custom/redirect/url'
|
21
|
+
#
|
22
|
+
# If using a URL helper, wrap the helper in a lambda or proc:
|
23
|
+
#
|
24
|
+
# config.failure_url = lambda { Rails.application.routes.url_helpers.root_url }
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module TokenAction
|
2
|
+
module Generators
|
3
|
+
# Run this generator once before using TokenAction in your application.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# rails generate token_action
|
7
|
+
#
|
8
|
+
# @see Devise::Generators::InstallGenerator
|
9
|
+
# @see Devise::Generators::DeviseGenerator
|
10
|
+
class TokenActionGenerator < Rails::Generators::Base
|
11
|
+
desc "Copies TokenAction's initializer and locale files, adds routes, " <<
|
12
|
+
"and creates an ActiveRecord migration file if necessary."
|
13
|
+
|
14
|
+
hook_for :orm
|
15
|
+
namespace 'token_action'
|
16
|
+
source_root File.expand_path('../templates', __FILE__)
|
17
|
+
|
18
|
+
def copy_initializer_file
|
19
|
+
template 'token_action.rb', 'config/initializers/token_action.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
def copy_locale_file
|
23
|
+
copy_file '../../../config/locales/en.yml', 'config/locales/token_action.en.yml'
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_routes
|
27
|
+
route "mount TokenAction::Engine => '/token_action'"
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_readme
|
31
|
+
readme 'README' if behavior == :invoke
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TokenAction
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include TokenAction::TokenGenerator
|
5
|
+
|
6
|
+
included do
|
7
|
+
validates_presence_of :token, :kind
|
8
|
+
|
9
|
+
# ActiveRecord, Mongoid, MongoMapper and DataMapper implement
|
10
|
+
# `validates_uniqueness_of`. Sequel implements it in a plugin.
|
11
|
+
#
|
12
|
+
# @see http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/ValidationClassMethods.html
|
13
|
+
validates_uniqueness_of :token
|
14
|
+
|
15
|
+
before_validation :set_token
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def set_token
|
21
|
+
self.token ||= self.class.generate_token(:token)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module TokenAction
|
2
|
+
module TokenGenerator
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Returns a new alphanumeric token.
|
7
|
+
#
|
8
|
+
# @param [String,Symbol] column an attribute
|
9
|
+
# @return [String] a random alphanumeric string
|
10
|
+
# @see Devise::Models::Authenticatable::ClassMethods
|
11
|
+
def generate_token(column)
|
12
|
+
loop do
|
13
|
+
token = TokenAction.friendly_token
|
14
|
+
break token unless to_adapter.find_first({ column => token })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|