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

@ -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]

@ -1266,7 +1266,7 @@ def association_accessor_methods(reflection, association_proxy_class)
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)

@ -110,7 +110,7 @@ def <<(*records)
@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 @@ def uniq(collection = self)
# 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 @@ def find_target
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 @@ def create_record(attrs)
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

@ -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)

@ -10,14 +10,14 @@ def initialize(owner, reflection)
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 @@ def insert_record(record, force=true)
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

@ -7,15 +7,21 @@ def initialize(owner, reflection)
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 @@ def new_record(replace_existing)
# 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)

@ -129,10 +129,45 @@ class AggregateReflection < MacroReflection #:nodoc:
# 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