Skip to content

Commit

Permalink
Core documentation (#34)
Browse files Browse the repository at this point in the history
* added yard gem

* add error documentation

* readd error, will remove on merge

* add yardoc files to gitignore

* remove ignored files

* will put error docs in separate branch

* add error comments

* remove check_configs and check_completed errors, methods, and tests

* remove yard from gemspec

* documentation for public core methods

* core-documentation

* Config documentation (#35)

* initial config documentation

* initial config documentation

* added descriptions and example

* added documentation, fixed block issue

* update tests
  • Loading branch information
inveterateliterate authored Apr 25, 2017
1 parent aba8c48 commit e2ff378
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 51 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
token_master-*.gem
Gemfile.lock
coverage
.yardopts
doc
.yardoc
2 changes: 1 addition & 1 deletion lib/token_master.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ def self.config
require 'token_master/error'
require 'token_master/model'
require 'token_master/version'
require 'token_master/railtie' if defined?(::Rails)
require 'token_master/railtie' if defined?(::Rails)
30 changes: 30 additions & 0 deletions lib/token_master/config.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
module TokenMaster
# `TokenMaster::Config` manages the configuration options for tokenable actions. These can be set with the initializer provided with the generator. Default values will be used if options are not specified
class Config

# Provides default values for a tokenable action:
# * `token_lifetime` is an integer representing the number of days before the token expires
# * `required_params` is an array of symbols, e.g., `[:password, :password_confirmation]`
# * `token_length` is an integer representing the number of characters in the token
DEFAULT_VALUES = {
token_lifetime: 14,
required_params: [],
Expand All @@ -8,26 +14,50 @@ class Config

attr_accessor :options

# Creates a new instance of `TokenMaster::Config`
def initialize
@options = {}
end

# Sets the key-value pairs needed to complete a tokenable action<br /> Key-value pairs used to complete a tokenable action are the `token_lifetime`, `required_params` (can be blank), and `token_length`
# @example Set a Tokenable Option
# config.add_tokenable_options(:invite, { token_lifetime: 10, required_params: [:password, :password_confirmation], token_length: 12 }) #=>
# { invite: {
# token_lifetime: 10,
# required_params: [:password, :password_confirmation],
# token_length: 12
# }
# }
# @param [Symbol] key the tokenable action
# @param [Symbol=>[Integer, String, Array]] params the key-value pairs
def add_tokenable_options(key, **params)
@options[key] = params
end

# Retrieves the `required_params` for a tokenable_action, either as set by the application, or by the default<br /> Used to update model attributes as needed for a given tokenable action
# @param [Symbol] key the tokenable action
# @return [Array] the `required_params` for a tokenable action
def get_required_params(key)
get_option(key, :required_params)
end

# Retrieves the `token_lifetime` for a tokenable action, either as set by the application, or by the default
# @param [Symbol] key the tokenable action
# @return [Integer] the `token_lifetime` for a tokenable action
def get_token_lifetime(key)
get_option(key, :token_lifetime)
end

# Retrieves the `token_length` for a tokenable action, either as set by the application, or by the default
# @param [Symbol] key the tokenable action
# @return [Integer] the `token_length` for a tokenable action
def get_token_length(key)
get_option(key, :token_length)
end

# Determines whether options are provided for a tokenable action
# @param [Symbol] key the tokenable action
# @return [Boolean] `true` => options are set; `false` => options are not set
def options_set?(key)
@options.key? key
end
Expand Down
69 changes: 56 additions & 13 deletions lib/token_master/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@
require 'securerandom'

module TokenMaster
# TODO
# `TokenMaster::Core` provides the core functionality of the TokenMaster gem. The `Core` module performs all of the logic of completing tokenable actions, and provides descriptive messages of the status or abilities of calls made
module Core
class << self
# TODO

# Completes the tokenable action for a tokenable model instance using a token, setting `tokenable_completed_at` to the time at completion
# @param [Object] klass the tokenable Class
# @param [String, Symbol] key the tokenable action
# @param token [String] the tokenable's token used to complete the action
# @param params [Symbol=>String] keyword arguments required to complete the tokenable action
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
# @raise [TokenNotFoundError] if a tokenable instance cannot be found by the given token
# @raise [TokenCompletedError] if the tokenable action has already been completed, i.e., the tokenable instance has a timestamp in `tokenable_completed_at`
# @raise [TokenExpiredError] if the token is expired, i.e., the date is beyond the token's `created_at` plus `token_lifetime`
# @raise [MissingRequiredParamsError] if the params required by a tokenable are not provided
# @return [Object] tokenable Class instance
def do_by_token!(klass, key, token, **params)
check_manageable! klass, key
token_column = { token_col(key) => token }
Expand All @@ -19,7 +30,16 @@ def do_by_token!(klass, key, token, **params)
model
end

# TODO
# Completes the token action for a tokenable instance _without_ a token, setting the `tokenable_completed_at` to the time at completion<br /> Usually implemented when you want to complete multiple tokenable actions at once, e.g., a user completes the invite action by setting up passwords, by default also completes the confirm action
# @example Force a Tokenable Action (Confirm)
# user.force_confirm! =>
# <User id: 205, name: "John Smith", email: "[email protected]", confirm_token: nil, confirm_created_at: nil, confirm_sent_at: nil, confirm_completed_at: "2017-04-25 14:17:13">
# @param [Object] model the tokenable model instance
# @param [String, Symbol] key the tokenable action
# @param [Symbol=>String] params keyword arguments required to complete the tokenable action
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
# @raise [MissingRequiredParamsError] if the params required by a tokenable are not provided
# @return [Object] tokenable Class instance
def force_tokenable!(model, key, **params)
check_manageable! model.class, key
check_params! key, params
Expand All @@ -30,7 +50,12 @@ def force_tokenable!(model, key, **params)
model
end

# TODO
# Generates a tokenable action token, sets the token and the time of creation on the tokenable model instance
# @param [Object] model the tokenable model instance
# @param [String, Symbol] key the tokenable action
# @param [Integer] token_length the length of the generated token, method will use configuration token_length if not provided otherwise
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
# @return [String] token
def set_token!(model, key, token_length = nil)
check_manageable! model.class, key
token_length ||= TokenMaster.config.get_token_length(key.to_sym)
Expand All @@ -46,7 +71,16 @@ def set_token!(model, key, token_length = nil)
token
end

# TODO
# Accepts a block to pass on a generated token through a block, such as a mailer method, and sets `tokenable_sent_at` to the time the method is called
# @example Send Reset Instructions
# user.send_reset_instruction! { user.send_email } =>
# <User id: 205, name: "John Smith", email: "[email protected]", reset_token: "3YcHkTJ7kXwV5wM", reset_created_at: 2017-04-25 14:20:54", reset_sent_at: "2017-04-25 14:22:42", reset_completed_at: nil>
# @param [Object] model the tokenable model instance
# @param [String, Symbol] key the tokenable action
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
# @raise [TokenNotSetError] if the tokenable model instance does not have a token for the tokenable action
# @raise [TokenSentError] if this has already been called for the instance and tokenable action, i.e., `tokenable_sent_at` is not `nil`
# @return [Object] tokenable model instance
def send_instructions!(model, key)
check_manageable! model.class, key
check_token_set! model, key
Expand All @@ -58,7 +92,16 @@ def send_instructions!(model, key)
model.save(validate: false)
end

# TODO
# Provides the status of the tokenable action, whether the action has been completed, the token has been sent, the token is expired, or the token has only been created
# @param [Object] model the tokenable model instance
# @param [String, Symbol] key the tokenable action
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
# @return [String] status of the tokenable action:
# * completed
# * sent
# * expired
# * created
# * no token
def status(model, key)
check_manageable! model.class, key
return 'completed' if completed?(model, key)
Expand Down Expand Up @@ -97,7 +140,7 @@ def required_params(key)
end

def check_manageable!(klass, key)
raise NotTokenable, "#{klass} not #{key}able" unless manageable?(klass, key)
raise Errors::NotTokenable, "#{klass} not #{key}able" unless manageable?(klass, key)
end

def manageable?(klass, key)
Expand All @@ -113,15 +156,15 @@ def manageable?(klass, key)

def check_params!(key, params)
required_params = TokenMaster.config.get_required_params(key.to_sym)
raise MissingRequiredParams, 'You did not pass in the required params for this tokenable' unless required_params.all? do |k|
raise Errors::MissingRequiredParams, 'You did not pass in the required params for this tokenable' unless required_params.all? do |k|
params.keys.include? k
end
end

def check_token_active!(model, key)
raise TokenNotFound, "#{key} token not found" unless model
raise TokenCompleted, "#{key} already completed" if completed?(model, key)
raise TokenExpired, "#{key} token expired" unless token_active?(model, key)
raise Errors::TokenNotFound, "#{key} token not found" unless model
raise Errors::TokenCompleted, "#{key} already completed" if completed?(model, key)
raise Errors::TokenExpired, "#{key} token expired" unless token_active?(model, key)
end

def token_active?(model, key)
Expand All @@ -131,7 +174,7 @@ def token_active?(model, key)
end

def check_instructions_sent!(model, key)
raise TokenSent, "#{key} already sent" if instructions_sent?(model, key)
raise Errors::TokenSent, "#{key} already sent" if instructions_sent?(model, key)
end

def instructions_sent?(model, key)
Expand All @@ -143,7 +186,7 @@ def token_set?(model, key)
end

def check_token_set!(model, key)
raise TokenNotSet, "#{key}_token not set" unless token_set?(model, key)
raise Errors::TokenNotSet, "#{key}_token not set" unless token_set?(model, key)
end

def completed?(model, key)
Expand Down
41 changes: 22 additions & 19 deletions lib/token_master/error.rb
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
module TokenMaster
# The base class for all errors raised by TokenMaster
class Error < StandardError; end
# `TokenMaster::Errors` holds all of the error classes used in applying TokenMaster
module Errors
# The base class for all errors raised by TokenMaster
class Error < StandardError; end

# Raised when the attributes for a tokenable do not exist.
# This could result from a migration not being run or a spelling error
class NotTokenable < Error; end
# Raised when the attributes for a tokenable do not exist.
# This could result from a migration not being run or a spelling error
class NotTokenable < Error; end

# Raised when the required parameters for a tokenable are not provided.
# This typically happens with reset and invite tokenables, that might require both `password` and `password_confirmation` fields,
# but only one is provided to the method
class MissingRequiredParams < Error; end
# Raised when the required parameters for a tokenable are not provided.
# This typically happens with reset and invite tokenables, that might require both `password` and `password_confirmation` fields,
# but only one is provided to the method
class MissingRequiredParams < Error; end

# Raised when the tokenable instance is not found
class TokenNotFound < Error; end
# Raised when the tokenable instance is not found
class TokenNotFound < Error; end

# Raised when the status of the token is reviewed, but the tokenable action has already been completed
class TokenCompleted < Error; end
# Raised when the status of the token is reviewed, but the tokenable action has already been completed
class TokenCompleted < Error; end

# Raised when the token has expired based on the tokenable's `token_lifetime`
class TokenExpired < Error; end
# Raised when the token has expired based on the tokenable's `token_lifetime`
class TokenExpired < Error; end

# Raised when the tokenable instructions have already been sent when calling `send_tokenable_instructions!`
class TokenSent < Error; end
# Raised when the tokenable instructions have already been sent when calling `send_tokenable_instructions!`
class TokenSent < Error; end

# Raised when the tokenable model instance does not have a token set for a tokenable
class TokenNotSet < Error; end
# Raised when the tokenable model instance does not have a token set for a tokenable action
class TokenNotSet < Error; end
end
end

4 changes: 2 additions & 2 deletions lib/token_master/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def token_master(*tokenables)
TokenMaster::Core.set_token!(self, tokenable)
end
# TODO
define_method("send_#{tokenable}_instructions!") do |email|
TokenMaster::Core.send_instructions!(self, tokenable, email)
define_method("send_#{tokenable}_instructions!") do |&email|
TokenMaster::Core.send_instructions!(self, tokenable, &email)
end
# TODO
define_method("#{tokenable}_status") do
Expand Down
Loading

0 comments on commit e2ff378

Please sign in to comment.