GithubHelp home page GithubHelp logo

launchpadlab / token-master Goto Github PK

View Code? Open in Web Editor NEW
17.0 18.0 1.0 178 KB

Minimal and Simple user management for Ruby and Rails applications.

License: MIT License

Ruby 95.46% HTML 1.71% JavaScript 1.75% CSS 1.07%
ruby rails token user-management devise launchpad-lab confirm invite

token-master's Introduction

Token Master Logo

Token Master

GitHub Documentation Inline docs Gem Version Build Status Test Coverage License

Simple token logic for providing (temporary) restricted access. No routing, views, controllers, or mailers, just logic that you can use wherever and whenever you want.

Tokens can be used for any action that needs the access, such as inviting, confirming, or resetting passwords. These actions can be considered tokenable actions.

Tokenable actions can be attributed to any model, not just users. These models then become tokenable models.

Quick Start

Installation

Add this line to your application's Gemfile:

gem 'token_master'

And then execute:

$ bundle

Or install it yourself as:

$ gem install token_master

Usage

These examples assume Rails 5, but anything >= 4 will work

Let's say you want to add email confirmation flow to your User. Your tokenable model then is the User model, and the tokenable action might be something like confirm (although you can name it anything, as long as you are consistent).

  1. Create and run a migration to add the necessary columns to the users table like so:
bundle exec rails generate token_master User confirm
bundle exec rails db:migrate
  1. Add the Token Master token_master hook to the User class, and pass in the symbol for your tokenable action:
class User < ApplicationRecord
  token_master :confirm
end
  1. Somewhere during the signup flow, generate and send the token:
class UsersController < ApplicationController

  def create

    # Creating the user is up to you, here is an example
    user = User.create!(
      email: params[:email],
      password: params[:password],
      password_confirmation: params[:password_confirmation]
    )

    # Generate and save a unique token on the new user
    token = user.set_confirm_token!

    # Mark the token as sent
    user.send_confirm_instructions! do
      # Sending the email is up to you, by passing a block here:
      UserMailer.send_confirm(user) # or some other logic
    end
  end

  def resend_confirmation_instructions

    # if you have a 'resend instructions?' flow you can generate a new token and send instructions again in one step
    user.resend_confirm_instructions! do
      # Sending the email is up to you, by passing a block here:
      UserMailer.send_confirm(user) # or some other logic
    end
  end

end
  1. Somewhere during the confirmation flow, find and confirm the user:
class UsersController < ApplicationController
  def confirm

    # finds the user by the token, and mark the token as completed
    user = User.confirm_by_token!(params[:token])

    ...

  end
end

Details

Let's revisit the Quick Start and fill in the details.

The Generator

When you ran the generator

bundle exec rails generate token_master User confirm

you provided two arguments:

  • User - The class name of the model to which you are adding the tokenable action
  • confirm - The name of the tokenable action

Both of these could be anything, as long as you use the same class and name later on. If you like, you can create multiple tokenables at the same time, just add more space-separated tokenable names when calling the generator:

bundle exec rails generate token_master User confirm invite reset

Running the generator does two things:

  1. Creates a migration file in #{Rails.root}/db/migrate that looks like:
class AddConfirmTokenableToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :confirm_token,        :string,    default: nil
    add_column :users, :confirm_created_at,   :timestamp, default: nil
    add_column :users, :confirm_completed_at, :timestamp, default: nil
    add_column :users, :confirm_sent_at,      :timestamp, default: nil

    add_index :users, :confirm_token
  end
end

where the :users table is determined from the User argument and :confirm_* is determined from the confirm argument.

  1. Creates an initializer #{Rails.root}/config/initializers/token_master.rb that looks like:
TokenMaster.config do |config|
  # Set up your configurations for each *tokenable* using the methods at the bottom of this file.
  # Example: For `confirm` logic:
  #
  # Default values:
  #   token_lifetime  = 15 # days
  #   required_params = []
  #   token_length    = 20 # characters

  config.add_tokenable_options :confirm,
    token_lifetime:  15, # days
    required_params: [:email],
    token_length:    30 # characters
end

The default values will be used unless you configure them otherwise. These options can be set for each tokenable action.

The Model

When you added the Token Master hook and tokenable action to your model

class User < ApplicationRecord
  token_master :confirm
end

just make sure the class User and tokenable(s) :confirm (this can be multiple tokenables) match what you used in your generator.

Ex.

token_master :confirm, :invite, :reset
  1. The token_master hook is included automatically by Token Master in your ApplicationRecord base class.

However, if necessary, you can add this yourself by including the following in your class:

include TokenMaster::Model

This adds the token_master class method we used above, and you can make the same calls we described in the confirm example above.

  1. When you call the token_master class method, for each tokenable action you provide, a handful of methods are added to the class for each tokenable action, and named accordingly.

Assuming the tokenable action below is confirm, the methods would look like this:

Instance methods

  • set_confirm_token!
  • send_confirm_instructions!
  • resend_confirm_instructions!
  • confirm_status
  • force_confirm!

Class methods

  • confirm_by_token!

In addition to the three you have already seen in action, there is also:

confirm_status - returns the current status of the tokenable action. This is one of:

  • 'no token'
  • 'created'
  • 'sent'
  • 'completed'
  • 'expired'

force_confirm! - forcibly completes the given tokenable action

See the Api Docs for more details.

Advanced

Sometimes in order to redeem a token, we want to make sure some additional information is present and possibly save that to our model. For example, when implementing a password reset flow, we want to update the User with the new password and make sure it's valid.

Assuming we are using has_secure_password or something similar all we need to do is:

  1. Configure the tokenable action to require these fields when redeeming the token

../initializers/token_master.rb

TokenMaster.config do |config|
  config.add_tokenable_options :reset_password,
    token_lifetime:  1
    required_params: [:password, :password_confirmation]
    token_length:    30
end
  1. Include those parameters when redeeming the token (If you don't you will get an error!)
User.reset_password_by_token!(
  token,
  password: password,
  password_confirmation: password_confirmation
)

Under the hood, Token Master calls update! on the model, so if the model is not valid, it won't be saved and the token will not be redeemed.

FAQ

Can I use this without Rails?

Yes! However, there is a small dependency on ActiveRecord, see below.

Can I use this without ActiveRecord?

Almost! There is only a slight dependence on a few ActiveRecord methods and its on our radar to refactor this a bit. In the meantime, a workaround is to make sure the class you are using implements update, update!, save, and find_by. In addition, you have to either add Token Master to your class with include TokenMaster::Model or use the Token Master core module explicitly:

TokenMaster::Core.set_token!(User, :confirm) (which is equivalent to user.set_confirm_token!(token))

See the Api Docs for more details.

Who is Launchpad Lab?

We are product builders, check us out at Launchpad Lab

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/LaunchpadLab/token-master.

License

The gem is available as open source under the terms of the MIT License.


token-master's People

Contributors

bhennes2 avatar francirp avatar inveterateliterate avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

arielrin

token-master's Issues

Document Error Classes

lib/token_master/error.rb

  • [] NotConfigured
  • [] MissingRequiredParams
  • [] TokenNotFound
  • [] TokenCompleted
  • [] TokenNotCompleted
  • [] TokenExpired
  • [] TokenSent
  • [] TokenNotSent

Refactor out dependence on ActiveRecord

This will involve handling both the presence and absence of Active Record. We will have to give users the options to handle things like update, save, find_by. More details to come.

Document TokenMaster::Config

  • [] Class
  • [] add_tokenable_options
  • [] get_required_params
  • [] get_token_lifetime
  • [] get_token_length
  • [] options_set?

Update README

Update README to reflect different uses cases of the gem, and code examples.

Token master doesn't play nice with Active Admin

I'm not 100% certain what the problem is yet, but I just want to get this down as an issue before digging in.

Token master is working as expected with my User model in my normal client <-> API communication, but when navigating the Active Admin interface I get an error undefined method 'token_master'.

The stack trace shows the error happening when the app is building the admin routes with ActiveAdmin.routes(self).

Relevant user model code:

class User < ApplicationRecord
  include TokenMaster::Core
  token_master :reset_password
  has_secure_password

I will look into this, but if anyone has seen this issue please post here!

force_confirm! is undefined on User

screen shot 2018-03-16 at 11 29 34 am

Other methods, like set_confirm_token! work fine:

screen shot 2018-03-16 at 11 30 18 am

User model:

class User < ApplicationRecord
  include TokenMaster::Core
  has_secure_password
  token_master :confirm, :reset_password, :invite
...

Circular dependency issue with ApplicationRecord

I am getting this error when using token_master on Rails 5.1.4: `load_missing_constant': Circular dependency detected while autoloading constant ApplicationRecord (RuntimeError)

I have a solution and will open a PR shortly.

Stack trace below:

rails s -p 4000
=> Booting Puma
=> Rails 5.1.4 application starting in development
=> Run `rails server -h` for more startup options
Exiting
/Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:508:in `load_missing_constant': Circular dependency detected while autoloading constant ApplicationRecord (RuntimeError)
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:202:in `const_missing'
	from /Users/ryanfrancis/launchpad/lpl_gems/token-master/lib/token_master/railtie.rb:10:in `block (2 levels) in <class:Railtie>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:69:in `instance_eval'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:69:in `block in execute_hook'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:60:in `with_execution_control'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:65:in `execute_hook'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:50:in `block in run_load_hooks'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:49:in `each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/lazy_load_hooks.rb:49:in `run_load_hooks'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/base.rb:326:in `<module:ActiveRecord>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/base.rb:25:in `<top (required)>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:292:in `require'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:292:in `block in require'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:256:in `block in load_dependency'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:661:in `new_constants_in'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:256:in `load_dependency'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:292:in `require'
	from /Users/ryanfrancis/launchpad/nsg/buku/app/models/application_record.rb:1:in `<top (required)>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:476:in `load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:476:in `block in load_file'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:661:in `new_constants_in'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:475:in `load_file'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:374:in `block in require_or_load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:36:in `block in load_interlock'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies/interlock.rb:12:in `block in loading'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies/interlock.rb:11:in `loading'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:36:in `load_interlock'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:357:in `require_or_load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:510:in `load_missing_constant'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:202:in `const_missing'
	from /Users/ryanfrancis/launchpad/nsg/buku/app/models/admin_user.rb:1:in `<top (required)>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:476:in `load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:476:in `block in load_file'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:661:in `new_constants_in'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:475:in `load_file'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:374:in `block in require_or_load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:36:in `block in load_interlock'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies/interlock.rb:12:in `block in loading'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies/interlock.rb:11:in `loading'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:36:in `load_interlock'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:357:in `require_or_load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:510:in `load_missing_constant'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:202:in `const_missing'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/inflector/methods.rb:269:in `const_get'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/inflector/methods.rb:269:in `block in constantize'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/inflector/methods.rb:267:in `each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/inflector/methods.rb:267:in `inject'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/inflector/methods.rb:267:in `constantize'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:582:in `get'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:613:in `constantize'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise.rb:313:in `get'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/mapping.rb:81:in `to'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/mapping.rb:76:in `modules'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/mapping.rb:93:in `routes'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/mapping.rb:160:in `default_used_route'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/mapping.rb:70:in `initialize'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise.rb:343:in `new'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise.rb:343:in `add_mapping'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/rails/routes.rb:241:in `block in devise_for'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/rails/routes.rb:240:in `each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise-4.3.0/lib/devise/rails/routes.rb:240:in `devise_for'
	from /Users/ryanfrancis/launchpad/nsg/buku/config/routes.rb:2:in `block in <top (required)>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/actionpack-5.1.4/lib/action_dispatch/routing/route_set.rb:426:in `instance_exec'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/actionpack-5.1.4/lib/action_dispatch/routing/route_set.rb:426:in `eval_block'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/actionpack-5.1.4/lib/action_dispatch/routing/route_set.rb:408:in `draw'
	from /Users/ryanfrancis/launchpad/nsg/buku/config/routes.rb:1:in `<top (required)>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:286:in `load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:286:in `block in load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:258:in `load_dependency'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/dependencies.rb:286:in `load'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:55:in `block in load_paths'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:55:in `each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:55:in `load_paths'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:18:in `reload!'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:41:in `block in updater'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/activesupport-5.1.4/lib/active_support/file_update_checker.rb:81:in `execute'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:42:in `updater'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/routes_reloader.rb:31:in `execute_if_updated'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application/finisher.rb:128:in `block in <module:Finisher>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/initializable.rb:30:in `instance_exec'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/initializable.rb:30:in `run'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/initializable.rb:59:in `block in run_initializers'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:228:in `block in tsort_each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:431:in `each_strongly_connected_component_from'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:349:in `block in each_strongly_connected_component'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `call'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:347:in `each_strongly_connected_component'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:226:in `tsort_each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/2.3.0/tsort.rb:205:in `tsort_each'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/initializable.rb:58:in `run_initializers'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/application.rb:353:in `initialize!'
	from /Users/ryanfrancis/launchpad/nsg/buku/config/environment.rb:5:in `<top (required)>'
	from config.ru:3:in `require_relative'
	from config.ru:3:in `block in <main>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/builder.rb:55:in `instance_eval'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/builder.rb:55:in `initialize'
	from config.ru:in `new'
	from config.ru:in `<main>'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/builder.rb:49:in `eval'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/builder.rb:49:in `new_from_string'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/builder.rb:40:in `parse_file'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/server.rb:319:in `build_app_and_options_from_config'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/server.rb:219:in `app'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:24:in `app'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/rack-2.0.3/lib/rack/server.rb:354:in `wrapped_app'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:80:in `log_to_stdout'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:42:in `start'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:135:in `block in perform'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:130:in `tap'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands/server/server_command.rb:130:in `perform'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/command/base.rb:63:in `perform'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/command.rb:44:in `invoke'
	from /Users/ryanfrancis/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/railties-5.1.4/lib/rails/commands.rb:16:in `<top (required)>'
	from bin/rails:4:in `require'
	from bin/rails:4:in `<main>'

Ruby >=3 handles positional args vs keyword args differently, users may run into an error with the comments in the config

Initializer Template includes guidance liek the below:

# Examples:
  # config.add_tokenable_options :confirm, TokenMaster::Config::DEFAULT_VALUES

However, because of the way the second argument (a hash) is deconstructed in lib/config:

def add_tokenable_options(key, **params)
      @options[key] = params
    end

This results in a conflict with the way Ruby 3.1 is passing those options along:
https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

The fix for the user is to splat their options in the initializer like so:
config.add_tokenable_options :confirm, **TokenMaster::Config::DEFAULT_VALUES

creating an issue in here in case there's an option to update the core code to support either approaches, for example not double-splatting and setting the default to be the TokenMaster default

Implement more specific error classes

Currently, whenever things go wrong we throw a generic error with a message. However, clients may want to react differently to different types of errors, ex. an expired token vs the model not found. Possibilities are:

  • TokenMaster::TokenNotFound
  • TokenMaster::TokenableComplete
  • TokenMaster::TokenExpired
  • TokenMaster::TokenSent
  • TokenMaster::TokenableDoesNotExist
    ...

Add ability to resend instructions

send_x_instructions! errs out (intentionally) when the instructions have already been sent. However, we commonly need to implement a "resend invitation" feature since recipients often miss the invitation the first time around.

I can think of two approaches to this "resending" functionality:

  1. Extend the existing token: set x_created_at to Time.now
  2. Reset the token: generate a new one and update x_created_at, etc.

Perhaps we actually implement the following two methods:

extend_x_token!
send_new_x_token!

Thoughts @inveterateliterate?

Update Ruby Version

Project is currently on Ruby V 2.3.0, which reached its end of life in 2019.

Add ability to 'force complete'

Sometimes one needs to 'force' a particular state, ex. when completing an 'invite', I also want to 'confirm' the user. Perhaps there could be a method force_<tokenable> that would do this. This would also be helpful for testing/seed data.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.