Skip to content
Browse files

add factory methods for empty alias trackers

If we know the alias tracker is empty, we can create one that doesn't
use a hash with default block for counting.

ActiveRecord::Base.establish_connection(adapter: 'sqlite3',
                                        database: ':memory:')

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.integer :comments_count
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class Post < ActiveRecord::Base; has_many :comments; end
class Comment < ActiveRecord::Base; belongs_to :post, counter_cache: true; end
10.times { Comment.create!(post: Post.create!) }

record = Post.first
association_name = :comments

Benchmark.ips do |x|
  reflection = record.class.reflect_on_association(association_name)
  association = reflection.association_class.new(record, reflection)
  x.report('assoc') do
    reflection.association_class.new(record, reflection)
  end
  x.report('reader') do
    association.reader;nil
  end
  x.report('combined') do
    reflection.association_class.new(record, reflection).reader;nil
  end
end

[aaron@higgins rails (tracker)]$ TEST=ips bundle exec ruby ../1bb5456b5e035343df9d/gistfile1.rb
-- create_table(:posts, {:force=>true})
   -> 0.0062s
-- create_table(:comments, {:force=>true})
   -> 0.0003s
Calculating -------------------------------------
               assoc       833 i/100ms
              reader     28703 i/100ms
            combined       839 i/100ms
-------------------------------------------------
               assoc     9010.3 (±3.8%) i/s -      44982 in   5.000022s
              reader  3214523.4 (±5.5%) i/s -   16016274 in   5.001136s
            combined     8841.0 (±5.8%) i/s -      44467 in   5.049269s

[aaron@higgins rails (tracker)]$ TEST=ips bundle exec ruby ../1bb5456b5e035343df9d/gistfile1.rb
-- create_table(:posts, {:force=>true})
   -> 0.0060s
-- create_table(:comments, {:force=>true})
   -> 0.0003s
Calculating -------------------------------------
               assoc       888 i/100ms
              reader     29217 i/100ms
            combined       900 i/100ms
-------------------------------------------------
               assoc     9674.3 (±3.3%) i/s -      48840 in   5.054022s
              reader  2988474.8 (±6.9%) i/s -   14842236 in   4.998230s
            combined     9674.0 (±3.1%) i/s -      48600 in   5.028694s
  • Loading branch information...
1 parent 4e823b6 commit bfc776f7bb114e90cf91f16f5892be636ed2f0c8 @tenderlove tenderlove committed
View
57 activerecord/lib/active_record/associations/alias_tracker.rb
@@ -7,10 +7,43 @@ module Associations
class AliasTracker # :nodoc:
attr_reader :aliases, :connection
+ def self.empty(connection)
+ new connection, Hash.new(0)
+ end
+
+ def self.create(connection, table_joins)
+ if table_joins.empty?
+ empty connection
+ else
+ aliases = Hash.new { |h,k|
+ h[k] = initial_count_for(connection, k, table_joins)
+ }
+ new connection, aliases
+ end
+ end
+
+ def self.initial_count_for(connection, name, table_joins)
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = connection.quote_table_name(name).downcase
+
+ counts = table_joins.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ else
+ join.left.table_name == name ? 1 : 0
+ end
+ end
+
+ counts.sum
+ end
+
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection, table_joins)
- @aliases = Hash.new { |h,k| h[k] = initial_count_for(k, table_joins) }
- @connection = connection
+ def initialize(connection, aliases)
+ @aliases = aliases
+ @connection = connection
end
def aliased_table_for(table_name, aliased_name)
@@ -45,24 +78,6 @@ def aliased_name_for(table_name, aliased_name)
private
- def initial_count_for(name, table_joins)
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = connection.quote_table_name(name).downcase
-
- counts = table_joins.map do |join|
- if join.is_a?(Arel::Nodes::StringJoin)
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- else
- join.left.table_name == name ? 1 : 0
- end
- end
-
- counts.sum
- end
-
def truncate(name)
name.slice(0, connection.table_alias_length - 2)
end
View
2 activerecord/lib/active_record/associations/association_scope.rb
@@ -12,7 +12,7 @@ def scope(association, connection)
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
- alias_tracker = AliasTracker.new(connection, [])
+ alias_tracker = AliasTracker.empty connection
scope.extending! Array(reflection.options[:extend])
add_constraints(scope, owner, klass, reflection, alias_tracker)
View
2 activerecord/lib/active_record/associations/join_dependency.rb
@@ -93,7 +93,7 @@ def self.walk_tree(associations, hash)
# joins # => []
#
def initialize(base, associations, joins)
- @alias_tracker = AliasTracker.new(base.connection, joins)
+ @alias_tracker = AliasTracker.create(base.connection, joins)
@alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)

0 comments on commit bfc776f

Please sign in to comment.
Something went wrong with that request. Please try again.