fnando/tokens

View on GitHub
lib/tokens/active_record.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Tokens
  module ActiveRecord
    def self.included(base)
      base.class_eval { extend  ClassMethods }
    end

    module Serializer
      class << self
        # Set the serializer adapter. Defaults to JSON.
        attr_accessor :adapter
      end

      require "json"
      self.adapter = ::JSON

      def self.load(data)
        data ? JSON.load(data) : {}
      end

      def self.dump(data)
        data ? JSON.dump(data) : nil
      end
    end

    module ClassMethods
      # Set up model for using tokens.
      #
      #   class User < ActiveRecord::Base
      #     tokenizable
      #   end
      #
      def tokenizable
        has_many :tokens, as: "tokenizable", dependent: :destroy
        include InstanceMethods
      end

      # Generate token with specified length.
      #
      #   User.generate_token(10)
      #
      def generate_token(size)
        validity = Proc.new {|token| Token.where(:token => token).first.nil?}

        begin
          token = SecureRandom.hex(size)[0, size]
          token = token.encode("UTF-8")
        end while validity[token] == false

        token
      end

      # Find a token
      #
      #   User.find_token(:activation, "abcdefg")
      #   User.find_token(name: activation, token: "abcdefg")
      #   User.find_token(name: activation, token: "abcdefg", tokenizable_id: 1)
      #
      def find_token(*args)
        if args.first.kind_of?(Hash)
          options = args.first
        else
          options = {
            name: args.first,
            token: args.last
          }
        end

        options.merge!(name: options[:name].to_s, tokenizable_type: self.name)
        Token.where(options).includes(:tokenizable).first
      end

      # Find object by token.
      #
      #   User.find_by_token(:activation, "abcdefg")
      #
      def find_by_token(name, hash)
        token = find_token(name: name.to_s, token: hash)
        return unless token
        token.tokenizable
      end

      # Find object by valid token (same name, same hash, not expired).
      #
      #   User.find_by_valid_token(:activation, "abcdefg")
      #
      def find_by_valid_token(name, hash)
        token = find_token(name: name.to_s, token: hash)
        return if !token || token.expired?
        token.tokenizable
      end
    end

    module InstanceMethods
      # Verify if given token is valid.
      #
      #   @user.valid_token?(:active, "abcdefg")
      #
      def valid_token?(name, hash)
        self.tokens.where(name: name.to_s, token: hash.to_s).first != nil
      end

      # Find a token.
      #
      #   @user.find_token(:activation, "abcdefg")
      #
      def find_token(name, token)
        self.class.find_token(
          tokenizable_id: self.id,
          name: name.to_s,
          token: token
        )
      end

      # Find token by its name.
      def find_token_by_name(name)
        self.tokens.where(name: name.to_s).first
      end

      # Return <tt>Token</tt> instance when token is valid.
      def find_valid_token(name, token)
        token = find_token(name, token)
        return unless token
        !token.expired? && token
      end

      # Remove token.
      #
      #   @user.remove_token(:activate)
      #
      def remove_token(name)
        return if new_record?
        token = find_token_by_name(name)
        token && token.destroy
      end

      # Add a new token.
      #
      #   @user.add_token(:api_key, token: 'abc123')
      #   @user.add_token(:api_key, expires_at: nil)
      #   @user.add_token(:api_key, size: 20)
      #   @user.add_token(:api_key, data: {when: Time.now})
      #
      def add_token(name, options={})
        options.reverse_merge!({
          expires_at: 2.days.from_now,
          size: 12,
          data: nil
        })

        remove_token(name)
        attrs = {
          name: name.to_s,
          token: options[:token] || self.class.generate_token(options[:size]),
          expires_at: options[:expires_at],
          data: options.fetch(:data) || {}
        }

        self.tokens.create!(attrs)
      end
    end
  end
end