it-swarm.com.de

Wie kann ich mit Rails festlegen, dass mein Primärschlüssel keine Ganzzahlspalte ist?

Ich verwende Rails Migrationen zum Verwalten eines Datenbankschemas und erstelle eine einfache Tabelle, in der ich einen nicht ganzzahligen Wert als Primärschlüssel verwenden möchte (insbesondere a Nehmen wir an, es gibt eine Tabelle employees, in der Mitarbeiter durch eine alphanumerische Zeichenfolge identifiziert werden, zB "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, die Zeile t.string :emp_id Völlig ignoriert und es zu einer ganzzahligen Spalte gemacht zu haben. Gibt es eine andere Möglichkeit, Rails) die PRIMARY_KEY-Einschränkung (ich verwende PostgreSQL) für mich zu generieren, ohne die SQL in einem execute -Aufruf schreiben zu müssen?

NOTE : Ich weiß, dass es nicht am besten ist, Zeichenfolgenspalten als Primärschlüssel zu verwenden, also bitte keine Antworten, nur um einen ganzzahligen Primärschlüssel hinzuzufügen. Ich kann trotzdem eine hinzufügen, aber diese Frage ist immer noch gültig.

83
Rudd Zwolinski

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

Warum funktioniert es nicht?

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

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 Optionen create_table 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 nur eine Spalte mit dem angegebenen Namen vom Typ :primary_key Erstellt. In den Standarddatenbankadaptern ist dies auf Folgendes festgelegt:

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 execute verwenden, um einen Primärschlüssel zu erstellen, der keine ganze Zahl ist (PostgreSQLs serial ist eine ganze Zahl, 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 , sollte Ihr ActiveRecord-Modell den Primärschlüssel mit set_primary_key Festlegen:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end
107
Rudd Zwolinski

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.

21
Austin

Ich habe eine Möglichkeit, damit umzugehen. Das ausgeführte SQL ist ANSI SQL, sodass es wahrscheinlich auf den meisten ANSI SQL-kompatiblen relationalen Datenbanken funktioniert. 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);"

Führen Sie in Ihrem Modell Folgendes aus:

class User < ActiveRecord::Base
    set_primary_key :oid
    ...
end
18
Sean McCleary

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

Schauen Sie sich die Dokumentation von column(name, type, options = {}) an und lesen Sie die Zeile:

Der Parameter type ist normalerweise einer der systemeigenen Migrationstypen. Dies ist einer der folgenden:: Primärschlüssel,: Zeichenfolge,: Text,: Ganzzahl,: Gleitkomma,: Dezimal,: DatumZeit,: Zeit,: Datum ,: binary,: boolean.

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

[[email protected]_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"
>>
9
Arup Rakshit

Es sieht so aus, als ob es möglich wäre, diesen Ansatz zu verwenden:

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

Damit wird die Spalte widget_id zum Primärschlüssel für die Widget-Klasse, und Sie müssen das Feld beim Erstellen von Objekten ausfüllen. Sie sollten in der Lage sein, dies mit dem Before Create Callback zu tun.

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
8
paulthenerd

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.

8
Trung LE

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 .

8
Pavel Chuchuva

Nach fast jeder Lösung, die besagt, dass "dies bei mir in der X-Datenbank funktioniert hat", wird im Original-Poster ein Kommentar angezeigt, der besagt, dass "bei mir bei Postgres nicht funktioniert hat". Das eigentliche Problem könnte in der Tat die Postgres-Unterstützung in Rails sein, die nicht fehlerfrei ist und wahrscheinlich 2009 schlechter war, als diese Frage ursprünglich gestellt wurde. Zum Beispiel, wenn ich mich richtig erinnere, wenn Sie auf Postgres sind, können Sie im Grunde keine nützliche Ausgabe von rake db:schema:dump.

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

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

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

im config/database.yml.

Und wenn Sie überprüfen können, ob es sich um ein Postgres-Support-Problem handelt, und einen Fix gefunden haben, tragen Sie bitte Patches zu Rails) bei, oder packen Sie Ihre Fixes in einen Edelstein, da die Postgres-Benutzer innerhalb des = Rails Community ist ziemlich groß, hauptsächlich dank Heroku.

4
Giles Bowkett

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

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
4
shicholas

Der Trick, der bei Rails 3 und MySQL funktioniert hat, war:

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 einen eindeutigen Index für diese Spalte hinzu

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

3
Clark

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
2

Das Hinzufügen von Index funktioniert für mich, ich benutze ü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
1
Guster

Wie wäre es mit dieser Lösung,

Warum können wir im Mitarbeitermodell keinen Code hinzufügen, der auf Eindeutigkeit in der Spalte prüft, zum Beispiel: Angenommen, Mitarbeiter ist Modell, Sie haben eine EmpId, die ein String ist, für den wir ": Eindeutigkeit => wahr") hinzufügen können? zu EmpId

    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.

1
Vijay Sali

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) verspricht viel und baut auch auf AREL-Prinzipien auf!

1
engineerDave