From 21c0a1f3016b1de81f455f2a242803db91f96f17 Mon Sep 17 00:00:00 2001
From: Griffin Smith <wildgriffin45@gmail.com>
Date: Fri, 10 Jul 2015 11:32:18 -0400
Subject: Support :if and :unless in has_secure_token

Pass through :if and :unless options from has_secure_token to the
generated before_create callback
---
 activerecord/CHANGELOG.md                      |  4 ++++
 activerecord/lib/active_record/secure_token.rb | 25 +++++++++++++++++++++++--
 activerecord/test/cases/secure_token_test.rb   | 10 ++++++++++
 activerecord/test/models/user.rb               |  3 +++
 4 files changed, 40 insertions(+), 2 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 3199951f68..56ac3b95b3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -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.
 
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index 8abda2ac49..f10f7a1515 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -20,14 +20,35 @@ module ActiveRecord
       #
       # <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
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
index e731443fc2..ff0072d59c 100644
--- a/activerecord/test/cases/secure_token_test.rb
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -29,4 +29,14 @@ class SecureTokenTest < ActiveRecord::TestCase
 
     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
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index f5dc93e994..a40385e047 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -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
-- 
cgit v1.2.3