Skip to content

Commit

Permalink
Support :if and :unless in has_secure_token
Browse files Browse the repository at this point in the history
Pass through :if and :unless options from has_secure_token to the
generated before_create callback
  • Loading branch information
glittershark committed Jan 9, 2016
1 parent 9d681fc commit 21c0a1f
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 2 deletions.
4 changes: 4 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Support `:if` and `:unless` options in `has_secure_token`

*Griffin Smith*

* Use `version` column as primary key for schema_migrations table because
schema_migrations versions are guaranteed to be unique.

Expand Down
25 changes: 23 additions & 2 deletions activerecord/lib/active_record/secure_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,35 @@ module ClassMethods
#
# <tt>SecureRandom::base58</tt> is used to generate the 24-character unique token, so collisions are highly unlikely.
#
# A secure token can also be only created given a condition, for example if a user should only have an
# auto-generated invitation token if the user was invited:
#
# # Schema: User(token:string, invited:boolean)
# class User < ActiveRecord::Base
# has_secure_token if: :invited?
# end
#
# user = User.new(invited: true)
# user.save
# user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
#
# user = User.new(invited: false)
# user.save
# user.token # => nil
#
# The secure token creation supports all the options a `before_create` does - like +:if+ and +:unless+.
#
# Note that it's still possible to generate a race condition in the database in the same way that
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
def has_secure_token(attribute = :token)
def has_secure_token(attribute = :token, **before_create_options)
# Load securerandom only when has_secure_token is used.
require 'active_support/core_ext/securerandom'

define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")}
before_create(before_create_options) do
self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")
end
end

def generate_unique_secure_token
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/cases/secure_token_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,14 @@ def test_token_value_not_overwritten_when_present

assert_equal @user.token, "custom-secure-token"
end

def test_token_with_if_condition_checks_condition_on_save
@user.token_condition = false
@user.save
assert_nil @user.conditional_token

@user.token_condition = true
@user.save
assert_not_nil @user.conditional_token
end
end
3 changes: 3 additions & 0 deletions activerecord/test/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class User < ActiveRecord::Base
has_secure_token
has_secure_token :auth_token
has_secure_token :conditional_token, if: :token_condition

attr_accessor :token_condition
end

class UserWithNotification < User
Expand Down

0 comments on commit 21c0a1f

Please sign in to comment.