Guest User

Untitled

a guest
Jan 22nd, 2018
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 2.81 KB | None | 0 0
  1. # frozen_string_literal: true
  2. module ModelSupportsBulkUpdates
  3. extend ActiveSupport::Concern
  4.  
  5. module ClassMethods
  6.  
  7. # Performs a bulk update with an efficient single query for all the records in the list.
  8. # Note that the records are not reloaded form the database.
  9. # This means that ActiveRecord will still see these records as dirty after the bulk_update.
  10. def bulk_update(records)
  11. records = records.reject { |r| r.new_record? || r.destroyed? || !r.changed? }
  12. grouped = records.group_by { |r| [r.class, r.changed] }
  13. grouped.each do |(model_class, changed_column_names), record_list|
  14. next if record_list.empty?
  15.  
  16. # generates a query that looks like this
  17. #
  18. # UPDATE users AS u
  19. # SET first_name = tmp.first_name, last_name = tmp.last_name
  20. # FROM (VALUES (1, 'Luke', 'Skywalker'), (2, 'Leia', 'Organa'), (2, 'Han', 'Solo')) AS tmp(id, first_name, last_name)
  21. # WHERE u.id = tmp.id
  22.  
  23. connection = model_class.connection
  24. table_alias = "_#{SecureRandom.alphanumeric}"
  25. temp_table_alias = "_#{SecureRandom.alphanumeric}"
  26.  
  27. temp_table_columns = [ connection.quote_column_name(:id) ]
  28. changed_column_names.each do |column_name|
  29. temp_table_columns << connection.quote_column_name(column_name)
  30. end
  31.  
  32. sets = changed_column_names.map do |column_name|
  33. quoted_column_name = connection.quote_column_name(column_name)
  34. "#{quoted_column_name} = #{temp_table_alias}.#{quoted_column_name}"
  35. end
  36.  
  37. values = record_list.map do |record|
  38. record_values = [cast_and_quoted_value(:id, record.id, model_class)]
  39. changed_column_names.each do |column_name|
  40. record_values << cast_and_quoted_value(column_name, record.send(column_name), model_class)
  41. end
  42. "(#{record_values.join ", "})"
  43. end
  44.  
  45. id_sql_type = model_class.columns_hash["id"].sql_type
  46.  
  47. query = <<~QUERY
  48. UPDATE #{model_class.quoted_table_name} AS #{table_alias}
  49. SET #{sets.join ", "}
  50. FROM (VALUES #{values.join ", "}) AS #{temp_table_alias}(#{temp_table_columns.join ", "})
  51. WHERE #{table_alias}.#{connection.quote_column_name :id} = CAST(#{temp_table_alias}.#{connection.quote_column_name :id} AS #{id_sql_type})
  52. QUERY
  53.  
  54. connection.execute query
  55. end
  56.  
  57. true
  58. end
  59.  
  60. private
  61.  
  62. def cast_and_quoted_value(column_name, value, model_class=nil)
  63. model_class ||= self
  64. connection = model_class.connection
  65. arel_table = model_class.arel_table
  66. cast_value = arel_table[column_name.to_sym].type_cast_for_database(value)
  67. connection.quote_default_expression cast_value, model_class.columns_hash[column_name.to_s]
  68. end
  69. end
  70.  
  71. included do
  72. delegate :bulk_update, to: "self.class"
  73. end
  74. end
Add Comment
Please, Sign In to add comment