<commit_before>module ManagerRefresh
  class InventoryObject
    attr_accessor :object, :id
    attr_reader :inventory_collection, :data

    delegate :manager_ref, :base_class_name, :to => :inventory_collection
    delegate :[], :to => :data

    def initialize(inventory_collection, data)
      @inventory_collection     = inventory_collection
      @data                     = data
      @object                   = nil
      @id                       = nil
      @allowed_attributes_index = nil
    end

    def manager_uuid
      manager_ref.map { |attribute| data[attribute].try(:id) || data[attribute].to_s }.join("__")
    end

    def load
      self
    end

    def attributes(inventory_collection_scope = nil)
      # TODO(lsmola) mark method with !, for performance reasons, this methods can be called only once, the second
      # call will not return saveable result. We do not want to cache the result, since we want the lowest memory
      # footprint.

      # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
      # different blacklist and whitelist. The generic code always passes a scope.
      inventory_collection_scope ||= inventory_collection

      attributes_for_saving = {}
      # First transform the values
      data.each do |key, value|
        if !allowed?(inventory_collection_scope, key)
          next
        elsif loadable?(value)
          # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
          # result
          data[key] = value.load
          if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
            # We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
            record_id                          = data[key].try(:id)
            attributes_for_saving[foreign_key] = record_id

            if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
              # If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
              # if record_id is missing
              attributes_for_saving[foreign_type] = record_id ? data[key].base_class_name : nil
            end
          elsif data[key].kind_of?(::ManagerRefresh::InventoryObject)
            # We have an association to fill but not an Activerecord association, so e.g. Ancestry, lets just load
            # it here. This way of storing ancestry is ineffective in DB call count, but RAM friendly
            attributes_for_saving[key] = data[key].base_class_name.constantize.find_by(:id => data[key].id)
          else
            # We have a normal attribute to fill
            attributes_for_saving[key] = data[key]
          end
        elsif value.kind_of?(Array) && value.any? { |x| loadable?(x) }
          # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
          # result
          data[key]                                            = value.compact.map(&:load).compact
          # We can use built in _ids methods to assign array of ids into has_many relations. So e.g. the :key_pairs=
          # relation setter will become :key_pair_ids=
          attributes_for_saving[key.to_s.singularize + "_ids"] = data[key].map(&:id).compact
        else
          attributes_for_saving[key] = value
        end
      end

      attributes_for_saving
    end

    def to_s
      "InventoryObject:('#{manager_uuid}', #{inventory_collection})"
    end

    def inspect
      to_s
    end

    def dependency?
      !inventory_collection.saved?
    end

    def []=(key, value)
      data[key] = value
      inventory_collection.actualize_dependency(key, value)
      value
    end

    private

    def association?(inventory_collection_scope, key)
      # Is the key an association on inventory_collection_scope model class?
      !inventory_collection_scope.association_to_foreign_key_mapping[key].nil?
    end

    def allowed?(inventory_collection_scope, key)
      foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] ||
                               inventory_collection_scope.foreign_type_to_association_mapping[key]<commit_msg>Make method like interface instead of hashes<commit_after>module ManagerRefresh
  class InventoryObject
    attr_accessor :object, :id
    attr_reader :inventory_collection, :data

    delegate :manager_ref, :base_class_name, :model_class, :to => :inventory_collection
    delegate :[], :to => :data

    def initialize(inventory_collection, data)
      @inventory_collection     = inventory_collection
      @data                     = data
      @object                   = nil
      @id                       = nil
      @allowed_attributes_index = nil
    end

    def manager_uuid
      manager_ref.map { |attribute| data[attribute].try(:id) || data[attribute].to_s }.join("__")
    end

    def load
      self
    end

    def attributes(inventory_collection_scope = nil)
      # TODO(lsmola) mark method with !, for performance reasons, this methods can be called only once, the second
      # call will not return saveable result. We do not want to cache the result, since we want the lowest memory
      # footprint.

      # We should explicitly pass a scope, since the inventory_object can be mapped to more InventoryCollections with
      # different blacklist and whitelist. The generic code always passes a scope.
      inventory_collection_scope ||= inventory_collection

      attributes_for_saving = {}
      # First transform the values
      data.each do |key, value|
        if !allowed?(inventory_collection_scope, key)
          next
        elsif loadable?(value)
          # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
          # result
          data[key] = value.load
          if (foreign_key = inventory_collection_scope.association_to_foreign_key_mapping[key])
            # We have an association to fill, lets fill also the :key, cause some other InventoryObject can refer to it
            record_id                          = data[key].try(:id)
            attributes_for_saving[foreign_key] = record_id

            if (foreign_type = inventory_collection_scope.association_to_foreign_type_mapping[key])
              # If we have a polymorphic association, we need to also fill a base class name, but we want to nullify it
              # if record_id is missing
              attributes_for_saving[foreign_type] = record_id ? data[key].base_class_name : nil
            end
          elsif data[key].kind_of?(::ManagerRefresh::InventoryObject)
            # We have an association to fill but not an Activerecord association, so e.g. Ancestry, lets just load
            # it here. This way of storing ancestry is ineffective in DB call count, but RAM friendly
            attributes_for_saving[key] = data[key].base_class_name.constantize.find_by(:id => data[key].id)
          else
            # We have a normal attribute to fill
            attributes_for_saving[key] = data[key]
          end
        elsif value.kind_of?(Array) && value.any? { |x| loadable?(x) }
          # Lets fill also the original data, so other InventoryObject referring to this attribute gets the right
          # result
          data[key]                                            = value.compact.map(&:load).compact
          # We can use built in _ids methods to assign array of ids into has_many relations. So e.g. the :key_pairs=
          # relation setter will become :key_pair_ids=
          attributes_for_saving[key.to_s.singularize + "_ids"] = data[key].map(&:id).compact
        else
          attributes_for_saving[key] = value
        end
      end

      attributes_for_saving
    end

    def to_s
      "InventoryObject:('#{manager_uuid}', #{inventory_collection})"
    end

    def inspect
      to_s
    end

    def dependency?
      !inventory_collection.saved?
    end

    def []=(key, value)
      data[key] = value
      inventory_collection.actualize_dependency(key, value)
      value
    end

    def allowed_writers
      return [] unless model_class

      # Get all writers of a model
      @allowed_writers ||= (model_class.new.methods - Object.methods).grep(/^[\w]+?\=$/)
    end

    def allowed_readers
      return [] unless model_class

      # Get all readers inferred from writers of a model
      @allowed_readers ||= allowed_writers.map { |x| x.to_s.gsub("=", "").to_sym }
    end

    def method_missing(method_name, *arguments, &block)
      if allowed_writers.include?(method_name)
        self.class.define_data_writer(method_name)
        send(method_name, arguments[0])
      elsif allowed_readers.include?(method_name)
        self.class.define_data_reader(method_name)
        send(method_name)
      else
        super
      end
    end

    def respond_to_missing?(method_name, _include_private = false)
      allowed_writers.include?(method_name) || allowed_readers.include?(method_name) || super
    end

    def self.define_data_writer(data_key)
      define_method(data_key) do |value|
        self.send(:[]=, data_key.to_s.gsub("=", "").to_sym, value)
      end
    end

    def self.define_data_reader(data_key)
      define_method(data_key) do
        self.send(:[], data_key)
      end
    end

    private

    def association?(inventory_collection_scope, key)
      # Is the key an association on inventory_collection_scope model class?
      !inventory_collection_scope.association_to_foreign_key_mapping[key].nil?
    end

    def allowed?(inventory_collection_scope, key)
      foreign_to_association = inventory_collection_scope.foreign_key_to_association_mapping[key] ||
                               inventory_collection_scope.foreign_type_to_association_mapping[key]
{'fraction_non_alphanumeric': 0.07306532663316583, 'fraction_numerical': 0.00010050251256281407, 'mean_word_length': 2.5400213447171827, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5992303', 'n_tokens_mistral': 2765, 'n_tokens_neox': 2666, 'n_words': 797}