Skip to content

Commit

Permalink
Added preliminary support for join models [DHH] Added preliminary sup…
Browse files Browse the repository at this point in the history
…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* *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.] * 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>] * 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/observer'
require 'active_record/validations' require 'active_record/validations'
require 'active_record/callbacks' require 'active_record/callbacks'
require 'active_record/reflection'
require 'active_record/associations' require 'active_record/associations'
require 'active_record/aggregations' require 'active_record/aggregations'
require 'active_record/transactions' require 'active_record/transactions'
require 'active_record/reflection'
require 'active_record/timestamp' require 'active_record/timestamp'
require 'active_record/acts/list' require 'active_record/acts/list'
require 'active_record/acts/tree' 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 ActiveRecord
module Aggregations # :nodoc: module Aggregations # :nodoc:
def self.append_features(base) def self.included(base)
super
base.extend(ClassMethods) base.extend(ClassMethods)
end end


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


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

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


private 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) def <<(*records)
result = true result = true
load_target load_target

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


result and self result && self
end end


alias_method :push, :<< 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. # Removes all records from this association. Returns +self+ so method calls may be chained.
def clear def clear
return self if length.zero? # forces load_target if hasn't happened already return self if length.zero? # forces load_target if hasn't happened already
if @options[:exclusively_dependent]
if @reflection.options[:exclusively_dependent]
destroy_all destroy_all
else else
delete_all delete_all
end end

self self
end end


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


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


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


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 alias_method :proxy_extend, :extend
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ } 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) def initialize(owner, reflection)
@owner = owner @owner, @reflection = owner, reflection
@options = options proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
@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]

reset reset
end end


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


def reset
@target = nil
@loaded = false
end

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


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


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


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


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


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


# Can be overwritten by associations that might have the foreign key available for an association without # 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 end


def raise_on_type_mismatch(record) 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 end
end end
Expand Down
@@ -1,41 +1,27 @@
module ActiveRecord module ActiveRecord
module Associations module Associations
class BelongsToAssociation < AssociationProxy #:nodoc: 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 = {}) def create(attributes = {})
record = @association_class.create(attributes) replace(@reflection.klass.create(attributes))
replace(record, true)
record
end end


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


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


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


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


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


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


def foreign_key_present 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 construct_sql
@finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
end end
end end
end end
Expand Down
@@ -1,69 +1,49 @@
module ActiveRecord module ActiveRecord
module Associations module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options) def replace(record)
@owner = owner if record.nil?
@options = options @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
@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
else else
@target = (AssociationProxy === obj ? obj.target : obj) @target = (AssociationProxy === record ? record.target : record)


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


@updated = true @updated = true
end end


@loaded = true loaded
record
end


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

private private
def find_target def find_target
return nil if association_class.nil? return nil if association_class.nil?


if @options[:conditions] if @reflection.options[:conditions]
association_class.find( association_class.find(
@owner[@association_class_primary_key_name], @owner[@reflection.primary_key_name],
:conditions => interpolate_sql(@options[:conditions]), :conditions => interpolate_sql(@reflection.options[:conditions]),
:include => @options[:include] :include => @reflection.options[:include]
) )
else 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
end end


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


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

def association_class 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 end
end end
Expand Down

0 comments on commit 6abda69

Please sign in to comment.