aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--activerecord/lib/active_record/counter_cache.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb52
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb25
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
15 files changed, 85 insertions, 44 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 8d77fad2d5..07dfc448e7 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -50,7 +50,7 @@ module ActiveRecord
def initialize(reflection)
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
- source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
+ source_associations = reflection.through_reflection.klass._reflections.keys
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@@ -151,7 +151,7 @@ module ActiveRecord
association = association_instance_get(name)
if association.nil?
- raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -1577,6 +1577,8 @@ module ActiveRecord
scope = nil
end
+ habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self)
+
builder = Builder::HasAndBelongsToMany.new name, self, options
join_model = builder.through_model
@@ -1590,6 +1592,7 @@ module ActiveRecord
Builder::HasMany.define_callbacks self, middle_reflection
Reflection.add_reflection self, middle_reflection.name, middle_reflection
+ middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1610,6 +1613,7 @@ module ActiveRecord
end
has_many name, scope, hm_options, &extension
+ self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 9ad2d2fb12..4a04303fb8 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -160,7 +160,7 @@ module ActiveRecord
def marshal_load(data)
reflection_name, ivars = data
ivars.each { |name, val| instance_variable_set(name, val) }
- @reflection = @owner.class.reflect_on_association(reflection_name)
+ @reflection = @owner.class._reflect_on_association(reflection_name)
end
def initialize_attributes(record) #:nodoc:
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 30b11c01eb..0ad5206980 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -66,13 +66,13 @@ module ActiveRecord::Associations::Builder
def self.add_left_association(name, options)
belongs_to name, options
- self.left_reflection = reflect_on_association(name)
+ self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
belongs_to rhs_name, options
- self.right_reflection = reflect_on_association(rhs_name)
+ self.right_reflection = _reflect_on_association(rhs_name)
end
}
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f5e911c739..2727e23870 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -100,7 +100,8 @@ module ActiveRecord
# Hence this method.
def inverse_updates_counter_cache?(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
+ reflection.klass._reflections.values.any? { |inverse_reflection|
+ :belongs_to == inverse_reflection.macro &&
inverse_reflection.counter_cache_column == counter_name
}
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 5842be3a7b..01173b68f3 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -207,7 +207,7 @@ module ActiveRecord
end
def find_reflection(klass, name)
- klass.reflect_on_association(name) or
+ klass._reflect_on_association(name) or
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 1a4d2957ec..2a7acf6787 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -273,9 +273,11 @@ module ActiveRecord
# go through nested autosave associations that are loaded in memory (without loading
# any new ones), and return true if is changed for autosave
def nested_records_changed_for_autosave?
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
- association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ self.class._reflections.values.any? do |reflection|
+ if reflection.options[:autosave]
+ association = association_instance_get(reflection.name)
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 71e176a328..05c4b13016 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -20,7 +20,7 @@ module ActiveRecord
def reset_counters(id, *counters)
object = find(id)
counters.each do |counter_association|
- has_many_association = reflect_on_association(counter_association.to_sym)
+ has_many_association = _reflect_on_association(counter_association.to_sym)
unless has_many_association
has_many = reflect_on_all_associations(:has_many)
has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
@@ -34,8 +34,7 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
+ reflection = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
@@ -167,7 +166,7 @@ module ActiveRecord
end
def each_counter_cached_associations
- reflections.each do |name, reflection|
+ _reflections.each do |name, reflection|
yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 47d32fae05..d40bea5ea7 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -649,7 +649,7 @@ module ActiveRecord
model_class
end
- reflection_class.reflect_on_all_associations.each do |association|
+ reflection_class._reflections.values.each do |association|
case association.macro
when :belongs_to
# Do not replace association name with association foreign key if they are named the same
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 29ed499b1b..7dc7169a02 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -305,7 +305,7 @@ module ActiveRecord
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
- if reflection = reflect_on_association(association_name)
+ if reflection = _reflect_on_association(association_name)
reflection.autosave = true
add_autosave_association_callbacks(reflection)
@@ -542,7 +542,7 @@ module ActiveRecord
end
def raise_nested_attributes_record_not_found!(association_name, record_id)
- raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0eec6774a0..dd80ec6274 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -6,9 +6,9 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :reflections
+ class_attribute :_reflections
class_attribute :aggregate_reflections
- self.reflections = {}
+ self._reflections = {}
self.aggregate_reflections = {}
end
@@ -24,7 +24,7 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name.to_s => reflection)
+ ar._reflections = ar._reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
@@ -53,6 +53,24 @@ module ActiveRecord
aggregate_reflections[aggregation.to_s]
end
+ # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
+ #
+ # Account.reflections # => {balance: AggregateReflection}
+ #
+ # @api public
+ def reflections
+ ref = {}
+ _reflections.each do |name, reflection|
+ parent_name, parent_reflection = reflection.parent_reflection
+ if parent_name
+ ref[parent_name] = parent_reflection
+ else
+ ref[name] = reflection
+ end
+ end
+ ref
+ end
+
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -63,6 +81,7 @@ module ActiveRecord
# Account.reflect_on_all_associations # returns an array of all associations
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
#
+ # @api public
def reflect_on_all_associations(macro = nil)
association_reflections = reflections.values
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
@@ -73,11 +92,19 @@ module ActiveRecord
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
+ # @api public
def reflect_on_association(association)
reflections[association.to_s]
end
+ # @api private
+ def _reflect_on_association(association) #:nodoc:
+ _reflections[association.to_s]
+ end
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
+ #
+ # @api public
def reflect_on_all_autosave_associations
reflections.values.select { |reflection| reflection.options[:autosave] }
end
@@ -129,6 +156,10 @@ module ActiveRecord
def autosave=(autosave)
@automatic_inverse_of = false
@options[:autosave] = autosave
+ _, parent_reflection = self.parent_reflection
+ if parent_reflection
+ parent_reflection.autosave = autosave
+ end
end
# Returns the class for the macro.
@@ -193,10 +224,11 @@ module ActiveRecord
end
attr_reader :type, :foreign_type
+ attr_accessor :parent_reflection # [:name, Reflection]
def initialize(macro, name, scope, options, active_record)
super
- @collection = :has_many == macro
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
@automatic_inverse_of = nil
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@@ -330,12 +362,12 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
def inverse_of
return unless inverse_name
- @inverse_of ||= klass.reflect_on_association inverse_name
+ @inverse_of ||= klass._reflect_on_association inverse_name
end
def polymorphic_inverse_of(associated_class)
if has_inverse?
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
inverse_relationship
else
raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -436,7 +468,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
begin
- reflection = klass.reflect_on_association(inverse_name)
+ reflection = klass._reflect_on_association(inverse_name)
rescue NameError
# Give up: we couldn't compute the klass type so we won't be able
# to find any associations either.
@@ -535,7 +567,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
- through_reflection.klass.reflect_on_association(source_reflection_name)
+ through_reflection.klass._reflect_on_association(source_reflection_name)
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -551,7 +583,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
- active_record.reflect_on_association(options[:through])
+ active_record._reflect_on_association(options[:through])
end
# Returns an array of reflections which are involved in this association. Each item in the
@@ -658,7 +690,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
names = names.find_all { |n|
- through_reflection.klass.reflect_on_association(n)
+ through_reflection.klass._reflect_on_association(n)
}
if names.length > 1
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 56cf9bcd27..d155517b18 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -277,7 +277,7 @@ module ActiveRecord
group_attrs = group_values
if group_attrs.first.respond_to?(:to_sym)
- association = @klass.reflect_on_association(group_attrs.first.to_sym)
+ association = @klass._reflect_on_association(group_attrs.first.to_sym)
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attrs)
else
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d40f276968..eff5c8f09c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -26,7 +26,7 @@ module ActiveRecord
queries << '1=0'
else
table = Arel::Table.new(column, default_table.engine)
- association = klass.reflect_on_association(column.to_sym)
+ association = klass._reflect_on_association(column.to_sym)
value.each do |k, v|
queries.concat expand(association && association.klass, table, k, v)
@@ -55,7 +55,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass.reflect_on_association(column.to_sym)
+ if klass && reflection = klass._reflect_on_association(column.to_sym)
if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
queries << build(table[reflection.foreign_type], base_class)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 2f6c34ac08..7f937e1875 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -4,26 +4,29 @@ module ActiveRecord
def call(attribute, value)
values = value.map { |x| x.is_a?(Base) ? x.id : x }
ranges, values = values.partition { |v| v.is_a?(Range) }
+ nils, values = values.partition(&:nil?)
- values_predicate = if values.include?(nil)
- values = values.compact
-
+ values_predicate =
case values.length
- when 0
- attribute.eq(nil)
- when 1
- attribute.eq(values.first).or(attribute.eq(nil))
- else
- attribute.in(values).or(attribute.eq(nil))
+ when 0 then NullPredicate
+ when 1 then attribute.eq(values.first)
+ else attribute.in(values)
end
- else
- attribute.in(values)
+
+ unless nils.empty?
+ values_predicate = values_predicate.or(attribute.eq(nil))
end
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
+
+ module NullPredicate
+ def self.or(other)
+ other
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 9a19483da3..e586744818 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -4,7 +4,7 @@ module ActiveRecord
def validate(record)
super
attributes.each do |attribute|
- next unless record.class.reflect_on_association(attribute)
+ next unless record.class._reflect_on_association(attribute)
associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index ee080451a9..b6fccc9b94 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -47,7 +47,7 @@ module ActiveRecord
end
def build_relation(klass, table, attribute, value) #:nodoc:
- if reflection = klass.reflect_on_association(attribute)
+ if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
end
@@ -74,7 +74,7 @@ module ActiveRecord
def scope_relation(record, table, relation)
Array(options[:scope]).each do |scope_item|
- if reflection = record.class.reflect_on_association(scope_item)
+ if reflection = record.class._reflect_on_association(scope_item)
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else