Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let t.foreign_key use the same to_table twice #23614

Merged
merged 1 commit into from
Feb 16, 2016

Conversation

george-carlin
Copy link
Contributor

I just discovered this issue with migrations in Rails 5 beta 2. Say I want to create a table with multiple foreign keys pointing to the same table:

def change
  create_table :flights do |t|
    t.integer :from_id, index: true, null: false
    t.integer :to_id,   index: true, null: false

    t.foreign_key :airports, column: :from_id
    t.foreign_key :airports, column: :to_id
  end
end

The problem is that this only creates the last foreign key (in this case the one on to_id) and ignores any previous foreign keys. From the source it's easy to see why:

def foreign_key(table_name, options = {}) # :nodoc:
  foreign_keys[table_name] = options
end

The hash keys are being re-used, so later definitions will overwrite previous ones.

This PR contains a simple fix, by turning foreign_keys[table_name] into an Array and allowing multiple definitions to be made for the same to_table.

@rails-bot
Copy link

r? @schneems

(@rails-bot has picked a reviewer for you, use r? to override)

@george-carlin
Copy link
Contributor Author

CI has failed, but the error doesn't seem to have anything to do with the changes I made. Here's the failing job and the error is ClientTest#test_many_clients: ThreadError: queue empty in actioncable/test/client_test.rb. Can someone explain to me what's going on?

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Feb 13, 2016
@@ -330,7 +330,8 @@ def index(column_name, options = {})
end

def foreign_key(table_name, options = {}) # :nodoc:
foreign_keys[table_name] = options
foreign_keys[table_name] ||= []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @foreign_keys should be an array rather than a hash because foreign keys can be added to the same table.

--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -212,7 +212,7 @@ module ActiveRecord
       def initialize(name, temporary, options, as = nil)
         @columns_hash = {}
         @indexes = {}
-        @foreign_keys = {}
+        @foreign_keys = []
         @primary_keys = nil
         @temporary = temporary
         @options = options
@@ -330,7 +330,7 @@ module ActiveRecord
       end

       def foreign_key(table_name, options = {}) # :nodoc:
-        foreign_keys[table_name] = options
+        foreign_keys << [table_name, options]
       end

@senny
Copy link
Member

senny commented Feb 15, 2016

Suggestion by @kamipo to turn @foreign_keys into an array seems reasonable. @georgemillo would you mind to update the PR accordingly?

@george-carlin
Copy link
Contributor Author

Updated and rebased.

@@ -50,7 +50,7 @@ def visit_TableDefinition(o)
end

if supports_foreign_keys?
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
statements.concat(o.foreign_keys.map { |fk| foreign_key_in_create(o.name, fk[0], fk[1]) })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to_table, options -> fk[0], fk[1] is reasonable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right; the original syntax works. I didn't realise that you could pass two parameters to the block like that when iterating over a nested array.

Reverted and rebased.

Previously if you used `t.foreign_key` twice within the same
`create_table` block using the same `to_table`, all statements except
the final one would fail silently. For example, the following code:

    def change
      create_table :flights do |t|
        t.integer :from_id, index: true, null: false
        t.integer :to_id,   index: true, null: false

        t.foreign_key :airports, column: :from_id
        t.foreign_key :airports, column: :to_id
      end
    end

Would only create one foreign key, on the column `from_id`.

This commit allows multiple foreign keys to the same table to be created
within one `create_table` block.
@senny senny merged commit aedde2a into rails:master Feb 16, 2016
senny added a commit that referenced this pull request Feb 16, 2016
Let t.foreign_key use the same `to_table` twice

Conflicts:
	activerecord/CHANGELOG.md
@senny
Copy link
Member

senny commented Feb 16, 2016

@georgemillo thanks. I merged a slightly modified version with a single assertion in the test-case. This combines all assertions and will give much more context when something goes wrong. 84c1824#diff-110b6f2d17622b19682ec73cce8b015fR160

@george-carlin george-carlin deleted the foreign_key branch February 16, 2016 18:22
kamipo pushed a commit to kamipo/rails that referenced this pull request Feb 22, 2016
Let t.foreign_key use the same `to_table` twice
yahonda added a commit to yahonda/oracle-enhanced that referenced this pull request Feb 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants