Skip to content

Instantly share code, notes, and snippets.

@p8
Created August 15, 2022 20:11
Show Gist options
  • Save p8/06c9655209b1cc85df87ae8894a6dcd4 to your computer and use it in GitHub Desktop.
Save p8/06c9655209b1cc85df87ae8894a6dcd4 to your computer and use it in GitHub Desktop.
require "bundler/setup"
require "active_record"
require "benchmark/ips"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.create_table(:orders) { |t| }
ActiveRecord::Base.connection.create_table(:items) do |t|
t.references :order, null: false
t.references :product, null: false
end
ActiveRecord::Base.connection.create_table(:products) { |t| }
class Order < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items
end
class Product < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items
end
class Item < ActiveRecord::Base
belongs_to :order
belongs_to :product
accepts_nested_attributes_for :order
accepts_nested_attributes_for :product
end
class Order2 < ActiveRecord::Base
self.table_name = :orders
has_many :items, foreign_key: :order_id
def mark_association_for_autosave(association_name)
@marked_autosave_associations ||= {}
@marked_autosave_associations[association_name] = true
end
def association_marked_for_autosave?(association_name)
@marked_autosave_associations && @marked_autosave_associations[association_name]
end
def save_collection_association(reflection)
if association = association_instance_get(reflection.name)
autosave = reflection.options[:autosave] || association_marked_for_autosave?(reflection.name)
# By saving the instance variable in a local variable,
# we make the whole callback re-entrant.
new_record_before_save = @new_record_before_save
# reconstruct the scope now that we know the owner's id
association.reset_scope
if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
if autosave
records_to_destroy = records.select(&:marked_for_destruction?)
records_to_destroy.each { |record| association.destroy(record) }
records -= records_to_destroy
end
records.each do |record|
next if record.destroyed?
saved = true
if autosave != false && (new_record_before_save || record.new_record?)
association.set_inverse_instance(record)
if autosave
saved = association.insert_record(record, false)
elsif !reflection.nested?
association_saved = association.insert_record(record)
if reflection.validate?
errors.add(reflection.name) unless association_saved
saved = association_saved
end
end
elsif autosave
saved = record.save(validate: false)
end
raise(RecordInvalid.new(association.owner)) unless saved
end
end
end
end
def generate_association_writer(association_name, type)
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
silence_redefinition_of_method :#{association_name}_attributes=
def #{association_name}_attributes=(attributes)
mark_association_for_autosave(:#{association_name})
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
eoruby
end
def self.accepts_nested_attributes_for(*attr_names)
options = { allow_destroy: false, update_only: false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
puts attr_names.inspect
attr_names.each do |association_name|
if reflection = _reflect_on_association(association_name)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
self.nested_attributes_options = nested_attributes_options
type = (reflection.collection? ? :collection : :one_to_one)
generate_association_writer(association_name, type)
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end
end
accepts_nested_attributes_for :items
end
order = Order.create
product = Product.create
Item.insert_all Array.new(100, { order_id: order.id, product_id: product.id })
Benchmark.ips do |x|
items_attributes = order.items.map { |item| { id: item.id } }
x.report("Order") do |times|
order = Order.first
order.update(items_attributes: items_attributes)
end
x.report("Order2") do |times|
order = Order2.first
order.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