提交 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:
# Set by subclasses
class_attribute :macro
attr_reader :model, :name, :options, :reflection
attr_reader :model, :name, :options, :reflection, :mixin
def self.build(model, name, options)
new(model, name, options).build
......@@ -14,6 +14,8 @@ def self.build(model, name, options)
def initialize(model, name, options)
@model, @name, @options = model, name, options
@mixin = Module.new
@model.__send__(:include, @mixin)
end
def build
......@@ -36,16 +38,14 @@ def define_accessors
def define_readers
name = self.name
model.redefine_method(name) do |*params|
mixin.send(:define_method, name) do |*params|
association(name).reader(*params)
end
end
def define_writers
name = self.name
model.redefine_method("#{name}=") do |value|
mixin.send(:define_method, "#{name}=") do |value|
association(name).writer(value)
end
end
......
......@@ -25,14 +25,14 @@ def add_counter_cache_callbacks(reflection)
name = self.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.class.increment_counter(cache_column, record.id) unless record.nil?
end
model.after_create(method_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.class.decrement_counter(cache_column, record.id) unless record.nil?
end
......@@ -48,7 +48,7 @@ def add_touch_callbacks(reflection)
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
touch = options[:touch]
model.redefine_method(method_name) do
mixin.send(:define_method, method_name) do
record = send(name)
unless record.nil?
......
......@@ -58,7 +58,7 @@ def define_readers
super
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
end
end
......@@ -67,7 +67,7 @@ def define_writers
super
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)
end
end
......
......@@ -15,14 +15,10 @@ def build
def define_destroy_hook
name = self.name
model.send(:include, Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
association(#{name.to_sym.inspect}).delete_all
super
end
RUBY
})
mixin.send(:define_method, :destroy_associations) do
association(name).delete_all
super()
end
end
# TODO: These checks should probably be moved into the Reflection, and we should not be
......
......@@ -28,7 +28,7 @@ def configure_dependency
def define_destroy_dependency_method
name = self.name
model.send(:define_method, dependency_method_name) do
mixin.send(:define_method, dependency_method_name) do
send(name).each do |o|
# 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
......@@ -45,7 +45,7 @@ class << o
def define_delete_all_dependency_method
name = self.name
model.send(:define_method, dependency_method_name) do
mixin.send(:define_method, dependency_method_name) do
send(name).delete_all
end
end
......@@ -53,7 +53,7 @@ def define_delete_all_dependency_method
def define_restrict_dependency_method
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?
end
end
......
......@@ -44,18 +44,17 @@ def dependency_method_name
end
def define_destroy_dependency_method
model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
def #{dependency_method_name}
association(#{name.to_sym.inspect}).delete
name = self.name
mixin.send(:define_method, dependency_method_name) do
association(name).delete
end
eoruby
end
alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method
def define_restrict_dependency_method
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?
end
end
......
......@@ -16,15 +16,15 @@ def define_accessors
def define_constructors
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)
end
model.redefine_method("create_#{name}") do |*params, &block|
mixin.send(:define_method, "create_#{name}") do |*params, &block|
association(name).create(*params, &block)
end
model.redefine_method("create_#{name}!") do |*params, &block|
mixin.send(:define_method, "create_#{name}!") do |*params, &block|
association(name).create!(*params, &block)
end
end
......
......@@ -8,6 +8,12 @@ module AttributeMethods #:nodoc:
include ActiveModel::AttributeMethods
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
# accessors, mutators and query methods.
def define_attribute_methods
......
require "cases/helper"
require 'models/computer'
require 'models/developer'
require 'models/project'
require 'models/company'
......@@ -273,3 +274,14 @@ def test_has_one_association_redefinition_reflections_should_differ_and_not_inhe
)
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.
先完成此消息的编辑!
想要评论请 注册