Add special AssociationReflection methods for creating association objects, and modify the code base to use those methods instead of creating association objects directly. This allows plugins to hook into association object creation behavior.

[#986 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
This commit is contained in:
Hongli Lai (Phusion)
2008-09-06 19:43:14 +02:00
committed by Jeremy Kemper
parent 1692940441
commit 1398db0128
7 changed files with 62 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
*Edge*
* Internal API: configurable association options so plugins may extend and override. #985 [Hongli Lai]
* Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 [Hongli Lai]
* Changed benchmarks to be reported in milliseconds [DHH]

View File

@@ -1266,7 +1266,7 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
new_value = reflection.build_association(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
if association_proxy_class == HasOneThroughAssociation
association.create_through_record(new_value)

View File

@@ -110,7 +110,7 @@ module ActiveRecord
@owner.transaction do
flatten_deeper(records).each do |record|
record = @reflection.klass.new(record) if @reflection.options[:accessible] && record.is_a?(Hash)
record = @reflection.build_association(record) if @reflection.options[:accessible] && record.is_a?(Hash)
raise_on_type_mismatch(record)
add_record_to_target_with_callbacks(record) do |r|
@@ -287,7 +287,7 @@ module ActiveRecord
# This will perform a diff and delete/add only records that have changed.
def replace(other_array)
other_array.map! do |val|
val.is_a?(Hash) ? @reflection.klass.new(val) : val
val.is_a?(Hash) ? @reflection.build_association(val) : val
end if @reflection.options[:accessible]
other_array.each { |val| raise_on_type_mismatch(val) }
@@ -377,7 +377,9 @@ module ActiveRecord
def create_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
ensure_owner_is_not_new
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
@reflection.build_association(attrs)
end
if block_given?
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
else
@@ -387,7 +389,7 @@ module ActiveRecord
def build_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
record = @reflection.klass.new(attrs)
record = @reflection.build_association(attrs)
if block_given?
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
else

View File

@@ -2,11 +2,11 @@ module ActiveRecord
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
def create(attributes = {})
replace(@reflection.klass.create(attributes))
replace(@reflection.create_association(attributes))
end
def build(attributes = {})
replace(@reflection.klass.new(attributes))
replace(@reflection.build_association(attributes))
end
def replace(record)

View File

@@ -10,14 +10,14 @@ module ActiveRecord
def create!(attrs = nil)
@reflection.klass.transaction do
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
object
end
end
def create(attrs = nil)
@reflection.klass.transaction do
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
object
end
end
@@ -47,8 +47,9 @@ module ActiveRecord
return false unless record.save
end
end
klass = @reflection.through_reflection.klass
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
through_reflection = @reflection.through_reflection
klass = through_reflection.klass
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
end
# TODO - add dependent option support

View File

@@ -7,15 +7,21 @@ module ActiveRecord
end
def create(attrs = {}, replace_existing = true)
new_record(replace_existing) { |klass| klass.create(attrs) }
new_record(replace_existing) do |reflection|
reflection.create_association(attrs)
end
end
def create!(attrs = {}, replace_existing = true)
new_record(replace_existing) { |klass| klass.create!(attrs) }
new_record(replace_existing) do |reflection|
reflection.create_association!(attrs)
end
end
def build(attrs = {}, replace_existing = true)
new_record(replace_existing) { |klass| klass.new(attrs) }
new_record(replace_existing) do |reflection|
reflection.build_association(attrs)
end
end
def replace(obj, dont_save = false)
@@ -91,7 +97,9 @@ module ActiveRecord
# instance. Otherwise, if the target has not previously been loaded
# elsewhere, the instance we create will get orphaned.
load_target if replace_existing
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
yield @reflection
end
if replace_existing
replace(record, true)

View File

@@ -129,10 +129,45 @@ module ActiveRecord
# Holds all the meta-data about an association as it was specified in the Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
# Returns the target association's class:
#
# class Author < ActiveRecord::Base
# has_many :books
# end
#
# Author.reflect_on_association(:books).klass
# # => Book
#
# <b>Note:</b> do not call +klass.new+ or +klass.create+ to instantiate
# a new association object. Use +build_association+ or +create_association+
# instead. This allows plugins to hook into association object creation.
def klass
@klass ||= active_record.send(:compute_type, class_name)
end
# Returns a new, unsaved instance of the associated class. +options+ will
# be passed to the class's constructor.
def build_association(*options)
klass.new(*options)
end
# Creates a new instance of the associated class, and immediates saves it
# with ActiveRecord::Base#save. +options+ will be passed to the class's
# creation method. Returns the newly created object.
def create_association(*options)
klass.create(*options)
end
# Creates a new instance of the associated class, and immediates saves it
# with ActiveRecord::Base#save!. +options+ will be passed to the class's
# creation method. If the created record doesn't pass validations, then an
# exception will be raised.
#
# Returns the newly created object.
def create_association!(*options)
klass.create!(*options)
end
def table_name
@table_name ||= klass.table_name
end