diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb')
-rw-r--r-- | activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb | 63 |
1 files changed, 63 insertions, 0 deletions
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 new file mode 100644 index 0000000000..4b48757da7 --- /dev/null +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -0,0 +1,63 @@ +module ActiveRecord::Associations::Builder + class HasAndBelongsToMany < CollectionAssociation #:nodoc: + self.macro = :has_and_belongs_to_many + + self.valid_options += [:join_table, :association_foreign_key, :delete_sql, :insert_sql] + + def build + reflection = super + check_validity(reflection) + define_after_destroy_method + reflection + end + + private + + def define_after_destroy_method + name = self.name + model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1) + def #{after_destroy_method_name} + association(#{name.to_sym.inspect}).delete_all + end + eoruby + model.after_destroy after_destroy_method_name + end + + def after_destroy_method_name + "has_and_belongs_to_many_after_destroy_for_#{name}" + end + + # TODO: These checks should probably be moved into the Reflection, and we should not be + # redefining the options[:join_table] value - instead we should define a + # reflection.join_table method. + def check_validity(reflection) + if reflection.association_foreign_key == reflection.foreign_key + raise ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection) + end + + reflection.options[:join_table] ||= join_table_name( + model.send(:undecorated_table_name, model.to_s), + model.send(:undecorated_table_name, reflection.class_name) + ) + + if model.connection.supports_primary_key? && (model.connection.primary_key(reflection.options[:join_table]) rescue false) + raise ActiveRecord::HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection) + end + end + + # Generates a join table name from two provided table names. + # The names in the join table names end up in lexicographic order. + # + # join_table_name("members", "clubs") # => "clubs_members" + # join_table_name("members", "special_clubs") # => "members_special_clubs" + def join_table_name(first_table_name, second_table_name) + if first_table_name < second_table_name + join_table = "#{first_table_name}_#{second_table_name}" + else + join_table = "#{second_table_name}_#{first_table_name}" + end + + model.table_name_prefix + join_table + model.table_name_suffix + end + end +end |