123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::InsertAll

Do not use. This class is for internal use only.
Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: activerecord/lib/active_record/insert_all.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ InsertAll

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 18

def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
  @relation = relation
  @model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
  @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
  @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps

  disallow_raw_sql!(on_duplicate)
  disallow_raw_sql!(returning)

  if @inserts.empty?
    @keys = []
  else
    resolve_sti
    resolve_attribute_aliases
    @keys = @inserts.first.keys
  end

  @scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
  @keys |= @scope_attributes.keys
  @keys = @keys.to_set

  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
  @returning = false if @returning == []

  @unique_by = find_unique_index_for(@unique_by)

  configure_on_duplicate_update_logic
  ensure_valid_options_for_connection!
end

Class Method Details

.execute(relation)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 11

def execute(relation, ...)
  relation.model.with_connection do |c|
    new(relation, c, ...).execute
  end
end

Instance Attribute Details

#connection (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 7

attr_reader :model, :connection, :inserts, :keys

#custom_update_sql_provided?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 145

def custom_update_sql_provided?
  @custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
end

#inserts (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 7

attr_reader :model, :connection, :inserts, :keys

#keys (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 7

attr_reader :model, :connection, :inserts, :keys

#model (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 7

attr_reader :model, :connection, :inserts, :keys

#on_duplicate (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 8

attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql

#record_timestamps?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 87

def record_timestamps?
  @record_timestamps
end

#returning (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 8

attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql

#skip_duplicates?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 65

def skip_duplicates?
  on_duplicate == :skip
end

#unique_by (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 8

attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql

#update_duplicates?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 69

def update_duplicates?
  on_duplicate == :update
end

#update_only (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 8

attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql

#update_sql (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 8

attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql

Instance Method Details

#configure_on_duplicate_update_logic (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 129

def configure_on_duplicate_update_logic
  if custom_update_sql_provided? && update_only.present?
    raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
  end

  if update_only.present?
    @updatable_columns = Array(update_only)
    @on_duplicate = :update
  elsif custom_update_sql_provided?
    @update_sql = on_duplicate
    @on_duplicate = :update
  elsif @on_duplicate == :update && updatable_columns.empty?
    @on_duplicate = :skip
  end
end

#disallow_raw_sql!(value) (private)

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 212

def disallow_raw_sql!(value)
  return if !value.is_a?(String) || Arel.arel_node?(value)

  raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \
                       "SQL) called: #{value}. " \
                       "Known-safe values can be passed " \
                       "by wrapping them in Arel.sql()."
end

#ensure_valid_options_for_connection! (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 173

def ensure_valid_options_for_connection!
  if returning && !connection.supports_insert_returning?
    raise ArgumentError, "#{connection.class} does not support :returning"
  end

  if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
    raise ArgumentError, "#{connection.class} does not support skipping duplicates"
  end

  if update_duplicates? && !connection.supports_insert_on_duplicate_update?
    raise ArgumentError, "#{connection.class} does not support upsert"
  end

  if unique_by && !connection.supports_insert_conflict_target?
    raise ArgumentError, "#{connection.class} does not support :unique_by"
  end
end

#execute

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 48

def execute
  return ActiveRecord::Result.empty if inserts.empty?

  message = +"#{model} "
  message << "Bulk " if inserts.many?
  message << (on_duplicate == :update ? "Upsert" : "Insert")
  connection.exec_insert_all to_sql, message
end

#find_unique_index_for(unique_by) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 149

def find_unique_index_for(unique_by)
  if !connection.supports_insert_conflict_target?
    return if unique_by.nil?

    raise ArgumentError, "#{connection.class} does not support :unique_by"
  end

  name_or_columns = unique_by || model.primary_key
  match = Array(name_or_columns).map(&:to_s)
  sorted_match = match.sort

  if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
    index
  elsif match == primary_keys
    unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
  else
    raise ArgumentError, "No unique index found for #{name_or_columns}"
  end
end

#has_attribute_aliases?(attributes) ⇒ Boolean (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 101

def has_attribute_aliases?(attributes)
  attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
end

#keys_including_timestamps

TODO: Consider renaming this method, as it only conditionally extends keys, not always

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 92

def keys_including_timestamps
  @keys_including_timestamps ||= if record_timestamps?
    keys + model.all_timestamp_attributes_in_model
  else
    keys
  end
end

#map_key_with_value

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 73

def map_key_with_value
  inserts.map do |attributes|
    attributes = attributes.stringify_keys
    attributes.merge!(@scope_attributes)
    attributes.reverse_merge!(timestamps_for_create) if record_timestamps?

    verify_attributes(attributes)

    keys_including_timestamps.map do |key|
      yield key, attributes[key]
    end
  end
end

#primary_keys

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 61

def primary_keys
  Array(@model.schema_cache.primary_keys(model.table_name))
end

#readonly_columns (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 197

def readonly_columns
  primary_keys + model.readonly_attributes
end

#resolve_attribute_alias(attribute) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 125

def resolve_attribute_alias(attribute)
  model.attribute_alias(attribute) || attribute
end

#resolve_attribute_aliases (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 114

def resolve_attribute_aliases
  return unless has_attribute_aliases?(@inserts.first)

  @inserts = @inserts.map do |insert|
    insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
  end

  @update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
  @unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
end

#resolve_sti (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 105

def resolve_sti
  return if model.descends_from_active_record?

  sti_type = model.sti_name
  @inserts = @inserts.map do |insert|
    insert.reverse_merge(model.inheritance_column.to_s => sti_type)
  end
end

#timestamps_for_create (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 221

def timestamps_for_create
  model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
end

#to_sql (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 192

def to_sql
  connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
end

#unique_by_columns (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 201

def unique_by_columns
  Array(unique_by&.columns)
end

#unique_indexes (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 169

def unique_indexes
  @model.schema_cache.indexes(model.table_name).select(&:unique)
end

#updatable_columns

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 57

def updatable_columns
  @updatable_columns ||= keys - readonly_columns - unique_by_columns
end

#verify_attributes(attributes) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/insert_all.rb', line 206

def verify_attributes(attributes)
  if keys_including_timestamps != attributes.keys.to_set
    raise ArgumentError, "All objects being inserted must have the same keys"
  end
end