Rails-Migration: add_reference to Table, aber anderer Spaltenname für Fremdschlüssel als Rails-Konvention

73

Ich habe die folgenden zwei Modelle:

class Store < ActiveRecord::Base
    belongs_to :person
end

class Person < ActiveRecord::Base
    has_one :store
end

Hier ist das Problem: Ich versuche, eine Migration zu erstellen, um den Fremdschlüssel in der Personentabelle zu erstellen. Die Spalte, die sich auf den Fremdschlüssel von Store bezieht, heißt jedoch nicht store_id, wie dies bei der Rails-Konvention der Fall wäre, sondern foo_bar_store_id .

Wenn ich der Rails-Konvention folgen würde, würde ich die Migration folgendermaßen durchführen:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_reference :people, :store, index: true
  end
end

Dies funktioniert jedoch nicht, da der Spaltenname nicht store_id, sondern foo_bar_store_id lautet . Wie gebe ich an, dass der Fremdschlüsselname nur anders ist, aber dennoch den Index: true beibehält, um eine schnelle Leistung zu gewährleisten?

Neil
quelle

Antworten:

9

EDIT: Für diejenigen, die das Häkchen sehen und nicht weiterlesen!

Diese Antwort erreicht zwar das Ziel eines unkonventionellen Fremdschlüsselspaltennamens bei der Indizierung, fügt der Datenbank jedoch keine fk-Einschränkung hinzu . In den anderen Antworten finden Sie passendere Lösungen mit add_foreign_keyund / oder 'add_reference'.

Hinweis: Schauen Sie sich IMMER die anderen Antworten an, die akzeptierte ist nicht immer die beste!

Ursprüngliche Antwort:

In Ihrer AddReferencesToPeopleMigration können Sie das Feld und den Index manuell hinzufügen, indem Sie:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id

Und dann teilen Sie Ihrem Modell den Fremdschlüssel folgendermaßen mit:

class Person < ActiveRecord::Base
  has_one :store, foreign_key: 'foo_bar_store_id'
end
Matt
quelle
53
Dies ist kein Fremdschlüssel in der Datenbank.
Baash05
@ baash05 Könntest du deine Frage erklären?
Matt
6
@matt Ich denke, was er vorhat, ist die Frage, ob ein Fremdschlüssel in der Tabelle (Datenbankebene) erstellt werden soll, nicht nur auf Modellebene. In neueren Rails 4.2 kann eine generierte Migration mit Referenzen den Fremdschlüssel in die entsprechende Tabelle einfügen, um die Datenintegrität sicherzustellen. Probleme mit der Formatierung, aber ich denke, der relevante Code wäre so etwas wieadd_foreign_key :people, :stores, column: :foo_bar_store_id
Randy Shelford
1
@RandyShelford Danke, aber ich denke nicht, dass das den Kommentar von baash05 erklärt. Meine Antwort umfasst das Erstellen des Fremdschlüssels in der Datenbank über die Migration und das Behandeln nicht standardmäßiger Fremdschlüssel im Modell. Was fehlt meiner Antwort? Ihr Vorschlag von add_foreign_keywurde hier bereits durch eine andere Antwort abgedeckt.
Matt
5
@Matt, um zu verdeutlichen, was Randy / baash05 gesagt haben, führt dies nicht zu einer Fremdschlüsseleinschränkung in der Datenbank. Das heißt, foo_bar_store_idkann auf eine ungültige Geschäfts-ID gesetzt werden, und das Modell besteht alle Überprüfungen. Korrigieren Sie mich, wenn ich falsch liege, aber wenn Sie nur diese Zeile in das Modell einfügen, werfen die Schienen beim Speichern keinen ActiveRecord :: InvalidForeignKey: PG :: ForeignKeyViolation: ERROR-Fehler.
CHawk
108

In Rails 5.x können Sie einer Tabelle mit einem anderen Namen wie diesem einen Fremdschlüssel hinzufügen:

class AddFooBarStoreToPeople < ActiveRecord::Migration[5.0]
  def change
    add_reference :people, :foo_bar_store, foreign_key: { to_table: :stores }
  end
end
schpet
quelle
1
@inye es scheint, dass dieser Fehler behoben wurde 👍
schpet
12
Dies ist einer dieser scheinbar undokumentierten Hacks, bei denen ich Rails gleichzeitig liebe und hasse.
Peelman
4
Dies funktioniert auch in einer create_tableRichtung wie:t.references :feature, foreign_key: {to_table: :product_features}
Toobulkeh
Liebes-Hass-Beziehungen sind die besten, aber mit den richtigen Mengen
ARK
Wenn ich dies in MySQL mache, erhalte ich ActiveRecord :: MismatchedForeignKey: Spalte incoming_location_idin Tabelle inventory_incomingsstimmt nicht mit Spalte idauf überein addresses, die Typ hat bigint(20). Um dieses Problem zu beheben, ändern Sie den Typ der incoming_location_idSpalte inventory_incomingsin: bigint.
hook38
77

In Rails 4.2 können Sie das Modell oder die Migration auch mit einem benutzerdefinierten Fremdschlüsselnamen einrichten. In Ihrem Beispiel wäre die Migration:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_column :people, :foo_bar_store_id, :integer, index: true
    add_foreign_key :people, :stores, column: :foo_bar_store_id
  end
end

Hier ist ein interessanter Blog-Beitrag zu diesem Thema. Hier ist der halbkryptische Abschnitt in den Rails Guides. Der Blog-Beitrag hat mir definitiv geholfen.

Geben Sie für Assoziationen den Fremdschlüssel oder Klassennamen explizit wie folgt an (ich denke, Ihre ursprünglichen Assoziationen wurden geändert, da das 'Gehört_zu' in der Klasse mit dem Fremdschlüssel steht):

class Store < ActiveRecord::Base
  has_one :person, foreign_key: :foo_bar_store_id
end

class Person < ActiveRecord::Base
  belongs_to :foo_bar_store, class_name: 'Store'
end

Beachten Sie, dass das Element class_name eine Zeichenfolge sein muss. Das Element alien_key kann entweder eine Zeichenfolge oder ein Symbol sein. Auf diese Weise können Sie im Wesentlichen auf die raffinierten ActiveRecord-Verknüpfungen mit Ihren semantisch benannten Zuordnungen zugreifen, z.

person = Person.first
person.foo_bar_store
# returns the instance of store equal to person's foo_bar_store_id

Weitere Informationen zu den Zuordnungsoptionen finden Sie in der Dokumentation zu gehör_zu und has_one .

Sia
quelle
1
Sollte `add_column: people ,: foo_bar_store_id, index: true` einen Typ deklarieren?
Chase Gilliam
1
@alex true - es wäre besser, getrennte Auf- und Ab-Methoden zu haben, als nur eine Änderungsmethode. up würde wahrscheinlich genauso aussehen wie die Änderungsmethode der Antwort, und down würde die Spalte entfernen.
Sia
9

Um die Antwort von schpet zu erweitern, funktioniert dies in einer create_tableRails 5-Migrationsanweisung wie folgt :

create_table :chapter do |t|
  t.references :novel, foreign_key: {to_table: :books}
  t.timestamps
end
toobulkeh
quelle
4
t.references :novel, foreign_key: {to_table: :books}wird in der Rails-API-Dokumentation dargestellt, es werden jedoch keine zusätzlichen Optionen für Fremdschlüssel dargestellt oder erwähnt. Solche Optionen finden Sie in den Dokumenten für die Methode
add_foreign_key
@JigneshGohel Vielen Dank für den Link zur Dokumentation! Ich war noch nie darauf gestoßen.
Allison
6
# Migration
change_table :people do |t|
  t.references :foo_bar_store, references: :store #-> foo_bar_store_id
end

# Model
# app/models/person.rb
class Person < ActiveRecord::Base
  has_one :foo_bar_store, class_name: "Store"
end
Richard Peck
quelle
froh, dass jemand auch die Modellreferenz hinzugefügt hat; hat mir etwas Zeit gespart!
Bigtrizzy
0

Unter dem Deckmantel delegiert add_reference nur an add_column und add_index, sodass Sie sich nur selbst darum kümmern müssen:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
Felsblock
quelle
Woher weiß das Personenmodell, dass foo_bar_store_id tatsächlich ein Fremdschlüssel für store_id ist?
Neil
4
Das gehört nicht zur Migration, sondern zur Assoziationsdefinition im Modell. Wie Matt erwähnt hathas_one :store, foreign_key: 'foo_bar_store_id'
Boulder