apeeyush/Data-Analytics-Log-Manager

View on GitHub
app/models/log.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# == Schema Information
#
# Table name: logs
#
#  id          :integer          not null, primary key
#  session     :string(255)
#  username    :string(255)
#  application :string(255)
#  activity    :string(255)
#  event       :string(255)
#  time        :datetime
#  parameters  :hstore
#  extras      :hstore
#  created_at  :datetime
#  updated_at  :datetime
#  event_value :string(255)
#

class Log < ActiveRecord::Base

  # Select logs for user based on user's application list
  scope :access_filter, lambda { |user|
      where(application: user.applications.pluck(:name) )
  }

  # Returns a hash with key as column_type and value as lists of names of that column_type 
  #
  # Example:
  #   Log.column_lists
  #   Returns:
  #     {
  #       "string_columns"=>["session", "username", "application", "activity", "event"],
  #       "time_columns"=>["time", "created_at", "updated_at"], 
  #       "hstore_columns"=>"parameters || extras"
  #     }
  def self.column_lists
    string_columns, time_columns, hstore_concat_string = [], [], ""
    columns_hash = Hash.new
    Log.columns.each do |column|
      columns_hash[column.name] = column.type
    end
    columns_hash.except!("id")
    columns_hash.each do |column_name, type|
      if type == :string
        string_columns << column_name
      elsif type == :datetime
        time_columns << column_name
      elsif type == :hstore
        hstore_concat_string == "" ? hstore_concat_string << column_name : hstore_concat_string << " || " + column_name
      end
    end
    logs_columns = Hash.new
    logs_columns["string_columns"] = string_columns
    logs_columns["time_columns"] = time_columns
    logs_columns["hstore_columns"] = hstore_concat_string
    return logs_columns
  end

  # Takes a key, returns the corresponding value if key is a column name, otherwise 
  # searches parameters and extras for presence of key, and if present, returns the
  # corresponding value.
  #
  # Example:
  #  log = Log.first; session_value = log.value("session");
  def value(key)
      if self[key].present?
      self[key].class != ActiveSupport::TimeWithZone ? self[key] : self[key].to_i
    elsif self[:parameters].present? && self[:parameters][key].present?
      return self[:parameters][key]
    elsif self[:extras].present? && self[:extras][key].present?
      return self[:extras][key]
      else
          return ""
      end
  end

  def update_value(key, value)
    new_log = self
    logs_columns = Log.column_lists
    if logs_columns["string_columns"].include? key #|| logs_columns["time_columns"].include? key
      new_log[key] = value
    else
      new_log[:parameters][key] = value
    end
    return new_log
  end

  # Filters data having specified values/range for the keys
  #
  # Example JSON Body:
  #   [
  #     {
  #       "key" : "username",
  #       "list" : ["peeyush", "apeeyush"]
  #     },
  #     {
  #       "key" : "time",
  #       "start_time" : "2014-02-25",
  #       "end_time" : "2014-10-29"
  #     },
  #     {
  #       "key" : "color"
  #       "remove" : true,                                  //Optional (For filter out)
  #       "list" : ["green"]
  #     }
  #   ]
  #   logs.filter(body)
  def self.filter(filter_list)
    logs = self
    logs_columns = Log.column_lists
    string_columns = logs_columns["string_columns"]
    time_columns = logs_columns["time_columns"]
    hstore_columns = logs_columns["hstore_columns"]
    filter_list.each do |filter|
      key = filter["key"]
      if string_columns.include? key
        if filter["remove"] == true
          logs = logs.where.not({ key => filter["list"]})
        else
          logs = logs.where({ key => filter["list"]})
        end
      elsif time_columns.include? key
        if filter["start_time"].present? && !filter["end_time"].present?
          logs = logs.where("#{key} >= :start_time",{start_time: filter["start_time"]})
        elsif filter["end_time"].present? && !filter["start_time"].present?
          logs = logs.where("#{key} <= :end_time",{end_time: filter["end_time"]})
        elsif filter["end_time"].present? && filter["start_time"].present?
          logs = logs.where("#{key} >= :start_time AND #{key} <= :end_time",{start_time: filter["start_time"], end_time: filter["end_time"]})
        end
      else
        if filter["remove"] == true
          logs = logs.where("#{hstore_columns} -> :key NOT IN ( :list )", :key => key, :list => filter["list"])
        else
          logs = logs.where("#{hstore_columns} -> :key IN ( :list )", :key => key, :list => filter["list"])
        end
      end
    end
    return logs
  end

  # Filters data having specified keys
  #
  # Example JSON Body:
  #   {
  #     "keys_list" : ["event","color"]
  #   }
  def self.filter_having_keys(filter)
    logs = self
    logs_columns = Log.column_lists
    string_columns = logs_columns["string_columns"]
    time_columns = logs_columns["time_columns"]
    hstore_columns = logs_columns["hstore_columns"]
    if filter["keys_list"].present?
      list = filter["keys_list"]
      additional_keys = []
      list.each do |key|
        if string_columns.include? key or time_columns.include? key
          logs = logs.where("#{key} IS NOT NULL")
        else
          additional_keys << key
        end
      end
      if additional_keys.any?
        logs = logs.where("#{hstore_columns} ?& ARRAY[:additional_keys]", additional_keys: additional_keys)
      end
    end
    return logs
  end

  # Returns the list of keys for logs except id
  #
  # Example:
  #   Log.keys_list
  def self.keys_list
    ids = all.pluck(:id)
    list = Log.column_names - %w{id parameters extras}
    if ids.size > 0
      list << (Log.connection.execute("SELECT DISTINCT (each(parameters)).key FROM logs WHERE id in (#{ids.join(',')})").values.flatten rescue [])
      list << (Log.connection.execute("SELECT DISTINCT (each(extras)).key     FROM logs WHERE id in (#{ids.join(',')})").values.flatten rescue [])
    end
    list.flatten.uniq
  end

  def satisfies_conditions(conditions)
    conditions.each do |key, value|
      return false if value(key) != value
    end
    return true
  end

end