Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #3005: association-duplicate-names-prevent-v3.patch

File association-duplicate-names-prevent-v3.patch, 5.4 kB (added by François Beausoleil <francois.beausoleil@gmail.com>, 3 years ago)

Updated to reflect mail thread

  • activerecord/test/associations_test.rb

    old new  
    258258    assert_equal a, firm.account 
    259259    assert_equal a, firm.account(true) 
    260260  end 
     261 
     262  def test_prevent_naming_association_with_existing_name 
     263    firm = Firm.new 
     264    assert_raise ActiveRecord::Reflection::ReflectionNameAlreadyTaken do 
     265      class << firm 
     266        has_one :quote 
     267      end 
     268    end 
     269  end 
    261270end 
    262271 
    263272 
     
    741750    assert_equal 2, firm.clients.length 
    742751    assert firm.clients.include?(companies(:second_client)) 
    743752  end 
     753 
     754  def test_prevent_naming_association_with_existing_name 
     755    firm = Firm.new 
     756    assert_raise ActiveRecord::Reflection::ReflectionNameAlreadyTaken do 
     757      class << firm 
     758        has_many :columns 
     759      end 
     760    end 
     761  end 
    744762end 
    745763 
    746764class BelongsToAssociationsTest < Test::Unit::TestCase 
     
    9971015    # the author id of the post should be the id we set 
    9981016    assert_equal post.author_id, author2.id 
    9991017  end 
    1000    
     1018 
     1019  def test_prevent_naming_association_with_existing_name 
     1020    firm = Firm.new 
     1021    assert_raise ActiveRecord::Reflection::ReflectionNameAlreadyTaken do 
     1022      class << firm 
     1023        belongs_to :quote 
     1024      end 
     1025    end 
     1026  end 
    10011027end 
    10021028 
    10031029 
     
    14171443      AND developer_id = #{developer.id} 
    14181444    end_sql 
    14191445  end 
     1446 
     1447  def test_prevent_naming_association_with_existing_name 
     1448    firm = Firm.new 
     1449    assert_raise ActiveRecord::Reflection::ReflectionNameAlreadyTaken do 
     1450      class << firm 
     1451        has_and_belongs_to_many :columns 
     1452      end 
     1453    end 
     1454  end 
    14201455end 
  • activerecord/test/aggregations_test.rb

    old new  
    6363  def test_gps_inequality 
    6464    assert GpsLocation.new('39x110') != GpsLocation.new('39x111') 
    6565  end 
     66 
     67  def test_prevent_naming_aggregation_with_existing_name 
     68    assert_raise ActiveRecord::Reflection::ReflectionNameAlreadyTaken do 
     69      class << customers(:david) 
     70        composed_of :base_class, :class_name => 'Money' 
     71      end 
     72    end 
     73  end 
    6674end 
  • activerecord/lib/active_record/reflection.rb

    old new  
    11module ActiveRecord 
    22  module Reflection # :nodoc: 
     3    class ReflectionNameAlreadyTaken < ActiveRecord::ConfigurationError; end 
     4 
    35    def self.included(base) 
    46      base.extend(ClassMethods) 
    57    end 
     
    1113    # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class. 
    1214    module ClassMethods 
    1315      def create_reflection(macro, name, options, active_record) 
     16        guard_against_already_used_reflection_name(name, active_record) 
    1417        case macro 
    1518          when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many 
    1619            reflection = AssociationReflection.new(macro, name, options, active_record) 
     
    4851      def reflect_on_association(association) 
    4952        reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil 
    5053      end 
     54 
     55      protected 
     56      def guard_against_already_used_reflection_name(name, active_record) 
     57        methods = active_record.instance_methods 
     58        methods += ActiveRecord::Base.methods 
     59        methods -= Object.methods 
     60        methods.uniq! 
     61        methods -= %w(name type) 
     62        return unless methods.include?(name.to_s) 
     63 
     64        # Raise the exception and say WHERE we found the method (very helpful) 
     65        raise_reflection_already_taken(active_record, name, :instance_methods) 
     66        raise_reflection_already_taken(ActiveRecord::Base, name, :methods) 
     67 
     68        # We couldn't find it !  Wow, that's bad.  Real bad... 
     69        raise ReflectionNameAlreadyTaken, "`#{name}' is the name of an existing method in #{active_record.name} (but I can't find where this method is already defined !).  You will have to rename your association." 
     70      end 
     71 
     72      def raise_reflection_already_taken(klass, association_name, target) 
     73        loop do 
     74          methods = klass.send(target) 
     75          methods -= klass.superclass.send(target) if klass.superclass 
     76          raise ReflectionNameAlreadyTaken, "`#{association_name}' is the name of an existing method in #{active_record.name} (class #{klass.name} [#{target}]).  You will have to rename your association." if methods.include?(association_name) 
     77          return if klass == Object 
     78          klass = klass.superclass 
     79        end 
     80      end 
    5181    end 
    5282 
    5383 
  • activerecord/lib/active_record/aggregations.rb

    old new  
    131131        class_name  = options[:class_name] || name_to_class_name(name) 
    132132        mapping     = options[:mapping] || [ name, name ] 
    133133 
     134        create_reflection(:composed_of, part_id, options, self) 
     135 
    134136        reader_method(name, class_name, mapping) 
    135137        writer_method(name, class_name, mapping) 
    136          
    137         create_reflection(:composed_of, part_id, options, self) 
    138138      end 
    139139 
    140140      private