mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
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.
This commit is contained in:
@@ -6,7 +6,7 @@ module ActiveRecord::Associations::Builder
|
||||
# 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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
end
|
||||
|
||||
def define_destroy_dependency_method
|
||||
model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
|
||||
def #{dependency_method_name}
|
||||
association(#{name.to_sym.inspect}).delete
|
||||
end
|
||||
eoruby
|
||||
name = self.name
|
||||
mixin.send(:define_method, dependency_method_name) do
|
||||
association(name).delete
|
||||
end
|
||||
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 @@ module ActiveRecord::Associations::Builder
|
||||
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 ActiveRecord
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require "cases/helper"
|
||||
require 'models/computer'
|
||||
require 'models/developer'
|
||||
require 'models/project'
|
||||
require 'models/company'
|
||||
@@ -273,3 +274,14 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
|
||||
)
|
||||
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
|
||||
Reference in New Issue
Block a user