Permalink
Browse files

Add special AssociationReflection methods for creating association ob…

…jects, 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>
  • Loading branch information...
1 parent 1692940 commit 1398db0128be7ae01700712eafc95be5de430f7c @FooBarWidget FooBarWidget committed with jeremy Sep 6, 2008
View
@@ -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

0 comments on commit 1398db0

Please sign in to comment.