Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added preliminary support for join models [DHH] Added preliminary sup…
…port for polymorphic associations [DHH] Refactored associations to use reflections to get DRYer, beware, major refactoring -- double check before deploying anything with this (all tests pass, but..)

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3213 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
dhh committed Dec 3, 2005
1 parent 57b7532 commit 6abda69
Show file tree
Hide file tree
Showing 17 changed files with 520 additions and 446 deletions.
4 changes: 4 additions & 0 deletions activerecord/CHANGELOG
@@ -1,5 +1,9 @@
*SVN*

* Added preliminary support for polymorphic associations [DHH]

* Added preliminary support for join models [DHH]

* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]

* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz <kennethkunz@gmail.com>]
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record.rb
Expand Up @@ -38,10 +38,10 @@
require 'active_record/observer'
require 'active_record/validations'
require 'active_record/callbacks'
require 'active_record/reflection'
require 'active_record/associations'
require 'active_record/aggregations'
require 'active_record/transactions'
require 'active_record/reflection'
require 'active_record/timestamp'
require 'active_record/acts/list'
require 'active_record/acts/tree'
Expand Down
5 changes: 3 additions & 2 deletions activerecord/lib/active_record/aggregations.rb
@@ -1,7 +1,6 @@
module ActiveRecord
module Aggregations # :nodoc:
def self.append_features(base)
super
def self.included(base)
base.extend(ClassMethods)
end

Expand Down Expand Up @@ -128,6 +127,8 @@ def composed_of(part_id, options = {})

reader_method(name, class_name, mapping)
writer_method(name, class_name, mapping)

create_reflection(:composed_of, part_id, options, self)
end

private
Expand Down
383 changes: 202 additions & 181 deletions activerecord/lib/active_record/associations.rb

Large diffs are not rendered by default.

Expand Up @@ -18,6 +18,7 @@ def reset
def <<(*records)
result = true
load_target

@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
Expand All @@ -28,7 +29,7 @@ def <<(*records)
end
end

result and self
result && self
end

alias_method :push, :<<
Expand Down Expand Up @@ -60,11 +61,13 @@ def delete(*records)
# Removes all records from this association. Returns +self+ so method calls may be chained.
def clear
return self if length.zero? # forces load_target if hasn't happened already
if @options[:exclusively_dependent]

if @reflection.options[:exclusively_dependent]
destroy_all
else
delete_all
end

self
end

Expand Down Expand Up @@ -124,14 +127,6 @@ def replace(other_array)
end

private
def raise_on_type_mismatch(record)
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
end

def target_obsolete?
false
end

# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
Expand All @@ -155,8 +150,8 @@ def callback(method, record)
end

def callbacks_for(callback_name)
full_callback_name = "#{callback_name.to_s}_for_#{@association_name.to_s}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) or []
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
end

end
Expand Down
38 changes: 20 additions & 18 deletions activerecord/lib/active_record/associations/association_proxy.rb
Expand Up @@ -5,15 +5,9 @@ class AssociationProxy #:nodoc:
alias_method :proxy_extend, :extend
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
@owner = owner
@options = options
@association_name = association_name
@association_class = eval(association_class_name, nil, __FILE__, __LINE__)
@association_class_primary_key_name = association_class_primary_key_name

proxy_extend(options[:extend]) if options[:extend]

def initialize(owner, reflection)
@owner, @reflection = owner, reflection
proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
reset
end

Expand All @@ -28,6 +22,11 @@ def ===(other)
other === @target
end

def reset
@target = nil
@loaded = false
end

def reload
reset
load_target
Expand All @@ -45,14 +44,14 @@ def target
@target
end

def target=(t)
@target = t
@loaded = true
def target=(target)
@target = target
loaded
end

protected
def dependent?
@options[:dependent] || false
@reflection.options[:dependent] || false
end

def quoted_record_ids(records)
Expand All @@ -68,7 +67,7 @@ def interpolate_sql(sql, record = nil)
end

def sanitize_sql(sql)
@association_class.send(:sanitize_sql, sql)
@reflection.klass.send(:sanitize_sql, sql)
end

def extract_options_from_args!(args)
Expand All @@ -84,13 +83,14 @@ def method_missing(method, *args, &block)
def load_target
if !@owner.new_record? || foreign_key_present
begin
@target = find_target if not loaded?
@target = find_target if !loaded?
rescue ActiveRecord::RecordNotFound
reset
end
end
@loaded = true if @target
@target

loaded if target
target
end

# Can be overwritten by associations that might have the foreign key available for an association without
Expand All @@ -100,7 +100,9 @@ def foreign_key_present
end

def raise_on_type_mismatch(record)
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
unless record.is_a?(@reflection.klass)
raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
end
end
end
end
Expand Down
@@ -1,41 +1,27 @@
module ActiveRecord
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
super
construct_sql
end

def reset
@target = nil
@loaded = false
end

def create(attributes = {})
record = @association_class.create(attributes)
replace(record, true)
record
replace(@reflection.klass.create(attributes))
end

def build(attributes = {})
record = @association_class.new(attributes)
replace(record, true)
record
replace(@reflection.klass.new(attributes))
end

def replace(obj, dont_save = false)
if obj.nil?
@target = @owner[@association_class_primary_key_name] = nil
def replace(record)
if record.nil?
@target = @owner[@reflection.primary_key_name] = nil
else
raise_on_type_mismatch(obj) unless obj.nil?
raise_on_type_mismatch(record)

@target = (AssociationProxy === obj ? obj.target : obj)
@owner[@association_class_primary_key_name] = obj.id unless obj.new_record?
@target = (AssociationProxy === record ? record.target : record)
@owner[@reflection.primary_key_name] = record.id unless record.new_record?
@updated = true
end
@loaded = true

return (@target.nil? ? nil : self)
loaded
record
end

def updated?
Expand All @@ -44,27 +30,15 @@ def updated?

private
def find_target
if @options[:conditions]
@association_class.find(
@owner[@association_class_primary_key_name],
:conditions => interpolate_sql(@options[:conditions]),
:include => @options[:include]
)
else
@association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
end
@reflection.klass.find(
@owner[@reflection.primary_key_name],
:conditions => @reflection.options[:conditions] ? interpolate_sql(@reflection.options[:conditions]) : nil,
:include => @reflection.options[:include]
)
end

def foreign_key_present
!@owner[@association_class_primary_key_name].nil?
end

def target_obsolete?
@owner[@association_class_primary_key_name] != @target.id
end

def construct_sql
@finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
!@owner[@reflection.primary_key_name].nil?
end
end
end
Expand Down
@@ -1,69 +1,49 @@
module ActiveRecord
module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
@owner = owner
@options = options
@association_name = association_name
@association_class_primary_key_name = association_class_primary_key_name

proxy_extend(options[:extend]) if options[:extend]

reset
end

def create(attributes = {})
raise ActiveRecord::ActiveRecordError, "Can't create an abstract polymorphic object"
end

def build(attributes = {})
raise ActiveRecord::ActiveRecordError, "Can't build an abstract polymorphic object"
end

def replace(obj, dont_save = false)
if obj.nil?
@target = @owner[@association_class_primary_key_name] = @owner[@options[:foreign_type]] = nil
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
def replace(record)
if record.nil?
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
else
@target = (AssociationProxy === obj ? obj.target : obj)
@target = (AssociationProxy === record ? record.target : record)

unless obj.new_record?
@owner[@association_class_primary_key_name] = obj.id
@owner[@options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, obj.class).to_s
unless record.new_record?
@owner[@reflection.primary_key_name] = record.id
@owner[@reflection.options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, record.class).to_s
end

@updated = true
end

@loaded = true
loaded
record
end

return (@target.nil? ? nil : self)
def updated?
@updated
end

private
def find_target
return nil if association_class.nil?

if @options[:conditions]
if @reflection.options[:conditions]
association_class.find(
@owner[@association_class_primary_key_name],
:conditions => interpolate_sql(@options[:conditions]),
:include => @options[:include]
@owner[@reflection.primary_key_name],
:conditions => interpolate_sql(@reflection.options[:conditions]),
:include => @reflection.options[:include]
)
else
association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
end
end

def foreign_key_present
!@owner[@association_class_primary_key_name].nil?
!@owner[@reflection.primary_key_name].nil?
end

def target_obsolete?
@owner[@association_class_primary_key_name] != @target.id
end

def association_class
@owner[@options[:foreign_type]] ? @owner[@options[:foreign_type]].constantize : nil
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
end
end
end
Expand Down

0 comments on commit 6abda69

Please sign in to comment.