Skip to content

Commit f060221

Browse files
bellmyerjeremy
authored andcommitted
raises exception (ActiveRecord::ConfigurationError with message) on habtm association creation if join table contains a primary key
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
1 parent d2d464e commit f060221

File tree

7 files changed

+99
-0
lines changed

7 files changed

+99
-0
lines changed

activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ def count_records
2929
end
3030

3131
def insert_record(record, force = true, validate = true)
32+
if ActiveRecord::Base.connection.supports_primary_key? && ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table])
33+
raise ActiveRecord::ConfigurationError, "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
34+
end
35+
3236
if record.new_record?
3337
if force
3438
record.save!

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ def supports_migrations?
5656
false
5757
end
5858

59+
# Can this adapter determine the primary key for tables not attached
60+
# to an ActiveRecord class, such as join tables? Backend specific, as
61+
# the abstract adapter always returns +false+.
62+
def supports_primary_key?
63+
false
64+
end
65+
5966
# Does this adapter support using DISTINCT within COUNT? This is +true+
6067
# for all adapters except sqlite.
6168
def supports_count_distinct?

activerecord/lib/active_record/connection_adapters/mysql_adapter.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ def supports_migrations? #:nodoc:
208208
true
209209
end
210210

211+
def supports_primary_key? #:nodoc:
212+
true
213+
end
214+
211215
def supports_savepoints? #:nodoc:
212216
true
213217
end
@@ -550,6 +554,12 @@ def pk_and_sequence_for(table) #:nodoc:
550554
keys.length == 1 ? [keys.first, nil] : nil
551555
end
552556

557+
# Returns just a table's primary key
558+
def primary_key(table)
559+
pk_and_sequence = pk_and_sequence_for(table)
560+
pk_and_sequence && pk_and_sequence.first
561+
end
562+
553563
def case_sensitive_equality_operator
554564
"= BINARY"
555565
end

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ def supports_migrations?
250250
true
251251
end
252252

253+
# Does PostgreSQL support finding primary key on non-ActiveRecord tables?
254+
def supports_primary_key? #:nodoc:
255+
true
256+
end
257+
253258
# Does PostgreSQL support standard conforming strings?
254259
def supports_standard_conforming_strings?
255260
# Temporarily set the client message level above error to prevent unintentional
@@ -811,6 +816,12 @@ def pk_and_sequence_for(table) #:nodoc:
811816
nil
812817
end
813818

819+
# Returns just a table's primary key
820+
def primary_key(table)
821+
pk_and_sequence = pk_and_sequence_for(table)
822+
pk_and_sequence && pk_and_sequence.first
823+
end
824+
814825
# Renames a table.
815826
def rename_table(name, new_name)
816827
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"

activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ def supports_migrations? #:nodoc:
101101
true
102102
end
103103

104+
def supports_primary_key? #:nodoc:
105+
true
106+
end
107+
104108
def requires_reloading?
105109
true
106110
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require 'cases/helper'
2+
3+
class MyReader < ActiveRecord::Base
4+
has_and_belongs_to_many :my_books
5+
end
6+
7+
class MyBook < ActiveRecord::Base
8+
has_and_belongs_to_many :my_readers
9+
end
10+
11+
class JoinTableTest < ActiveRecord::TestCase
12+
def setup
13+
ActiveRecord::Base.connection.create_table :my_books, :force => true do |t|
14+
t.string :name
15+
end
16+
assert ActiveRecord::Base.connection.table_exists?(:my_books)
17+
18+
ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t|
19+
t.string :name
20+
end
21+
assert ActiveRecord::Base.connection.table_exists?(:my_readers)
22+
23+
ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t|
24+
t.integer :my_book_id
25+
t.integer :my_reader_id
26+
end
27+
assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers)
28+
end
29+
30+
def teardown
31+
ActiveRecord::Base.connection.drop_table :my_books
32+
ActiveRecord::Base.connection.drop_table :my_readers
33+
ActiveRecord::Base.connection.drop_table :my_books_my_readers
34+
end
35+
36+
uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key
37+
def test_should_raise_exception_when_join_table_has_a_primary_key
38+
if ActiveRecord::Base.connection.supports_primary_key?
39+
assert_raise ActiveRecord::ConfigurationError do
40+
jaime = MyReader.create(:name=>"Jaime")
41+
jaime.my_books << MyBook.create(:name=>'Great Expectations')
42+
end
43+
end
44+
end
45+
end

activerecord/test/cases/pk_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,22 @@ def test_instance_update_should_quote_pkey
9898
def test_instance_destroy_should_quote_pkey
9999
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
100100
end
101+
102+
def test_supports_primary_key
103+
assert_nothing_raised NoMethodError do
104+
ActiveRecord::Base.connection.supports_primary_key?
105+
end
106+
end
107+
108+
def test_primary_key_returns_value_if_it_exists
109+
if ActiveRecord::Base.connection.supports_primary_key?
110+
assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers')
111+
end
112+
end
113+
114+
def test_primary_key_returns_nil_if_it_does_not_exist
115+
if ActiveRecord::Base.connection.supports_primary_key?
116+
assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
117+
end
118+
end
101119
end

0 commit comments

Comments
 (0)