aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations/class_methods
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations/class_methods')
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency.rb233
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb281
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb26
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb80
4 files changed, 0 insertions, 620 deletions
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
deleted file mode 100644
index b711ff35ca..0000000000
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-require 'active_record/associations/class_methods/join_dependency/join_part'
-require 'active_record/associations/class_methods/join_dependency/join_base'
-require 'active_record/associations/class_methods/join_dependency/join_association'
-
-module ActiveRecord
- module Associations
- module ClassMethods
- class JoinDependency # :nodoc:
- attr_reader :join_parts, :reflections, :table_aliases, :active_record
-
- def initialize(base, associations, joins)
- @active_record = base
- @table_joins = joins
- @join_parts = [JoinBase.new(base)]
- @associations = {}
- @reflections = []
- @table_aliases = Hash.new do |h,name|
- h[name] = count_aliases_from_table_joins(name.downcase)
- end
- @table_aliases[base.table_name] = 1
- build(associations)
- end
-
- def graft(*associations)
- associations.each do |association|
- join_associations.detect {|a| association == a} ||
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
- end
- self
- end
-
- def join_associations
- join_parts.last(join_parts.length - 1)
- end
-
- def join_base
- join_parts.first
- end
-
- def columns
- join_parts.collect { |join_part|
- table = join_part.aliased_table
- join_part.column_names_with_alias.collect{ |column_name, aliased_name|
- table[column_name].as Arel.sql(aliased_name)
- }
- }.flatten
- end
-
- def count_aliases_from_table_joins(name)
- return 0 if Arel::Table === @table_joins
-
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = active_record.connection.quote_table_name(name).downcase
-
- @table_joins.map { |join|
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- }.sum
- end
-
- def instantiate(rows)
- primary_key = join_base.aliased_primary_key
- parents = {}
-
- records = rows.map { |model|
- primary_id = model[primary_key]
- parent = parents[primary_id] ||= join_base.instantiate(model)
- construct(parent, @associations, join_associations, model)
- parent
- }.uniq
-
- remove_duplicate_results!(active_record, records, @associations)
- records
- end
-
- def remove_duplicate_results!(base, records, associations)
- case associations
- when Symbol, String
- reflection = base.reflections[associations]
- remove_uniq_by_reflection(reflection, records)
- when Array
- associations.each do |association|
- remove_duplicate_results!(base, records, association)
- end
- when Hash
- associations.keys.each do |name|
- reflection = base.reflections[name]
- remove_uniq_by_reflection(reflection, records)
-
- parent_records = []
- records.each do |record|
- if descendant = record.send(reflection.name)
- if reflection.collection?
- parent_records.concat descendant.target.uniq
- else
- parent_records << descendant
- end
- end
- end
-
- remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
- end
- end
- end
-
- protected
-
- def cache_joined_association(association)
- associations = []
- parent = association.parent
- while parent != join_base
- associations.unshift(parent.reflection.name)
- parent = parent.parent
- end
- ref = @associations
- associations.each do |key|
- ref = ref[key]
- end
- ref[association.reflection.name] ||= {}
- end
-
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
- parent ||= join_parts.last
- case associations
- when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
- unless join_association = find_join_association(reflection, parent)
- @reflections << reflection
- join_association = build_join_association(reflection, parent)
- join_association.join_type = join_type
- @join_parts << join_association
- cache_joined_association(join_association)
- end
- join_association
- when Array
- associations.each do |association|
- build(association, parent, join_type)
- end
- when Hash
- associations.keys.sort_by { |a| a.to_s }.each do |name|
- join_association = build(name, parent, join_type)
- build(associations[name], join_association, join_type)
- end
- else
- raise ConfigurationError, associations.inspect
- end
- end
-
- def find_join_association(name_or_reflection, parent)
- if String === name_or_reflection
- name_or_reflection = name_or_reflection.to_sym
- end
-
- join_associations.detect { |j|
- j.reflection == name_or_reflection && j.parent == parent
- }
- end
-
- def remove_uniq_by_reflection(reflection, records)
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
- end
-
- def build_join_association(reflection, parent)
- JoinAssociation.new(reflection, self, parent)
- end
-
- def construct(parent, associations, join_parts, row)
- case associations
- when Symbol, String
- name = associations.to_s
-
- join_part = join_parts.detect { |j|
- j.reflection.name.to_s == name &&
- j.parent_table_name == parent.class.table_name }
-
- raise(ConfigurationError, "No such association") unless join_part
-
- join_parts.delete(join_part)
- construct_association(parent, join_part, row)
- when Array
- associations.each do |association|
- construct(parent, association, join_parts, row)
- end
- when Hash
- associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
- association = construct(parent, association_name, join_parts, row)
- construct(association, assoc, join_parts, row) if association
- end
- else
- raise ConfigurationError, associations.inspect
- end
- end
-
- def construct_association(record, join_part, row)
- return if record.id.to_s != join_part.parent.record_id(row).to_s
-
- macro = join_part.reflection.macro
- if macro == :has_one
- return if record.association_cache.key?(join_part.reflection.name)
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
- set_target_and_inverse(join_part, association, record)
- else
- return if row[join_part.aliased_primary_key].nil?
- association = join_part.instantiate(row)
- case macro
- when :has_many, :has_and_belongs_to_many
- other = record.association(join_part.reflection.name)
- other.loaded!
- other.target.push(association)
- other.set_inverse_instance(association)
- when :belongs_to
- set_target_and_inverse(join_part, association, record)
- else
- raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
- end
- end
- association
- end
-
- def set_target_and_inverse(join_part, association, record)
- other = record.association(join_part.reflection.name)
- other.target = association
- other.set_inverse_instance(association)
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
deleted file mode 100644
index aaa475109e..0000000000
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
+++ /dev/null
@@ -1,281 +0,0 @@
-module ActiveRecord
- module Associations
- module ClassMethods
- class JoinDependency # :nodoc:
- class JoinAssociation < JoinPart # :nodoc:
- # The reflection of the association represented
- attr_reader :reflection
-
- # The JoinDependency object which this JoinAssociation exists within. This is mainly
- # relevant for generating aliases which do not conflict with other joins which are
- # part of the query.
- attr_reader :join_dependency
-
- # A JoinBase instance representing the active record we are joining onto.
- # (So in Author.has_many :posts, the Author would be that base record.)
- attr_reader :parent
-
- # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
- attr_accessor :join_type
-
- # These implement abstract methods from the superclass
- attr_reader :aliased_prefix, :aliased_table_name
-
- delegate :options, :through_reflection, :source_reflection, :to => :reflection
- delegate :table, :table_name, :to => :parent, :prefix => :parent
-
- def initialize(reflection, join_dependency, parent = nil)
- reflection.check_validity!
-
- if reflection.options[:polymorphic]
- raise EagerLoadPolymorphicError.new(reflection)
- end
-
- super(reflection.klass)
-
- @reflection = reflection
- @join_dependency = join_dependency
- @parent = parent
- @join_type = Arel::InnerJoin
- @aliased_prefix = "t#{ join_dependency.join_parts.size }"
-
- # This must be done eagerly upon initialisation because the alias which is produced
- # depends on the state of the join dependency, but we want it to work the same way
- # every time.
- allocate_aliases
- @table = Arel::Table.new(
- table_name, :as => aliased_table_name, :engine => arel_engine
- )
- end
-
- def ==(other)
- other.class == self.class &&
- other.reflection == reflection &&
- other.parent == parent
- end
-
- def find_parent_in(other_join_dependency)
- other_join_dependency.join_parts.detect do |join_part|
- parent == join_part
- end
- end
-
- def join_to(relation)
- send("join_#{reflection.macro}_to", relation)
- end
-
- def join_relation(joining_relation)
- self.join_type = Arel::OuterJoin
- joining_relation.joins(self)
- end
-
- attr_reader :table
- # More semantic name given we are talking about associations
- alias_method :target_table, :table
-
- protected
-
- def aliased_table_name_for(name, suffix = nil)
- aliases = @join_dependency.table_aliases
-
- if aliases[name] != 0 # We need an alias
- connection = active_record.connection
-
- name = connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
- aliases[name] += 1
- name = name[0, connection.table_alias_length-3] + "_#{aliases[name]}" if aliases[name] > 1
- else
- aliases[name] += 1
- end
-
- name
- end
-
- def pluralize(table_name)
- ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
- end
-
- private
-
- def allocate_aliases
- @aliased_table_name = aliased_table_name_for(table_name)
-
- if reflection.macro == :has_and_belongs_to_many
- @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
- elsif [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
- @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
- end
- end
-
- def process_conditions(conditions, table_name)
- if conditions.respond_to?(:to_proc)
- conditions = instance_eval(&conditions)
- end
-
- Arel.sql(sanitize_sql(conditions, table_name))
- end
-
- def sanitize_sql(condition, table_name)
- active_record.send(:sanitize_sql, condition, table_name)
- end
-
- def join_target_table(relation, condition)
- conditions = [condition]
-
- # If the target table is an STI model then we must be sure to only include records of
- # its type and its sub-types.
- unless active_record.descends_from_active_record?
- sti_column = target_table[active_record.inheritance_column]
- subclasses = active_record.descendants
- sti_condition = sti_column.eq(active_record.sti_name)
-
- conditions << subclasses.inject(sti_condition) { |attr,subclass|
- attr.or(sti_column.eq(subclass.sti_name))
- }
- end
-
- # If the reflection has conditions, add them
- if options[:conditions]
- conditions << process_conditions(options[:conditions], aliased_table_name)
- end
-
- ands = relation.create_and(conditions)
-
- join = relation.create_join(
- target_table,
- relation.create_on(ands),
- join_type)
-
- relation.from join
- end
-
- def join_has_and_belongs_to_many_to(relation)
- join_table = Arel::Table.new(
- options[:join_table]
- ).alias(@aliased_join_table_name)
-
- fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
- klass_fk = options[:association_foreign_key] || reflection.klass.to_s.foreign_key
-
- relation = relation.join(join_table, join_type)
- relation = relation.on(
- join_table[fk].
- eq(parent_table[reflection.active_record.primary_key])
- )
-
- join_target_table(
- relation,
- target_table[reflection.klass.primary_key].
- eq(join_table[klass_fk])
- )
- end
-
- def join_has_many_to(relation)
- if reflection.options[:through]
- join_has_many_through_to(relation)
- elsif reflection.options[:as]
- join_has_many_polymorphic_to(relation)
- else
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
- primary_key = options[:primary_key] || parent.primary_key
-
- join_target_table(
- relation,
- target_table[foreign_key].
- eq(parent_table[primary_key])
- )
- end
- end
- alias :join_has_one_to :join_has_many_to
-
- def join_has_many_through_to(relation)
- join_table = Arel::Table.new(
- through_reflection.klass.table_name
- ).alias @aliased_join_table_name
-
- jt_conditions = []
- first_key = second_key = nil
-
- if through_reflection.macro == :belongs_to
- jt_primary_key = through_reflection.foreign_key
- jt_foreign_key = through_reflection.association_primary_key
- else
- jt_primary_key = through_reflection.active_record_primary_key
- jt_foreign_key = through_reflection.foreign_key
-
- if through_reflection.options[:as] # has_many :through against a polymorphic join
- jt_conditions <<
- join_table["#{through_reflection.options[:as]}_type"].
- eq(parent.active_record.base_class.name)
- end
- end
-
- case source_reflection.macro
- when :has_many
- second_key = options[:foreign_key] || primary_key
-
- if source_reflection.options[:as]
- first_key = "#{source_reflection.options[:as]}_id"
- else
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- end
-
- unless through_reflection.klass.descends_from_active_record?
- jt_conditions <<
- join_table[through_reflection.active_record.inheritance_column].
- eq(through_reflection.klass.sti_name)
- end
- when :belongs_to
- first_key = primary_key
-
- if reflection.options[:source_type]
- second_key = source_reflection.association_foreign_key
-
- jt_conditions <<
- join_table[reflection.source_reflection.foreign_type].
- eq(reflection.options[:source_type])
- else
- second_key = source_reflection.foreign_key
- end
- end
-
- jt_conditions <<
- parent_table[jt_primary_key].
- eq(join_table[jt_foreign_key])
-
- if through_reflection.options[:conditions]
- jt_conditions << process_conditions(through_reflection.options[:conditions], aliased_table_name)
- end
-
- relation = relation.join(join_table, join_type).on(*jt_conditions)
-
- join_target_table(
- relation,
- target_table[first_key].eq(join_table[second_key])
- )
- end
-
- def join_has_many_polymorphic_to(relation)
- join_target_table(
- relation,
- target_table["#{reflection.options[:as]}_id"].
- eq(parent_table[parent.primary_key]).and(
- target_table["#{reflection.options[:as]}_type"].
- eq(parent.active_record.base_class.name))
- )
- end
-
- def join_belongs_to_to(relation)
- foreign_key = options[:foreign_key] || reflection.foreign_key
- primary_key = options[:primary_key] || reflection.klass.primary_key
-
- join_target_table(
- relation,
- target_table[primary_key].eq(parent_table[foreign_key])
- )
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
deleted file mode 100644
index 67567f06df..0000000000
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActiveRecord
- module Associations
- module ClassMethods
- class JoinDependency # :nodoc:
- class JoinBase < JoinPart # :nodoc:
- def ==(other)
- other.class == self.class &&
- other.active_record == active_record
- end
-
- def aliased_prefix
- "t0"
- end
-
- def table
- Arel::Table.new(table_name, arel_engine)
- end
-
- def aliased_table_name
- active_record.table_name
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb
deleted file mode 100644
index cd16ae5a8b..0000000000
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-module ActiveRecord
- module Associations
- module ClassMethods
- class JoinDependency # :nodoc:
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
- # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
- # everything else is being joined onto. A JoinAssociation represents an association which
- # is joining to the base. A JoinAssociation may result in more than one actual join
- # operations (for example a has_and_belongs_to_many JoinAssociation would result in
- # two; one for the join table and one for the target table).
- class JoinPart # :nodoc:
- # The Active Record class which this join part is associated 'about'; for a JoinBase
- # this is the actual base model, for a JoinAssociation this is the target model of the
- # association.
- attr_reader :active_record
-
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
-
- def initialize(active_record)
- @active_record = active_record
- @cached_record = {}
- @column_names_with_alias = nil
- end
-
- def aliased_table
- Arel::Nodes::TableAlias.new aliased_table_name, table
- end
-
- def ==(other)
- raise NotImplementedError
- end
-
- # An Arel::Table for the active_record
- def table
- raise NotImplementedError
- end
-
- # The prefix to be used when aliasing columns in the active_record's table
- def aliased_prefix
- raise NotImplementedError
- end
-
- # The alias for the active_record's table
- def aliased_table_name
- raise NotImplementedError
- end
-
- # The alias for the primary key of the active_record's table
- def aliased_primary_key
- "#{aliased_prefix}_r0"
- end
-
- # An array of [column_name, alias] pairs for the table
- def column_names_with_alias
- unless @column_names_with_alias
- @column_names_with_alias = []
-
- ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
- @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
- end
- end
- @column_names_with_alias
- end
-
- def extract_record(row)
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
- end
-
- def record_id(row)
- row[aliased_primary_key]
- end
-
- def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
- end
- end
- end
- end
- end
-end