Skip to content

Instantly share code, notes, and snippets.

@p8
Last active August 16, 2022 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save p8/8ed895c2de3cff57bcb5ae9f256f5268 to your computer and use it in GitHub Desktop.
Save p8/8ed895c2de3cff57bcb5ae9f256f5268 to your computer and use it in GitHub Desktop.
require "bundler/setup"
require "active_record"
begin
require "benchmark/ips"
rescue LoadError
raise LoadError, "Run install `gem install benchmark-ips`"
end
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.create_table(:orders) { |t| }
ActiveRecord::Base.connection.create_table(:items) { |t| t.references :order, null: false }
class Order < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items
end
class Item < ActiveRecord::Base
end
class Order2 < ActiveRecord::Base
self.table_name = :orders
has_many :items, foreign_key: :order_id
accepts_nested_attributes_for :items
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = nested_attributes_options[association_name]
if attributes_collection.respond_to?(:permitted?)
attributes_collection = attributes_collection.to_h
end
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
check_record_limit!(options[:limit], attributes_collection)
if attributes_collection.is_a? Hash
keys = attributes_collection.keys
attributes_collection = if keys.include?("id") || keys.include?(:id)
[attributes_collection]
else
attributes_collection.values
end
end
association = association(association_name)
existing_records = if association.loaded?
association.target
else
attribute_ids = attributes_collection.filter_map { |a| a["id"] || a[:id] }
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
end
existing_records = existing_records.index_by { |record| record.id.to_s }
attributes_collection.each do |attributes|
if attributes.respond_to?(:permitted?)
attributes = attributes.to_h
end
attributes = attributes.with_indifferent_access
if attributes["id"].blank?
unless reject_new_record?(association_name, attributes)
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_record = existing_records[attributes["id"].to_s]
unless call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
# proxy_target array (either by finding it, or adding it if not found)
# Take into account that the proxy_target may have changed due to callbacks
target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
if target_record
existing_record = target_record
else
association.add_to_target(existing_record, skip_callbacks: true)
end
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
end
end
end
end
Benchmark.ips do |x|
order = Order.create
Item.insert_all Array.new(2000, { order_id: order.id })
items_attributes = order.items.map { |item| { id: item.id } }
order = Order.first
order.reload
x.report("Order") do |times|
order.update(items_attributes: items_attributes)
end
order2 = Order2.first
order2.reload
x.report("Order2") do |times|
order2.update(items_attributes: items_attributes)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment