Skip to content

Commit

Permalink
use a hash to store relation values
Browse files Browse the repository at this point in the history
  • Loading branch information
jonleighton committed Apr 13, 2012
1 parent 1480dfa commit 6311975
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 52 deletions.
27 changes: 12 additions & 15 deletions activerecord/lib/active_record/relation.rb
Expand Up @@ -29,11 +29,7 @@ def initialize(klass, table)
@implicit_readonly = nil
@loaded = false
@default_scoped = false

SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
MULTI_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_values", [])}

@create_with_value = {}
@values = {}
end

def insert(values)
Expand Down Expand Up @@ -84,7 +80,8 @@ def new(*args, &block)
end

def initialize_copy(other)
@bind_values = @bind_values.dup
@values = @values.dup
@values[:bind] = @values[:bind].dup if @values[:bind]
reset
end

Expand Down Expand Up @@ -174,17 +171,17 @@ def exec_queries
default_scoped = with_default_scope

if default_scoped.equal?(self)
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)

preload = @preload_values
preload += @includes_values unless eager_loading?
preload = preload_values
preload += includes_values unless eager_loading?
preload.each do |associations|
ActiveRecord::Associations::Preloader.new(@records, associations).run
end

# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
# are JOINS and no explicit SELECT.
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
@records.each { |record| record.readonly! } if readonly
else
@records = default_scoped.to_a
Expand Down Expand Up @@ -224,7 +221,7 @@ def many?
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
@limit_value ? to_a.many? : size > 1
limit_value ? to_a.many? : size > 1
end
end

Expand Down Expand Up @@ -460,7 +457,7 @@ def reset
end

def to_sql
@to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
@to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
end

def where_values_hash
Expand All @@ -482,16 +479,16 @@ def scope_for_create

def eager_loading?
@should_eager_load ||=
@eager_load_values.any? ||
@includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
eager_load_values.any? ||
includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end

# Joins that are also marked for preloading. In which case we should just eager load them.
# Note that this is a naive implementation because we could have strings and symbols which
# represent the same association, but that aren't matched by this. Also, we could have
# nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
def joined_includes_values
@includes_values & @joins_values
includes_values & joins_values
end

def ==(other)
Expand Down
10 changes: 5 additions & 5 deletions activerecord/lib/active_record/relation/calculations.rb
Expand Up @@ -216,7 +216,7 @@ def perform_calculation(operation, column_name, options = {})
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end

if @group_values.any?
if group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
execute_simple_calculation(operation, column_name, distinct)
Expand Down Expand Up @@ -259,7 +259,7 @@ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
end

def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
group_attr = @group_values
group_attr = group_values
association = @klass.reflect_on_association(group_attr.first.to_sym)
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attr)
Expand All @@ -282,7 +282,7 @@ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
operation,
distinct).as(aggregate_alias)
]
select_values += @select_values unless @having_values.empty?
select_values += select_values unless having_values.empty?

select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
"#{field} AS #{aliaz}"
Expand Down Expand Up @@ -347,8 +347,8 @@ def type_cast_using_column(value, column)
end

def select_for_count
if @select_values.present?
select = @select_values.join(", ")
if select_values.present?
select = select_values.join(", ")
select if select !~ /[,*]/
end
end
Expand Down
14 changes: 7 additions & 7 deletions activerecord/lib/active_record/relation/finder_methods.rb
Expand Up @@ -236,12 +236,12 @@ def find_with_associations
end

def construct_join_dependency_for_association_find
including = (@eager_load_values + @includes_values).uniq
including = (eager_load_values + includes_values).uniq
ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
end

def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
including = (eager_load_values + includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
Expand Down Expand Up @@ -340,7 +340,7 @@ def find_one(id)
id = id.id if ActiveRecord::Base === id

column = columns_hash[primary_key]
substitute = connection.substitute_at(column, @bind_values.length)
substitute = connection.substitute_at(column, bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values += [[column, id]]
record = relation.first
Expand All @@ -358,15 +358,15 @@ def find_some(ids)
result = where(table[primary_key].in(ids)).all

expected_size =
if @limit_value && ids.size > @limit_value
@limit_value
if limit_value && ids.size > limit_value
limit_value
else
ids.size
end

# 11 ids with limit 3, offset 9 should give 2 results.
if @offset_value && (ids.size - @offset_value < expected_size)
expected_size = ids.size - @offset_value
if offset_value && (ids.size - offset_value < expected_size)
expected_size = ids.size - offset_value
end

if result.size == expected_size
Expand Down
61 changes: 43 additions & 18 deletions activerecord/lib/active_record/relation/query_methods.rb
Expand Up @@ -5,12 +5,37 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern

attr_accessor :includes_values, :eager_load_values, :preload_values,
:select_values, :group_values, :order_values, :joins_values,
:where_values, :having_values, :bind_values,
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
:from_value, :reordering_value, :reverse_order_value,
:uniq_value, :references_values, :extending_values
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_values # def select_values
@values[:#{name}] || [] # @values[:select] || []
end # end
#
def #{name}_values=(values) # def select_values=(values)
@values[:#{name}] = values # @values[:select] = values
end # end
CODE
end

(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_value # def readonly_value
@values[:#{name}] # @values[:readonly]
end # end
#
def #{name}_value=(value) # def readonly_value=(value)
@values[:#{name}] = value # @values[:readonly] = value
end # end
CODE
end

def create_with_value
@values[:create_with] || {}
end

def create_with_value=(value)
@values[:create_with] = value
end

alias extensions extending_values

Expand Down Expand Up @@ -372,26 +397,26 @@ def arel
def build_arel
arel = table.from table

build_joins(arel, @joins_values) unless @joins_values.empty?
build_joins(arel, joins_values) unless joins_values.empty?

collapse_wheres(arel, (@where_values - ['']).uniq)
collapse_wheres(arel, (where_values - ['']).uniq)

arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?

arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
arel.skip(@offset_value.to_i) if @offset_value
arel.take(connection.sanitize_limit(limit_value)) if limit_value
arel.skip(offset_value.to_i) if offset_value

arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?

order = @order_values
order = reverse_sql_order(order) if @reverse_order_value
order = order_values
order = reverse_sql_order(order) if reverse_order_value
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?

build_select(arel, @select_values.uniq)
build_select(arel, select_values.uniq)

arel.distinct(@uniq_value)
arel.from(@from_value) if @from_value
arel.lock(@lock_value) if @lock_value
arel.distinct(uniq_value)
arel.from(from_value) if from_value
arel.lock(lock_value) if lock_value

arel
end
Expand Down
14 changes: 7 additions & 7 deletions activerecord/test/cases/relation_test.rb
Expand Up @@ -43,19 +43,19 @@ def test_empty_where_values_hash
relation = Relation.new :a, :b
assert_equal({}, relation.where_values_hash)

relation.where_values << :hello
relation.where! :hello
assert_equal({}, relation.where_values_hash)
end

def test_has_values
relation = Relation.new Post, Post.arel_table
relation.where_values << relation.table[:id].eq(10)
relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end

def test_values_wrong_table
relation = Relation.new Post, Post.arel_table
relation.where_values << Comment.arel_table[:id].eq(10)
relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end

Expand All @@ -64,7 +64,7 @@ def test_tree_is_not_traversed
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
relation.where_values << combine
relation.where! combine
assert_equal({}, relation.where_values_hash)
end

Expand All @@ -87,7 +87,7 @@ def test_create_with_value

def test_create_with_value_with_wheres
relation = Relation.new Post, Post.arel_table
relation.where_values << relation.table[:id].eq(10)
relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
end
Expand All @@ -97,7 +97,7 @@ def test_scope_for_create_is_cached
relation = Relation.new Post, Post.arel_table
assert_equal({}, relation.scope_for_create)

relation.where_values << relation.table[:id].eq(10)
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)

relation.create_with_value = {:hello => 'world'}
Expand All @@ -111,7 +111,7 @@ def test_empty_eager_loading?

def test_eager_load_values
relation = Relation.new :a, :b
relation.eager_load_values << :b
relation.eager_load! :b
assert relation.eager_loading?
end

Expand Down

0 comments on commit 6311975

Please sign in to comment.