Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up homogeneous AR lists / reduce allocations #33223

Merged
merged 4 commits into from Jun 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions activerecord/lib/active_record/core.rb
Expand Up @@ -344,6 +344,28 @@ def init_with(coder)
self
end

##
# Initializer used for instantiating objects that have been read from the
# database. +attributes+ should be an attributes object, and unlike the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double space after # database. redundant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. We learned double space after punctuation in school because everything was fixed width font at that time. These days we don't need it because most stuff (except code) isn't fixed width. Since code is fixed width, I go with double space but I'm not sure what our style guide is in this case. @fxn?

# `initialize` method, no assignment calls are made per attribute.
#
# :nodoc:
def init_from_db(attributes)
init_internals

@new_record = false
@attributes = attributes

self.class.define_attribute_methods

yield self if block_given?

_run_find_callbacks
_run_initialize_callbacks

self
end

##
# :method: clone
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
Expand Down
12 changes: 11 additions & 1 deletion activerecord/lib/active_record/persistence.rb
Expand Up @@ -67,8 +67,18 @@ def create!(attributes = nil, &block)
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {}, &block)
klass = discriminate_class_for_record(attributes)
instantiate_instance_of(klass, attributes, column_types, &block)
end

# Given a class, an attributes hash, +instantiate_instance_of+ returns a
# new instance of the class. Accepts only keys as strings.
#
# This is private, don't call it. :)
#
# :nodoc:
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
klass.allocate.init_from_db(attributes, &block)
end

# Updates an object (or multiple objects) and saves it to the database, if validations pass.
Expand Down
7 changes: 6 additions & 1 deletion activerecord/lib/active_record/querying.rb
Expand Up @@ -49,7 +49,12 @@ def find_by_sql(sql, binds = [], preparable: nil, &block)
}

message_bus.instrument("instantiation.active_record", payload) do
result_set.map { |record| instantiate(record, column_types, &block) }
if result_set.includes_column?(inheritance_column)
result_set.map { |record| instantiate(record, column_types, &block) }
else
# Instantiate a homogeneous set
result_set.map { |record| instantiate_instance_of(self, record, column_types, &block) }
end
end
end

Expand Down
5 changes: 5 additions & 0 deletions activerecord/lib/active_record/result.rb
Expand Up @@ -43,6 +43,11 @@ def initialize(columns, rows, column_types = {})
@column_types = column_types
end

# Returns true if this result set includes the column named +name+
def includes_column?(name)
@columns.include? name
end

# Returns the number of elements in the rows array.
def length
@rows.length
Expand Down
5 changes: 5 additions & 0 deletions activerecord/test/cases/result_test.rb
Expand Up @@ -12,6 +12,11 @@ def result
])
end

test "includes_column?" do
assert result.includes_column?("col_1")
assert_not result.includes_column?("foo")
end

test "length" do
assert_equal 3, result.length
end
Expand Down