class InventoryRefresh::SaveCollection::Saver::Base

Attributes

arel_primary_key[R]
association[R]
batch_size[R]
batch_size_for_persisting[R]
deserializable_keys[R]
inventory_collection[R]
model_class[R]
pg_types[R]
primary_key[R]
q_table_name[R]
record_key_method[R]
select_keys[R]
select_keys_indexes[R]
serializable_keys[R]
table_name[R]
unique_db_indexes[R]
unique_db_primary_keys[R]
unique_index_keys[R]
unique_index_keys_to_s[R]

Public Class Methods

new(inventory_collection) click to toggle source

@param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we will be saving

# File lib/inventory_refresh/save_collection/saver/base.rb, line 13
def initialize(inventory_collection)
  @inventory_collection = inventory_collection
  @association = inventory_collection.db_collection_for_comparison

  # Private attrs
  @model_class            = inventory_collection.model_class
  @table_name             = @model_class.table_name
  @q_table_name           = get_connection.quote_table_name(@table_name)
  @primary_key            = @model_class.primary_key
  @arel_primary_key       = @model_class.arel_attribute(@primary_key)
  @unique_index_keys      = inventory_collection.unique_index_keys
  @unique_index_keys_to_s = inventory_collection.manager_ref_to_cols.map(&:to_s)
  @select_keys            = [@primary_key] + @unique_index_keys_to_s + internal_columns.map(&:to_s)
  @unique_db_primary_keys = Set.new
  @unique_db_indexes      = Set.new

  @batch_size_for_persisting = inventory_collection.batch_size_pure_sql
  @batch_size                = inventory_collection.use_ar_object? ? @batch_size_for_persisting : inventory_collection.batch_size

  @record_key_method   = inventory_collection.pure_sql_record_fetching? ? :pure_sql_record_key : :ar_record_key
  @select_keys_indexes = @select_keys.each_with_object({}).with_index { |(key, obj), index| obj[key.to_s] = index }
  @pg_types            = @model_class.attribute_names.each_with_object({}) do |key, obj|
    obj[key.to_sym] = inventory_collection.model_class.columns_hash[key]
                                          .try(:sql_type_metadata)
                                          .try(:instance_values)
                                          .try(:[], "sql_type")
  end

  @serializable_keys = {}
  @deserializable_keys = {}
  @model_class.attribute_names.each do |key|
    attribute_type = @model_class.type_for_attribute(key.to_s)
    pg_type        = @pg_types[key.to_sym]

    if inventory_collection.use_ar_object?
      # When using AR object, lets make sure we type.serialize(value) every value, so we have a slow but always
      # working way driven by a configuration
      @serializable_keys[key.to_sym] = attribute_type
      @deserializable_keys[key.to_sym] = attribute_type
    elsif attribute_type.respond_to?(:coder) ||
          attribute_type.type == :int4range ||
          attribute_type.type == :jsonb ||
          pg_type == "text[]" ||
          pg_type == "character varying[]"
      # Identify columns that needs to be encoded by type.serialize(value), it's a costy operations so lets do
      # do it only for columns we need it for.
      # TODO: should these set @deserializable_keys too?
      @serializable_keys[key.to_sym] = attribute_type
    elsif attribute_type.type == :decimal
      # Postgres formats decimal columns with fixed number of digits e.g. '0.100'
      # Need to parse and let Ruby format the value to have a comparable string.
      @serializable_keys[key.to_sym] = attribute_type
      @deserializable_keys[key.to_sym] = attribute_type
    end
  end
end

Public Instance Methods

save_inventory_collection!() click to toggle source

Saves the InventoryCollection

# File lib/inventory_refresh/save_collection/saver/base.rb, line 71
def save_inventory_collection!
  # Create/Update/Archive/Delete records based on InventoryCollection data and scope
  save!(association)
end

Protected Instance Methods

transform_to_hash!(all_attribute_keys, hash) click to toggle source
# File lib/inventory_refresh/save_collection/saver/base.rb, line 104
def transform_to_hash!(all_attribute_keys, hash)
  if serializable_keys?
    values_for_database!(all_attribute_keys,
                         hash)
  else
    hash
  end
end
values_for_database!(all_attribute_keys, attributes) click to toggle source

Applies serialize method for each relevant attribute, which will cast the value to the right type.

@param all_attribute_keys [Symbol] attribute keys we want to process @param attributes [Hash] attributes hash @return [Hash] modified hash from parameter attributes with casted values

# File lib/inventory_refresh/save_collection/saver/base.rb, line 91
def values_for_database!(all_attribute_keys, attributes)
  # TODO(lsmola) we'll need to fill default value from the DB to the NOT_NULL columns here, since sending NULL
  # to column with NOT_NULL constraint always fails, even if there is a default value
  all_attribute_keys.each do |key|
    next unless attributes.key?(key)

    if (type = serializable_keys[key])
      attributes[key] = type.serialize(attributes[key])
    end
  end
  attributes
end

Private Instance Methods

assert_distinct_relation(primary_key_value) click to toggle source

Check if relation provided is distinct, i.e. the relation should not return the same primary key value twice.

@param primary_key_value [Bigint] primary key value @raise [Exception] if env is not production and relation is not distinct @return [Boolean] false if env is production and relation is not distinct

# File lib/inventory_refresh/save_collection/saver/base.rb, line 132
def assert_distinct_relation(primary_key_value)
  if unique_db_primary_keys.include?(primary_key_value) # Include on Set is O(1)
    # Change the InventoryCollection's :association or :arel parameter to return distinct results. The :through
    # relations can return the same record multiple times. We don't want to do SELECT DISTINCT by default, since
    # it can be very slow.
    unless inventory_collection.assert_graph_integrity
      logger.warn("Please update :association or :arel for #{inventory_collection} to return a DISTINCT result. "\
                  " The duplicate value is being ignored.")
      return false
    else
      raise("Please update :association or :arel for #{inventory_collection} to return a DISTINCT result. ")
    end
  else
    unique_db_primary_keys << primary_key_value
  end
  true
end
assert_referential_integrity(hash) click to toggle source

Check that the needed foreign key leads to real value. This check simulates NOT NULL and FOREIGN KEY constraints we should have in the DB. The needed foreign keys are identified as fixed_foreign_keys, which are the foreign keys needed for saving of the record.

@param hash [Hash] data we want to save @raise [Exception] if env is not production and a foreign_key is missing @return [Boolean] false if env is production and a foreign_key is missing

# File lib/inventory_refresh/save_collection/saver/base.rb, line 157
def assert_referential_integrity(hash)
  inventory_collection.fixed_foreign_keys.each do |x|
    next unless hash[x].nil?
    subject = "#{hash} of #{inventory_collection} because of missing foreign key #{x} for "\
              "#{inventory_collection.parent.class.name}:"\
              "#{inventory_collection.parent.try(:id)}"
    unless inventory_collection.assert_graph_integrity
      logger.warn("Referential integrity check violated, ignoring #{subject}")
      return false
    else
      raise("Referential integrity check violated for #{subject}")
    end
  end
  true
end
assign_attributes_for_create!(hash, create_time) click to toggle source

Enriches data hash with timestamp and type columns

@param hash [Hash] data hash @param create_time [Time] data hash

# File lib/inventory_refresh/save_collection/saver/base.rb, line 197
def assign_attributes_for_create!(hash, create_time)
  hash[:created_on]   = create_time if supports_column?(:created_on)
  hash[:created_at]   = create_time if supports_column?(:created_at)
  assign_attributes_for_update!(hash, create_time)
end
assign_attributes_for_update!(hash, update_time) click to toggle source

Enriches data hash with timestamp columns

@param hash [Hash] data hash @param update_time [Time] data hash

# File lib/inventory_refresh/save_collection/saver/base.rb, line 187
def assign_attributes_for_update!(hash, update_time)
  hash[:type]         = model_class.name if supports_sti? && hash[:type].nil?
  hash[:updated_on]   = update_time if supports_column?(:updated_on)
  hash[:updated_at]   = update_time if supports_column?(:updated_at)
end
internal_columns() click to toggle source
# File lib/inventory_refresh/save_collection/saver/base.rb, line 203
def internal_columns
  @internal_columns ||= inventory_collection.internal_columns
end
inventory_collection_details() click to toggle source

@return [String] a string for logging purposes

# File lib/inventory_refresh/save_collection/saver/base.rb, line 123
def inventory_collection_details
  "strategy: #{inventory_collection.strategy}, saver_strategy: #{inventory_collection.saver_strategy}"
end
serializable_keys?() click to toggle source

@return [Boolean] true if any serializable keys are present

# File lib/inventory_refresh/save_collection/saver/base.rb, line 234
def serializable_keys?
  @serializable_keys_bool_cache ||= serializable_keys.present?
end
supports_remote_data_timestamp?(all_attribute_keys) click to toggle source

@return [Boolean] true if the keys we are saving have resource_timestamp column

# File lib/inventory_refresh/save_collection/saver/base.rb, line 239
def supports_remote_data_timestamp?(all_attribute_keys)
  all_attribute_keys.include?(:resource_timestamp) # include? on Set is O(1)
end
supports_remote_data_version?(all_attribute_keys) click to toggle source

@return [Boolean] true if the keys we are saving have resource_counter column

# File lib/inventory_refresh/save_collection/saver/base.rb, line 244
def supports_remote_data_version?(all_attribute_keys)
  all_attribute_keys.include?(:resource_counter) # include? on Set is O(1)
end
supports_resource_version?(all_attribute_keys) click to toggle source

@return [Boolean] true if the keys we are saving have resource_version column, which solves for a quick check

if the record was modified
# File lib/inventory_refresh/save_collection/saver/base.rb, line 250
def supports_resource_version?(all_attribute_keys)
  all_attribute_keys.include?(resource_version_column) # include? on Set is O(1)
end
supports_sti?() click to toggle source

@return [Boolean] true if the model_class supports STI

# File lib/inventory_refresh/save_collection/saver/base.rb, line 229
def supports_sti?
  @supports_sti_cache ||= inventory_collection.supports_sti?
end
time_now() click to toggle source

@return [Time] A rails friendly time getting config from ActiveRecord::Base.default_timezone (can be :local

or :utc)
# File lib/inventory_refresh/save_collection/saver/base.rb, line 175
def time_now
  if ActiveRecord::Base.default_timezone == :utc
    Time.now.utc
  else
    Time.zone.now
  end
end
unique_index_columns() click to toggle source

@return [Array<Symbol>] all columns that are part of the best fit unique index

# File lib/inventory_refresh/save_collection/saver/base.rb, line 217
def unique_index_columns
  @unique_index_columns ||= inventory_collection.unique_index_columns
end
unique_index_columns_to_s() click to toggle source

@return [Array<String>] all columns that are part of the best fit unique index

# File lib/inventory_refresh/save_collection/saver/base.rb, line 222
def unique_index_columns_to_s
  return @unique_index_columns_to_s if @unique_index_columns_to_s

  @unique_index_columns_to_s = unique_index_columns.map(&:to_s)
end
unique_index_for(keys) click to toggle source

Finds an index that fits the list of columns (keys) the best

@param keys [Array<Symbol>] @raise [Exception] if the unique index for the columns was not found @return [ActiveRecord::ConnectionAdapters::IndexDefinition] unique index fitting the keys

# File lib/inventory_refresh/save_collection/saver/base.rb, line 212
def unique_index_for(keys)
  inventory_collection.unique_index_for(keys)
end