module JunkDrawer::BulkUpdatable
module to allow bulk updates for `ActiveRecord` models
Public Instance Methods
bulk_update(objects)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 10 def bulk_update(objects) objects = objects.select(&:changed?) return unless objects.any? unique_objects = uniquify_and_merge(objects) changed_attributes = extract_changed_attributes(unique_objects) query = build_query_for(unique_objects, changed_attributes) connection.execute(query) objects.each(&:clear_changes_information) end
Private Instance Methods
build_query_for(objects, attributes)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 51 def build_query_for(objects, attributes) object_values = objects.map do |object| sanitized_values(object, attributes) end.join(', ') assignment_query = attributes.map do |attribute| quoted_column_name = connection.quote_column_name(attribute) "#{quoted_column_name} = tmp_table.#{quoted_column_name}" end.join(', ') "UPDATE #{table_name} " \ "SET #{assignment_query} " \ "FROM (VALUES #{object_values}) " \ "AS tmp_table(id, #{attributes.join(', ')}) " \ "WHERE #{table_name}.id = tmp_table.id" end
extract_changed_attributes(objects)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 38 def extract_changed_attributes(objects) now = Time.zone.now objects.each { |object| object.updated_at = now } changed_attributes = objects.flat_map(&:changed).uniq if ::ActiveRecord::VERSION::MAJOR >= 5 column_names & changed_attributes else # to remove virtual columns from jsonb_accessor 0.3.3 columns.select(&:sql_type).map(&:name) & changed_attributes end end
sanitized_values(object, attributes)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 68 def sanitized_values(object, attributes) postgres_values = attributes.map do |attribute| value = object[attribute] # AR internal `columns_hash` column = columns_hash[attribute.to_s] # AR internal `type_for_attribute` type = type_for_attribute(column.name) type_cast = "::#{column.sql_type}" type_cast = "#{type_cast}[]" if column.array "#{connection.quote(serialized_value(type, value))}#{type_cast}" end "(#{[object.id, *postgres_values].join(', ')})" end
serialized_value(type, value)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 86 def serialized_value(type, value) if ::ActiveRecord::VERSION::MAJOR >= 5 type.serialize(value) else type.type_cast_for_database(value) end end
uniquify_and_merge(objects)
click to toggle source
# File lib/junk_drawer/rails/bulk_updatable.rb, line 23 def uniquify_and_merge(objects) grouped_objects = objects.group_by(&:id).values grouped_objects.each do |group| next if group.length == 1 attrs = group.each_with_object({}) do |object, changes| object.changed.each do |changed_attribute| changes[changed_attribute] = object[changed_attribute] end end group.each { |object| object.attributes = attrs } end grouped_objects.map(&:first) end