Created
August 15, 2022 20:11
-
-
Save p8/06c9655209b1cc85df87ae8894a6dcd4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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