Wie kann ich mithilfe von Rails festlegen, dass mein Primärschlüssel keine ganzzahlige Spalte ist?

85

Ich verwende Rails-Migrationen zum Verwalten eines Datenbankschemas und erstelle eine einfache Tabelle, in der ich einen nicht ganzzahligen Wert als Primärschlüssel (insbesondere eine Zeichenfolge) verwenden möchte. Nehmen wir an, es gibt eine Tabelle, employeesin der Mitarbeiter durch eine alphanumerische Zeichenfolge identifiziert werden, z "134SNW".

Ich habe versucht, die Tabelle in einer Migration wie folgt zu erstellen:

create_table :employees, {:primary_key => :emp_id} do |t|
    t.string :emp_id
    t.string :first_name
    t.string :last_name
end

Was mir das gibt, ist, wie es scheint, als hätte es die Zeile völlig ignoriert t.string :emp_idund sie zu einer ganzzahligen Spalte gemacht. Gibt es eine andere Möglichkeit, Rails die PRIMARY_KEY-Einschränkung (ich verwende PostgreSQL) für mich generieren zu lassen, ohne die SQL in einem executeAufruf schreiben zu müssen ?

HINWEIS : Ich weiß, dass es nicht am besten ist, Zeichenfolgenspalten als Primärschlüssel zu verwenden. Bitte keine Antworten, die nur das Hinzufügen eines ganzzahligen Primärschlüssels bedeuten. Ich kann trotzdem eine hinzufügen, aber diese Frage ist immer noch gültig.

Rudd Zwolinski
quelle
Ich habe das gleiche Problem und würde gerne eine Antwort darauf sehen. Keiner der Vorschläge hat bisher funktioniert.
Sean McCleary
1
Keiner von ihnen funktioniert, wenn Sie Postgres verwenden. Dan Chaks "Enterprise Rails" enthält einige Tipps zur Verwendung von natürlichen / zusammengesetzten Schlüsseln.
Azeem.Butt
Beachten Sie Folgendes, wenn Sie Folgendes verwenden: <! - language: lang-rb -> "ALTER TABLE-Mitarbeiter ADD PRIMARY KEY (emp_id)" ausführen; Obwohl die Tabelleneinschränkung nach dem Ausführen korrekt festgelegt wurde rake db:migrateDie automatisch generierte Schemadefinition enthält diese Einschränkung nicht!
Pymkin

Antworten:

107

Leider habe ich festgestellt, dass es ohne Verwendung nicht möglich ist execute.

Warum es nicht funktioniert

Durch Untersuchen der ActiveRecord-Quelle können wir den Code finden für create_table:

In schema_statements.rb:

def create_table(table_name, options={})
  ...
  table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
  ...
end

Wenn Sie also versuchen, einen Primärschlüssel in den create_tableOptionen anzugeben , wird ein Primärschlüssel mit diesem angegebenen Namen erstellt (oder, falls keiner angegeben ist id). Dazu wird dieselbe Methode aufgerufen, die Sie in einem Tabellendefinitionsblock verwenden können : primary_key.

In schema_statements.rb:

def primary_key(name)
  column(name, :primary_key)
end

Dadurch wird lediglich eine Spalte mit dem angegebenen Typnamen erstellt :primary_key. Dies ist in den Standard-Datenbankadaptern wie folgt eingestellt:

PostgreSQL: "serial primary key"
MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"

Die Problemumgehung

Da wir uns an diese als Primärschlüsseltypen halten, müssen wir executeeinen Primärschlüssel erstellen, der keine Ganzzahl ist (PostgreSQL serialist eine Ganzzahl, die eine Sequenz verwendet):

create_table :employees, {:id => false} do |t|
  t.string :emp_id
  t.string :first_name
  t.string :last_name
end
execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"

Und wie Sean McCleary erwähnt hat , sollte Ihr ActiveRecord-Modell den Primärschlüssel mithilfe von set_primary_key:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end
Rudd Zwolinski
quelle
15
Durch die Problemumgehung wird der Rake db: test: clone oder der Rake db: schema: load erstellt, um emp_id als Ganzzahlspalte zu erstellen.
Donny Kurnia
18
Eigentlich sollten Sie jetzt self.primary_key=statt verwenden set_primary_key, da letzteres veraltet ist.
Gary S. Weaver
2
Hier ist die einzige Lösung, die für mich funktioniert hat. Ich hoffe, es hilft anderen: stackoverflow.com/a/15297616/679628
findchris
8
Das Problem bei diesem Ansatz ist, dass beim Einrichten Ihrer Datenbank schema.rb emp_idwieder eine integerTypspalte angezeigt wird. Es scheint, dass schema.rbdas execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"in keiner Weise speichern kann.
Tim und Struppi 81
4
@DonnyKurnia @ Tintin81 Sie können den Schema-Dump von schema.rbauf structure.sqlüber eine config.active_record.schema_formatEinstellung umschalten , die entweder :sqloder sein kann :ruby. guides.rubyonrails.org/v3.2.13/…
Svilen Ivanov
21

Das funktioniert:

create_table :employees, :primary_key => :emp_id do |t|
  t.string :first_name
  t.string :last_name
end
change_column :employees, :emp_id, :string

Es mag nicht schön sein, aber das Endergebnis ist genau das, was Sie wollen.

Austin
quelle
1
Schließlich! Dies ist die einzige Lösung, die für mich funktioniert hat.
Findchris
Müssen wir auch Code für die Migration nach unten angeben? oder ist Rails klug genug, um zu wissen, was bei einer Migration nach unten zu tun ist?
amey1908
2
Für die Abwanderung:drop_table :employees
Austin
6
Leider hinterlässt diese Methode auch eine schema.rbnicht synchronisierte Methode ... sie behält die :primary_key => :emp_idDeklaration bei, aber wenn sie rake db:schema:loadaufgerufen wird, wie zu Beginn der Tests, wird eine ganzzahlige Spalte erzeugt. Es kann jedoch vorkommen, dass beim Umschalten auf die Verwendung von structure.sql(Konfigurationsoption) Tests die Einstellungen beibehalten und diese Datei zum Laden des Schemas verwendet werden.
Tom Harrison
18

Ich habe eine Möglichkeit, damit umzugehen. Das ausgeführte SQL ist ANSI SQL, daher funktioniert es wahrscheinlich auf den meisten ANSI SQL-kompatiblen relationalen Datenbanken. Ich habe getestet, dass dies für MySQL funktioniert.

Migration:

create_table :users, :id => false do |t|
    t.string :oid, :limit => 10, :null => false
    ...
end
execute "ALTER TABLE users ADD PRIMARY KEY (oid);"

Machen Sie in Ihrem Modell Folgendes:

class User < ActiveRecord::Base
    set_primary_key :oid
    ...
end

Sean McCleary
quelle
9

Ich habe es in Rails 4.2 versucht. Um Ihren benutzerdefinierten Primärschlüssel hinzuzufügen, können Sie Ihre Migration wie folgt schreiben:

# tracks_ migration
class CreateTracks < ActiveRecord::Migration
  def change
    create_table :tracks, :id => false do |t|
      t.primary_key :apple_id, :string, limit: 8
      t.string :artist
      t.string :label
      t.string :isrc
      t.string :vendor_id
      t.string :vendor_offer_code

      t.timestamps null: false
    end
    add_index :tracks, :label
  end
end

Während Sie sich die Dokumentation von column(name, type, options = {})ansehen und die Zeile lesen:

Der typeParameter ist normalerweise einer der nativen Migrationstypen. Dies ist einer der folgenden :: primary_key ,: string ,: text ,: integer ,: float ,: decimal ,: datetime ,: time ,: date ,: binary ,: boolean .

Ich habe die oben genannten IDs, wie ich gezeigt habe. Hier sind die Tabellen-Metadaten nach dem Ausführen dieser Migration:

[arup@music_track (master)]$ rails db
psql (9.2.7)
Type "help" for help.

music_track_development=# \d tracks
                    Table "public.tracks"
      Column       |            Type             | Modifiers
-------------------+-----------------------------+-----------
 apple_id          | character varying(8)        | not null
 artist            | character varying           |
 label             | character varying           |
 isrc              | character varying           |
 vendor_id         | character varying           |
 vendor_offer_code | character varying           |
 created_at        | timestamp without time zone | not null
 updated_at        | timestamp without time zone | not null
 title             | character varying           |
Indexes:
    "tracks_pkey" PRIMARY KEY, btree (apple_id)
    "index_tracks_on_label" btree (label)

music_track_development=#

Und von der Rails-Konsole:

Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>
Arup Rakshit
quelle
3
Das Problem besteht jedoch darin, dass die schema.rbgenerierte Datei nicht den stringTyp für den Primärschlüssel widerspiegelt. Wenn Sie also die Datenbank mit a generieren load, wird der Primärschlüssel als Ganzzahl erstellt.
Peter Alfvin
9

In Rails 5 können Sie tun

create_table :employees, id: :string do |t|
  t.string :first_name
  t.string :last_name
end

Siehe create_table-Dokumentation .

Pavel Chuchuva
quelle
Es ist erwähnenswert, dass Sie before_create :set_iddem Modell eine Methode hinzufügen müssen, um den Wert des Primärschlüssels zuzuweisen
FloatingRock
8

Es sieht so aus, als ob dies mit diesem Ansatz möglich ist:

create_table :widgets, :id => false do |t|
  t.string :widget_id, :limit => 20, :primary => true

  # other column definitions
end

class Widget < ActiveRecord::Base
  set_primary_key "widget_id"
end

Dadurch wird die Spalte widget_id zum Primärschlüssel für die Widget-Klasse. Anschließend müssen Sie das Feld beim Erstellen von Objekten füllen. Sie sollten dies mit dem vor dem Erstellen erstellten Rückruf tun können.

Also etwas in der Art von

class Widget < ActiveRecord::Base
  set_primary_key "widget_id"

  before_create :init_widget_id

  private
  def init_widget_id
    self.widget_id = generate_widget_id
    # generate_widget_id represents whatever logic you are using to generate a unique id
  end
end
paulthenerd
quelle
Dies funktioniert bei mir nicht, zumindest nicht in PostgreSQL. Es ist überhaupt kein Primärschlüssel angegeben.
Rudd Zwolinski
Hmm interessant, ich habe es mit MySQL on Rails 2.3.2 getestet - könnte es vielleicht auch mit der Version von Rails zusammenhängen?
Paulthenerd
Ich bin mir nicht sicher - ich glaube nicht, dass es die Version von Rails ist. Ich benutze Rails 2.3.3.
Rudd Zwolinski
Leider wird mit diesem Ansatz kein tatsächlicher Primärschlüssel für die Tabelle festgelegt.
Sean McCleary
2
Dieser Ansatz funktioniert zumindest mit Rails 4.2 und Postgresql. Ich habe getestet, aber Sie müssen verwenden, primary_key: trueanstatt nur primaryin der Antwort gegeben
Anwar
8

Ich bin auf Rails 2.3.5 und meine folgende Methode funktioniert mit SQLite3

create_table :widgets, { :primary_key => :widget_id } do |t|
  t.string :widget_id

  # other column definitions
end

Es besteht keine Notwendigkeit für: id => false.

Trung LE
quelle
Entschuldigung, aber widget_id wurde in Integer geändert, als ich Ihre Technik anwendete ... haben Sie eine Lösung?
Kikicarbonell
1
Dies funktioniert nicht mehr, zumindest nicht in ActiveRecord 4.1.4. Es gibt den folgenden Fehler:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Tamer Shlash
4

Nach fast jeder Lösung, die besagt, dass "dies bei mir in der X-Datenbank funktioniert hat", sehe ich einen Kommentar des Originalplakats zum Effekt "hat bei Postgres nicht bei mir funktioniert". Das eigentliche Problem hier könnte tatsächlich die Postgres-Unterstützung in Rails sein, die nicht fehlerfrei ist und wahrscheinlich 2009 schlimmer war, als diese Frage ursprünglich gestellt wurde. Wenn ich mich zum Beispiel richtig erinnere, wenn Sie auf Postgres sind, können Sie im Grunde keine nützliche Ausgabe von erhalten rake db:schema:dump.

Ich bin selbst kein Postgres-Ninja, ich habe diese Informationen aus Xavier Shays ausgezeichnetem PeepCode-Video auf Postgres erhalten. Dieses Video übersieht tatsächlich eine Bibliothek von Aaron Patterson, ich denke Texticle, aber ich könnte mich falsch erinnern. Aber ansonsten ist es ziemlich gut.

Wenn Sie auf Postgres auf dieses Problem stoßen, prüfen Sie, ob die Lösungen in anderen Datenbanken funktionieren. Vielleicht verwenden Sie rails new, um eine neue App als Sandbox zu generieren, oder erstellen Sie einfach so etwas

sandbox:
  adapter: sqlite3
  database: db/sandbox.sqlite3
  pool: 5
  timeout: 5000

in config/database.yml.

Wenn Sie überprüfen können, ob es sich um ein Postgres-Supportproblem handelt, und einen Fix finden, fügen Sie bitte Patches zu Rails hinzu oder verpacken Sie Ihre Fixes in einem Juwel, da die Postgres-Benutzerbasis innerhalb der Rails-Community vor allem dank Heroku ziemlich groß ist .

Giles Bowkett
quelle
2
Downvoting für FUD und Ungenauigkeit. Ich habe Postgres seit 2007 für die meisten meiner Rails-Projekte verwendet. Die Postgres-Unterstützung in Rails ist ausgezeichnet, und es gibt kein Problem mit dem Schema-Dumper.
Marnen Laibow-Koser
2
Einerseits ist es wahr, dass Postgres hier nicht das Problem war. Auf der anderen Seite ist hier ein Nur- Seiten -Schema-Dumper-Fehler von 2009 rails.lighthouseapp.com/projects/8994/tickets/2418 und hier ist ein weiterer Rails.lighthouseapp.com/projects/8994/tickets/2514
Giles Bowkett
Es gab ein entschiedenes Problem mit Rails und Postgres im Zusammenhang mit dem Postgres 8.3-Update, bei dem sie (IMO) beschlossen, auf den SQL-Standard für String-Literale und Anführungszeichen umzusteigen. Der Rails-Adapter überprüfte die Postgres-Versionen nicht und musste für eine Weile monkeypatched werden.
Judson
@Judson Interessant. Darauf bin ich nie gestoßen.
Marnen Laibow-Koser
4

Ich habe eine Lösung dafür gefunden, die mit Rails 3 funktioniert:

Die Migrationsdatei:

create_table :employees, {:primary_key => :emp_id} do |t|
  t.string :emp_id
  t.string :first_name
  t.string :last_name
end

Und im Modell employee.rb:

self.primary_key = :emp_id
Shicholas
quelle
3

Der Trick, der bei Rails 3 und MySQL für mich funktioniert hat, war folgender:

create_table :events, {:id => false} do |t|
  t.string :id, :null => false
end

add_index :events, :id, :unique => true

So:

  1. Verwenden Sie: id => false, um keinen ganzzahligen Primärschlüssel zu generieren
  2. Verwenden Sie den gewünschten Datentyp und fügen Sie Folgendes hinzu: null => false
  3. Fügen Sie dieser Spalte einen eindeutigen Index hinzu

Scheint, dass MySQL den eindeutigen Index für eine Nicht-Null-Spalte in einen Primärschlüssel konvertiert!

Clark
quelle
In Rails 3.22 mit MySQL 5.5.15 wird nur ein eindeutiger Schlüssel erstellt, jedoch kein Primärschlüssel.
Lulalala
2

Sie müssen die Option verwenden: id => false

create_table :employees, :id => false, :primary_key => :emp_id do |t|
    t.string :emp_id
    t.string :first_name
    t.string :last_name
end
BvuRVKyUVlViVIc7
quelle
Dies funktioniert bei mir nicht, zumindest nicht in PostgreSQL. Es ist überhaupt kein Primärschlüssel angegeben.
Rudd Zwolinski
1
Bei diesem Ansatz wird die ID-Spalte weggelassen, der Primärschlüssel wird jedoch nicht in der Tabelle festgelegt.
Sean McCleary
1

Wie wäre es mit dieser Lösung,

Warum können wir im Mitarbeitermodell keinen Code hinzufügen, der die Eindeutigkeit in der Spalte überprüft, zum Beispiel: Angenommen, Mitarbeiter ist Modell, indem Sie EmpId haben, eine Zeichenfolge, für die wir EmpId ": Eindeutigkeit => true" hinzufügen können

    class Employee < ActiveRecord::Base
      validates :EmpId , :uniqueness => true
    end

Ich bin nicht sicher, ob dies eine Lösung ist, aber das hat bei mir funktioniert.

Vijay Sali
quelle
1

Ich weiß, dass dies ein alter Thread ist, über den ich gestolpert bin ... aber ich bin schockiert, dass niemand DataMapper erwähnt hat.

Ich finde, wenn Sie von der ActiveRecord-Konvention abweichen müssen, habe ich festgestellt, dass dies eine großartige Alternative ist. Es ist auch ein besserer Ansatz für Legacy und Sie können die Datenbank "wie sie ist" unterstützen.

Ruby Object Mapper (DataMapper 2) ist vielversprechend und baut auch auf AREL-Prinzipien auf!

IngenieurDave
quelle
1

Das Hinzufügen eines Index funktioniert für mich, ich verwende übrigens MySql.

create_table :cards, {:id => false} do |t|
    t.string :id, :limit => 36
    t.string :name
    t.string :details
    t.datetime :created_date
    t.datetime :modified_date
end
add_index :cards, :id, :unique => true
Guster
quelle