koombea/fuzzy_where

View on GitHub
lib/fuzzy_where/fuzzy_relation_builder.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'fuzzy_where/fuzzy_derivation'
require 'fuzzy_where/predicate_membership_degree'

# SQLf implementation for ActiveRecord
module FuzzyWhere
  # Class to build and {ActiveRecord_Relation} based on fuzzy conditions
  class FuzzyRelationBuilder
    class << self
      # Is sqlite3 validation
      def sqlite3?
        active_record_adapter == 'sqlite3'.freeze
      end

      private

      # ActiveRecord adapter
      def active_record_adapter
        ActiveRecord::Base.connection.instance_values['config'][:adapter]
      end
    end
    # @!attribute [r] relation
    #   @return [ActiveRecord_Relation] the current standard query
    attr_reader :relation

    # New FuzzyRelationBuilder intance
    # @param table [String] table name
    # @param relation [ActiveRecord_Relation] query to append
    # @param conditions [Hash] fuzzy conditions
    def initialize(table, relation, conditions)
      @table = table
      @conditions = conditions
      @relation = relation
      @calibration = calibration
      @membership_degrees = []
    end

    # Build an ActiveRecord relation based on fuzzy conditions
    #
    # @return [ActiveRecord_Relation] the final standard query
    def build
      process_conditions
      add_membership_column
      add_calibration_column
      order_by_membership_degree
    end

    private

    def calibration
      @calibration = @conditions.delete(FuzzyWhere.config.calibration_name)
      @calibration ||= 0.5
      validate_calibration
    end

    def validate_calibration
      Float @calibration
    rescue
      raise ArgumentError,
            "calibration must be a Float, got #{@calibration.inspect}"
    end

    def process_conditions
      @conditions.each do |column, predicate|
        pred_def = load_fuzzy_predicate_definition(predicate)
        @relation = derivate_condition(column, pred_def)
        membership_degree_function(column, pred_def)
      end
      @relation
    end

    def load_fuzzy_predicate_definition(predicate)
      pred_def = FuzzyWhere.config.fuzzy_predicate(predicate)
      raise FuzzyError, "couldn't find fuzzy definition" unless pred_def
      pred_def
    end

    def derivate_condition(column, pred_def)
      FuzzyDerivation.new(@relation, @table, column, pred_def)
                     .derivative_condition
    end

    def membership_degree_function(column, pred_def)
      membership_degree = PredicateMembershipDegree.new(@table,
                                                        column,
                                                        pred_def)
      @membership_degrees << membership_degree.membership_function
    end

    def add_membership_column
      name = FuzzyWhere.config.membership_degree_column_name
      @relation = @relation
                  .select("#{@table}.*, (#{membership_degree}) AS #{name}")
    end

    def add_calibration_column
      @relation = @relation.where("(#{membership_degree}) >= ?", @calibration)
    end

    def membership_degree
      if @membership_degrees.size > 1
        min_membership_degree
      else
        @membership_degrees.first
      end
    end

    # Use apropiate SQL method for minimun value
    def min_membership_degree
      if FuzzyRelationBuilder.sqlite3?
        "MIN(#{@membership_degrees.join(',')})"
      else
        "LEAST(#{@membership_degrees.join(',')})"
      end
    end

    def order_by_membership_degree
      name = FuzzyWhere.config.membership_degree_column_name
      @relation.order("#{name} DESC")
    end
  end
end