module ActiveRecord::SignedId::ClassMethods

def combine_signed_id_purposes(purpose)

:nodoc:
def combine_signed_id_purposes(purpose)
  [ base_class.name.underscore, purpose.to_s ].compact_blank.join("/")
end

def find_signed(signed_id, purpose: nil)

User.find_signed signed_id, purpose: :password_reset # => User.first
travel_back

User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired
travel 16.minutes

User.find_signed signed_id # => nil, since the purpose does not match

signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset

==== Examples

finding. If there's a mismatch, nil is again returned.
or email verification. The purpose that was set during generation must match the purpose set when
general base model, like a User, which might have signed ids for several things, like password reset
It's possible to further restrict the use of a signed id with a purpose. This helps when you have a

the signed id will no longer be valid, and nil is returned.
signed_id(expires_in: 15.minutes). If the time has elapsed before a signed find is attempted,
You set the time period that the signed id is valid for during generation, using the instance method

a certain time period.
the bearer of the signed id to be able to interact with the underlying record, but usually only within
This is particularly useful for things like password reset or email verification, where you want
Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
def find_signed(signed_id, purpose: nil)
  raise UnknownPrimaryKey.new(self) if primary_key.nil?
  if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
    find_by primary_key => id
  end
end

def find_signed!(signed_id, purpose: nil)

User.find_signed! signed_id # => ActiveRecord::RecordNotFound
User.first.destroy
signed_id = User.first.signed_id

User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature

=== Examples

the valid signed id can't find a record.
or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
def find_signed!(signed_id, purpose: nil)
  if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
    find(id)
  end
end

def signed_id_verifier

Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
with the class-level +signed_id_verifier_secret+, which within Rails comes from the
The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
def signed_id_verifier
  @signed_id_verifier ||= begin
    secret = signed_id_verifier_secret
    secret = secret.call if secret.respond_to?(:call)
    if secret.nil?
      raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
    else
      ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
    end
  end
end

def signed_id_verifier=(verifier)

your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
def signed_id_verifier=(verifier)
  @signed_id_verifier = verifier
end