diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/builder')
6 files changed, 118 insertions, 108 deletions
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index bbc1c20f60..34de1a1f32 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -2,7 +2,7 @@ # used by all associations. # # The hierarchy is defined as follows: -# Association +# Association # - SingularAssociation # - BelongsToAssociation # - HasOneAssociation @@ -13,50 +13,44 @@ module ActiveRecord::Associations::Builder class Association #:nodoc: class << self - attr_accessor :valid_options + attr_accessor :extensions end + self.extensions = [] - self.valid_options = [:class_name, :foreign_key, :validate] + VALID_OPTIONS = [:class_name, :foreign_key, :validate] - attr_reader :model, :name, :scope, :options, :reflection + attr_reader :name, :scope, :options - def self.build(*args, &block) - new(*args, &block).build - end - - def initialize(model, name, scope, options) + def self.build(model, name, scope, options, &block) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) - @model = model - @name = name - if scope.is_a?(Hash) - @scope = nil - @options = scope - else - @scope = scope - @options = options + options = scope + scope = nil end - if @scope && @scope.arity == 0 - prev_scope = @scope - @scope = proc { instance_exec(&prev_scope) } - end - end - - def mixin - @model.generated_feature_methods + builder = new(name, scope, options, &block) + reflection = builder.build(model) + builder.define_accessors model + builder.define_callbacks model, reflection + builder.define_extensions model + reflection end - include Module.new { def build; end } + def initialize(name, scope, options) + @name = name + @scope = scope + @options = options - def build validate_options - define_accessors - configure_dependency if options[:dependent] - @reflection = model.create_reflection(macro, name, scope, options, model) - super # provides an extension point - @reflection + + if scope && scope.arity == 0 + @scope = proc { instance_exec(&scope) } + end + end + + def build(model) + ActiveRecord::Reflection.create(macro, name, scope, options, model) end def macro @@ -64,26 +58,37 @@ module ActiveRecord::Associations::Builder end def valid_options - Association.valid_options + VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) end def validate_options options.assert_valid_keys(valid_options) end - + + def define_extensions(model) + end + + def define_callbacks(model, reflection) + add_before_destroy_callbacks(model, name) if options[:dependent] + Association.extensions.each do |extension| + extension.build model, reflection + end + end + # Defines the setter and getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end - # + # # Post.first.comments and Post.first.comments= methods are defined by this method... - def define_accessors - define_readers - define_writers + def define_accessors(model) + mixin = model.generated_feature_methods + define_readers(mixin) + define_writers(mixin) end - def define_readers + def define_readers(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}(*args) association(:#{name}).reader(*args) @@ -91,7 +96,7 @@ module ActiveRecord::Associations::Builder CODE end - def define_writers + def define_writers(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) @@ -99,17 +104,18 @@ module ActiveRecord::Associations::Builder CODE end - def configure_dependency + def valid_dependent_options + raise NotImplementedError + end + + private + + def add_before_destroy_callbacks(model, name) unless valid_dependent_options.include? options[:dependent] raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}" end - n = name - model.before_destroy lambda { |o| o.association(n).handle_dependency } - end - - def valid_dependent_options - raise NotImplementedError + model.before_destroy lambda { |o| o.association(name).handle_dependency } end end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 81293e464d..4e88b50ec5 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -12,17 +12,21 @@ module ActiveRecord::Associations::Builder !options[:polymorphic] end - def build - reflection = super - add_counter_cache_callbacks(reflection) if options[:counter_cache] - add_touch_callbacks(reflection) if options[:touch] - reflection - end - def valid_dependent_options [:destroy, :delete] end + def define_callbacks(model, reflection) + super + add_counter_cache_callbacks(model, reflection) if options[:counter_cache] + add_touch_callbacks(model, reflection) if options[:touch] + end + + def define_accessors(mixin) + super + add_counter_cache_methods mixin + end + private def add_counter_cache_methods(mixin) @@ -70,9 +74,8 @@ module ActiveRecord::Associations::Builder end end - def add_counter_cache_callbacks(reflection) + def add_counter_cache_callbacks(model, reflection) cache_column = reflection.counter_cache_column - add_counter_cache_methods mixin association = self model.after_create lambda { |record| @@ -117,7 +120,7 @@ module ActiveRecord::Associations::Builder end end - def add_touch_callbacks(reflection) + def add_touch_callbacks(model, reflection) foreign_key = reflection.foreign_key n = name touch = options[:touch] diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 5eb11f98c8..7bd0687c0b 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -12,56 +12,50 @@ module ActiveRecord::Associations::Builder :after_add, :before_remove, :after_remove, :extend] end - attr_reader :block_extension, :extension_module + attr_reader :block_extension - def initialize(*args, &extension) - super(*args) - @block_extension = extension - end - - def build - wrap_block_extension - reflection = super - CALLBACKS.each { |callback_name| define_callback(callback_name) } - reflection + def initialize(name, scope, options) + super + @mod = nil + if block_given? + @mod = Module.new(&Proc.new) + @scope = wrap_scope @scope, @mod + end end - def writable? - true + def define_callbacks(model, reflection) + super + CALLBACKS.each { |callback_name| define_callback(model, callback_name) } end - def wrap_block_extension - if block_extension - @extension_module = mod = Module.new(&block_extension) - silence_warnings do - model.parent.const_set(extension_module_name, mod) - end - - prev_scope = @scope - - if prev_scope - @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) } - else - @scope = proc { extending(mod) } - end + def define_extensions(model) + if @mod + extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" + model.parent.const_set(extension_module_name, @mod) end end - def extension_module_name - @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" - end - - def define_callback(callback_name) + def define_callback(model, callback_name) full_callback_name = "#{callback_name}_for_#{name}" # TODO : why do i need method_defined? I think its because of the inheritance chain - model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name) - model.send("#{full_callback_name}=", Array(options[callback_name.to_sym])) + model.class_attribute full_callback_name unless model.method_defined?(full_callback_name) + callbacks = Array(options[callback_name.to_sym]).map do |callback| + case callback + when Symbol + ->(method, owner, record) { owner.send(callback, record) } + when Proc + ->(method, owner, record) { callback.call(owner, record) } + else + ->(method, owner, record) { callback.send(method, owner, record) } + end + end + model.send "#{full_callback_name}=", callbacks end # Defines the setter and getter methods for the collection_singular_ids. - def define_readers + def define_readers(mixin) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 @@ -71,7 +65,7 @@ module ActiveRecord::Associations::Builder CODE end - def define_writers + def define_writers(mixin) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 @@ -80,5 +74,15 @@ module ActiveRecord::Associations::Builder end CODE end + + private + + def wrap_scope(scope, mod) + if scope + proc { |owner| instance_exec(owner, &scope).extending(mod) } + else + proc { extending(mod) } + end + end end end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 64ee24c7c6..55ec3bf4f1 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -8,13 +8,8 @@ module ActiveRecord::Associations::Builder super + [:join_table, :association_foreign_key] end - def build - reflection = super - define_destroy_hook - reflection - end - - def define_destroy_hook + def define_callbacks(model, reflection) + super name = self.name model.send(:include, Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 2406437a07..62d454ce55 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -14,12 +14,14 @@ module ActiveRecord::Associations::Builder !options[:through] end - def configure_dependency - super unless options[:through] - end - def valid_dependent_options [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end + + private + + def add_before_destroy_callbacks(model, name) + super unless options[:through] + end end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 76e48e66e5..d97c0e9afd 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -10,14 +10,14 @@ module ActiveRecord::Associations::Builder true end - def define_accessors + def define_accessors(model) super - define_constructors if constructable? + define_constructors(model.generated_feature_methods) if constructable? end # Defines the (build|create)_association methods for belongs_to or has_one association - - def define_constructors + + def define_constructors(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) association(:#{name}).build(*args, &block) |