提交 7cba6a37 编写于 作者: J Josh Susser

association methods are now generated in modules

Instead of generating association methods directly in the model
class, they are generated in an anonymous module which
is then included in the model class. There is one such module
for each association. The only subtlety is that the
generated_attributes_methods module (from ActiveModel) must
be forced to be included before association methods are created
so that attribute methods will not shadow association methods.
上级 8d1a2b3e
...@@ -6,7 +6,7 @@ class Association #:nodoc: ...@@ -6,7 +6,7 @@ class Association #:nodoc:
# Set by subclasses # Set by subclasses
class_attribute :macro class_attribute :macro
attr_reader :model, :name, :options, :reflection attr_reader :model, :name, :options, :reflection, :mixin
def self.build(model, name, options) def self.build(model, name, options)
new(model, name, options).build new(model, name, options).build
...@@ -14,6 +14,8 @@ def self.build(model, name, options) ...@@ -14,6 +14,8 @@ def self.build(model, name, options)
def initialize(model, name, options) def initialize(model, name, options)
@model, @name, @options = model, name, options @model, @name, @options = model, name, options
@mixin = Module.new
@model.__send__(:include, @mixin)
end end
def build def build
...@@ -36,16 +38,14 @@ def define_accessors ...@@ -36,16 +38,14 @@ def define_accessors
def define_readers def define_readers
name = self.name name = self.name
mixin.send(:define_method, name) do |*params|
model.redefine_method(name) do |*params|
association(name).reader(*params) association(name).reader(*params)
end end
end end
def define_writers def define_writers
name = self.name name = self.name
mixin.send(:define_method, "#{name}=") do |value|
model.redefine_method("#{name}=") do |value|
association(name).writer(value) association(name).writer(value)
end end
end end
......
...@@ -25,14 +25,14 @@ def add_counter_cache_callbacks(reflection) ...@@ -25,14 +25,14 @@ def add_counter_cache_callbacks(reflection)
name = self.name name = self.name
method_name = "belongs_to_counter_cache_after_create_for_#{name}" method_name = "belongs_to_counter_cache_after_create_for_#{name}"
model.redefine_method(method_name) do mixin.send(:define_method, method_name) do
record = send(name) record = send(name)
record.class.increment_counter(cache_column, record.id) unless record.nil? record.class.increment_counter(cache_column, record.id) unless record.nil?
end end
model.after_create(method_name) model.after_create(method_name)
method_name = "belongs_to_counter_cache_before_destroy_for_#{name}" method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
model.redefine_method(method_name) do mixin.send(:define_method, method_name) do
record = send(name) record = send(name)
record.class.decrement_counter(cache_column, record.id) unless record.nil? record.class.decrement_counter(cache_column, record.id) unless record.nil?
end end
...@@ -48,7 +48,7 @@ def add_touch_callbacks(reflection) ...@@ -48,7 +48,7 @@ def add_touch_callbacks(reflection)
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}" method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
touch = options[:touch] touch = options[:touch]
model.redefine_method(method_name) do mixin.send(:define_method, method_name) do
record = send(name) record = send(name)
unless record.nil? unless record.nil?
......
...@@ -58,7 +58,7 @@ def define_readers ...@@ -58,7 +58,7 @@ def define_readers
super super
name = self.name name = self.name
model.redefine_method("#{name.to_s.singularize}_ids") do mixin.send(:define_method, "#{name.to_s.singularize}_ids") do
association(name).ids_reader association(name).ids_reader
end end
end end
...@@ -67,7 +67,7 @@ def define_writers ...@@ -67,7 +67,7 @@ def define_writers
super super
name = self.name name = self.name
model.redefine_method("#{name.to_s.singularize}_ids=") do |ids| mixin.send(:define_method, "#{name.to_s.singularize}_ids=") do |ids|
association(name).ids_writer(ids) association(name).ids_writer(ids)
end end
end end
......
...@@ -15,14 +15,10 @@ def build ...@@ -15,14 +15,10 @@ def build
def define_destroy_hook def define_destroy_hook
name = self.name name = self.name
model.send(:include, Module.new { mixin.send(:define_method, :destroy_associations) do
class_eval <<-RUBY, __FILE__, __LINE__ + 1 association(name).delete_all
def destroy_associations super()
association(#{name.to_sym.inspect}).delete_all end
super
end
RUBY
})
end end
# TODO: These checks should probably be moved into the Reflection, and we should not be # TODO: These checks should probably be moved into the Reflection, and we should not be
......
...@@ -28,7 +28,7 @@ def configure_dependency ...@@ -28,7 +28,7 @@ def configure_dependency
def define_destroy_dependency_method def define_destroy_dependency_method
name = self.name name = self.name
model.send(:define_method, dependency_method_name) do mixin.send(:define_method, dependency_method_name) do
send(name).each do |o| send(name).each do |o|
# No point in executing the counter update since we're going to destroy the parent anyway # No point in executing the counter update since we're going to destroy the parent anyway
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
...@@ -45,7 +45,7 @@ class << o ...@@ -45,7 +45,7 @@ class << o
def define_delete_all_dependency_method def define_delete_all_dependency_method
name = self.name name = self.name
model.send(:define_method, dependency_method_name) do mixin.send(:define_method, dependency_method_name) do
send(name).delete_all send(name).delete_all
end end
end end
...@@ -53,7 +53,7 @@ def define_delete_all_dependency_method ...@@ -53,7 +53,7 @@ def define_delete_all_dependency_method
def define_restrict_dependency_method def define_restrict_dependency_method
name = self.name name = self.name
model.send(:define_method, dependency_method_name) do mixin.send(:define_method, dependency_method_name) do
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty? raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
end end
end end
......
...@@ -44,18 +44,17 @@ def dependency_method_name ...@@ -44,18 +44,17 @@ def dependency_method_name
end end
def define_destroy_dependency_method def define_destroy_dependency_method
model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1) name = self.name
def #{dependency_method_name} mixin.send(:define_method, dependency_method_name) do
association(#{name.to_sym.inspect}).delete association(name).delete
end end
eoruby
end end
alias :define_delete_dependency_method :define_destroy_dependency_method alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method alias :define_nullify_dependency_method :define_destroy_dependency_method
def define_restrict_dependency_method def define_restrict_dependency_method
name = self.name name = self.name
model.redefine_method(dependency_method_name) do mixin.send(:define_method, dependency_method_name) do
raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil? raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
end end
end end
......
...@@ -16,15 +16,15 @@ def define_accessors ...@@ -16,15 +16,15 @@ def define_accessors
def define_constructors def define_constructors
name = self.name name = self.name
model.redefine_method("build_#{name}") do |*params, &block| mixin.send(:define_method, "build_#{name}") do |*params, &block|
association(name).build(*params, &block) association(name).build(*params, &block)
end end
model.redefine_method("create_#{name}") do |*params, &block| mixin.send(:define_method, "create_#{name}") do |*params, &block|
association(name).create(*params, &block) association(name).create(*params, &block)
end end
model.redefine_method("create_#{name}!") do |*params, &block| mixin.send(:define_method, "create_#{name}!") do |*params, &block|
association(name).create!(*params, &block) association(name).create!(*params, &block)
end end
end end
......
...@@ -8,6 +8,12 @@ module AttributeMethods #:nodoc: ...@@ -8,6 +8,12 @@ module AttributeMethods #:nodoc:
include ActiveModel::AttributeMethods include ActiveModel::AttributeMethods
module ClassMethods module ClassMethods
def inherited(child_class)
# force creation + include before accessor method modules
child_class.generated_attribute_methods
super
end
# Generates all the attribute related methods for columns in the database # Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods. # accessors, mutators and query methods.
def define_attribute_methods def define_attribute_methods
......
require "cases/helper" require "cases/helper"
require 'models/computer'
require 'models/developer' require 'models/developer'
require 'models/project' require 'models/project'
require 'models/company' require 'models/company'
...@@ -273,3 +274,14 @@ def test_has_one_association_redefinition_reflections_should_differ_and_not_inhe ...@@ -273,3 +274,14 @@ def test_has_one_association_redefinition_reflections_should_differ_and_not_inhe
) )
end end
end end
class GeneratedMethodsTest < ActiveRecord::TestCase
fixtures :developers, :computers
def test_association_methods_override_attribute_methods_of_same_name
assert_equal(developers(:david), computers(:workstation).developer)
# this next line will fail if the attribute methods module is generated lazily
# after the association methods module is generated
assert_equal(developers(:david), computers(:workstation).developer)
assert_equal(developers(:david).id, computers(:workstation)[:developer])
end
end
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册