matthutchinson/acts_as_textcaptcha

lib/acts_as_textcaptcha/textcaptcha.rb
A

Cyclomatic complexity for acts_as_textcaptcha is too high. [8/6]
Fixed

Severity: Minor
Found in lib/acts_as_textcaptcha/textcaptcha.rb by rubocop

This cop checks that the cyclomatic complexity of methods is not higher than the configured maximum. The cyclomatic complexity is the number of linearly independent paths through a method. The algorithm counts decision points and adds one.

An if statement (or unless or ?:) increases the complexity by one. An else branch does not, since it doesn't add a decision point. The && operator (or keyword and) can be converted to a nested if statement, and ||/or is shorthand for a sequence of ifs, so they also add one. Loops can be said to have an exit condition, so they add one.

There are no issues that match your filters.

Category
Status
require 'yaml'
require 'net/http'
require 'digest/md5'
require 'acts_as_textcaptcha/textcaptcha_cache'
require 'acts_as_textcaptcha/textcaptcha_api'

module ActsAsTextcaptcha

  module Textcaptcha #:nodoc:

    def acts_as_textcaptcha(options = nil)
      cattr_accessor :textcaptcha_config
      attr_accessor  :textcaptcha_question, :textcaptcha_answer, :textcaptcha_key

      # Rails 3, ensure these attrs are accessible
      if respond_to?(:accessible_attributes) && respond_to?(:attr_accessible)
        attr_accessible :textcaptcha_answer, :textcaptcha_key
      end

      validate :validate_textcaptcha, :if => :perform_textcaptcha?

      if options.is_a?(Hash)
        self.textcaptcha_config = options.symbolize_keys!
      else
        begin
          self.textcaptcha_config = YAML.load(File.read("#{Rails.root ? Rails.root.to_s : '.'}/config/textcaptcha.yml"))[Rails.env].symbolize_keys!
        rescue
          raise ArgumentError.new('could not find any textcaptcha options, in config/textcaptcha.yml or model - run rake textcaptcha:config to generate a template config file')
        end
      end

      include InstanceMethods
    end


    module InstanceMethods

      # override this method to toggle textcaptcha checking
      # by default this will only allow new records to be
      # protected with textcaptchas
      def perform_textcaptcha?
        !respond_to?('new_record?') || new_record?
      end

      # generate and assign textcaptcha
      def textcaptcha
        if perform_textcaptcha? && textcaptcha_config
          question = answers = nil

          # get textcaptcha from api
          if textcaptcha_config[:api_key]
            question, answers = TextcaptchaApi.fetch(textcaptcha_config[:api_key], textcaptcha_config)
          end

          # fall back to config based textcaptcha
          unless question && answers
            question, answers = textcaptcha_config_questions
          end

          if question && answers
            assign_textcaptcha(question, answers)
          end
        end
      end


      private

      def textcaptcha_config_questions
        if textcaptcha_config[:questions]
          random_question = textcaptcha_config[:questions][rand(textcaptcha_config[:questions].size)].symbolize_keys!
          [random_question[:question], (random_question[:answers] || '').split(',').map!{ |answer| safe_md5(answer) }]
        end
      end


      # check textcaptcha, if incorrect, regenerate a new textcaptcha
      def validate_textcaptcha
        valid_answers = textcaptcha_cache.read(textcaptcha_key) || []
        reset_textcaptcha
        if valid_answers.include?(safe_md5(textcaptcha_answer))
          # answer was valid, mutate the key again
          self.textcaptcha_key = textcaptcha_random_key
          textcaptcha_cache.write(textcaptcha_key, valid_answers, textcaptcha_cache_options)
          true
        else
          if valid_answers.empty?
            # took too long to answer
            errors.add(:textcaptcha_answer, :expired, :message => 'was not submitted quickly enough, try another question instead')
          else
            # incorrect answer
            errors.add(:textcaptcha_answer, :incorrect, :message => 'is incorrect, try another question instead')
          end
          textcaptcha
          false
        end
      end

      def reset_textcaptcha
        if textcaptcha_key
          textcaptcha_cache.delete(textcaptcha_key)
          self.textcaptcha_key = nil
        end
      end

      def assign_textcaptcha(question, answers)
        self.textcaptcha_question = question
        self.textcaptcha_key      = textcaptcha_random_key
        textcaptcha_cache.write(textcaptcha_key, answers, textcaptcha_cache_options)
      end

      # strip whitespace pass through mb_chars (a multibyte
      # safe proxy for string methods) then downcase
      def safe_md5(answer)
        Digest::MD5.hexdigest(answer.to_s.strip.mb_chars.downcase)
      end

      # a random cache key, time based and random
      def textcaptcha_random_key
        safe_md5(Time.now.to_i + rand(1_000_000))
      end

      def textcaptcha_cache_options
        if textcaptcha_config[:cache_expiry_minutes]
          { :expires_in => textcaptcha_config[:cache_expiry_minutes].to_f.minutes }
        else
          {}
        end
      end

      # cache is used to persist textcaptcha questions and answers
      # between requests
      def textcaptcha_cache
        @@textcaptcha_cache ||= TextcaptchaCache.new
      end
    end
  end
end

Size

Lines of code
138