# frozen_string_literal: true module Gitlab module ImportExport module Base # Base class for Group & Project Object Builders. # This class is not intended to be used on its own but # rather inherited from. # # Cache keeps 1000 entries at most, 1000 is chosen based on: # - one cache entry uses around 0.5K memory, 1000 items uses around 500K. # (leave some buffer it should be less than 1M). It is afforable cost for project import. # - for projects in Gitlab.com, it seems 1000 entries for labels/milestones is enough. # For example, gitlab has ~970 labels and 26 milestones. LRU_CACHE_SIZE = 1000 class ObjectBuilder def self.build(*args) new(*args).find end def initialize(klass, attributes) @klass = klass.ancestors.include?(Label) ? Label : klass @attributes = attributes if Gitlab::SafeRequestStore.active? @lru_cache = cache_from_request_store @cache_key = [klass, attributes] end end def find find_with_cache do find_object || klass.create(prepare_attributes) end end protected def where_clauses raise NotImplementedError end # attributes wrapped in a method to be # adjusted in sub-class if needed def prepare_attributes attributes end private attr_reader :klass, :attributes, :lru_cache, :cache_key def find_with_cache return yield unless lru_cache && cache_key lru_cache[cache_key] ||= yield end def cache_from_request_store Gitlab::SafeRequestStore[:lru_cache] ||= LruRedux::Cache.new(LRU_CACHE_SIZE) end def find_object klass.find_by(where_clause) end def where_clause where_clauses.reduce(:and) end def table @table ||= klass.arel_table end # Returns Arel clause: # `"{table_name}"."{attrs.keys[0]}" = '{attrs.values[0]} AND {table_name}"."{attrs.keys[1]}" = '{attrs.values[1]}"` # from the given Hash of attributes. def attrs_to_arel(attrs) attrs.map do |key, value| table[key].eq(value) end.reduce(:and) end # Returns Arel clause `"{table_name}"."title" = '{attributes['title']}'` # if attributes has 'title key, otherwise `nil`. def where_clause_for_title attrs_to_arel(attributes.slice('title')) end # Returns Arel clause `"{table_name}"."description" = '{attributes['description']}'` # if attributes has 'description key, otherwise `nil`. def where_clause_for_description attrs_to_arel(attributes.slice('description')) end # Returns Arel clause `"{table_name}"."created_at" = '{attributes['created_at']}'` # if attributes has 'created_at key, otherwise `nil`. def where_clause_for_created_at attrs_to_arel(attributes.slice('created_at')) end end end end end