问题
I have a Rails 4 app using Devise (the most recent) and am trying to create a random token for each user (like the ID, but longer, etc.) Using this answer I was able to come up with the follow code:
# app/models/model_name.rb
class ModelName < ActiveRecord::Base
include Tokenable
end
# app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless self.class.exists?(token: random_token)
end
end
end
This code works fantastically for tokens that are unique for any given model. I.e. All Users will have unique tokens, and all Admins will have unique tokens. But an Admin may have the same token as a User – this behavior is unwanted.
Is there an elegant way, short of abstracting the token into its own model and using "has_one" relationships, to ensure that the token does not exist in all the models it is a part of?
(I guess I could hard code unless (User.exists? ... or Admin.exists? ... )
into the unless clause, though this seems bulky.)
Any thoughts or suggestions are appreciated! Thanks!
回答1:
I would create a method that lists each of the Classes that are including my concern and then test against the token for each. Something like this:
# app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def included_classes
ActiveRecord::Base.descendants.select do |c|
c.included_modules.include(Concerns::Tokenable)}.map(&:name)
end
end
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless included_classes.map {|c| c.constantize.exists?(token: random_token) }.include?(true)
end
end
end
So include_classes is going to return an array of names as strings of each of the classes that include the Tokenable concern. And then in the loop within generate_token is going to check against each of these classes generating an array of true or false which then we just check if any are true with include?(true)
.
Here is were I found how to get included classes (first answer).
EDIT
In Rails 5 the included_classes looks like this (note the ApplicationRecord and not needing the Concerns::Tokenable):
def included_classes
ApplicationRecord.descendants.select do |c|
c.included_modules.include?(Tokenable)
end
end
回答2:
Rails 5 comes with a new feaeture has_secure_token
is really easy to use:
# Schema: User(token:string, auth_token:string)
class User < ActiveRecord::Base
has_secure_token :auth_token
end
user = User.new
user.save
user.auth_token # => "pX27zsMN2ViQKta1bGfLmVJE"
user.regenerate_auth_token # => true
Since Rails 5 isn't already released, you can use the Backport has_secure_token gem
来源:https://stackoverflow.com/questions/27335681/create-random-unique-tokens-upon-account-creation-in-rails