J'utilise Rails migrations pour gérer un schéma de base de données, et je crée une table simple où j'aimerais utiliser une valeur non entière comme clé primaire (en particulier, un Pour résumer loin de mon problème, disons qu'il y a une table employees
où les employés sont identifiés par une chaîne alphanumérique, par exemple "134SNW"
.
J'ai essayé de créer la table dans une migration comme celle-ci:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
Ce que cela me donne, c'est ce qui semble avoir complètement ignoré la ligne t.string :emp_id
et est allé de l'avant et en a fait une colonne entière. Existe-t-il une autre manière d'avoir Rails générer la contrainte PRIMARY_KEY (j'utilise PostgreSQL) pour moi, sans avoir à écrire le SQL dans un appel execute
?
NOTE : Je sais qu'il n'est pas préférable d'utiliser des colonnes de chaînes comme clés primaires, donc s'il vous plaît pas de réponses en disant simplement d'ajouter une clé primaire entière. Je peux quand même en ajouter un, mais cette question est toujours d'actualité.
Malheureusement, j'ai déterminé qu'il n'est pas possible de le faire sans utiliser execute
.
En examinant la source ActiveRecord, nous pouvons trouver le code pour create_table
:
Dans 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
Nous pouvons donc voir que lorsque vous essayez de spécifier une clé primaire dans le create_table
options, il crée une clé primaire avec ce nom spécifié (ou, si aucun n'est spécifié, id
). Pour ce faire, il appelle la même méthode que vous pouvez utiliser dans un bloc de définition de table: primary_key
.
Dans schema_statements.rb
:
def primary_key(name)
column(name, :primary_key)
end
Cela crée simplement une colonne avec le nom spécifié de type :primary_key
. Il est défini comme suit dans les adaptateurs de base de données standard:
PostgreSQL: "serial primary key"
MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"
Puisque nous sommes coincés avec ceux-ci comme types de clé primaire, nous devons utiliser execute
pour créer une clé primaire qui n'est pas un entier (le serial
de PostgreSQL est un entier utilisant une séquence):
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);"
Et comme Sean McCleary l'a mentionné , votre modèle ActiveRecord devrait définir la clé primaire en utilisant set_primary_key
:
class Employee < ActiveRecord::Base
set_primary_key :emp_id
...
end
Cela fonctionne:
create_table :employees, :primary_key => :emp_id do |t|
t.string :first_name
t.string :last_name
end
change_column :employees, :emp_id, :string
Ce n'est peut-être pas joli, mais le résultat final est exactement ce que vous voulez.
J'ai une façon de gérer cela. Le SQL exécuté est ANSI SQL, il fonctionnera donc probablement sur la plupart des bases de données relationnelles conformes à ANSI SQL. J'ai testé que cela fonctionne pour MySQL.
Migration:
create_table :users, :id => false do |t|
t.string :oid, :limit => 10, :null => false
...
end
execute "ALTER TABLE users ADD PRIMARY KEY (oid);"
Dans votre modèle, procédez comme suit:
class User < ActiveRecord::Base
set_primary_key :oid
...
end
Je l'ai essayé dans Rails 4.2. Pour ajouter votre clé primaire personnalisée, vous pouvez écrire votre migration comme:
# 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
En regardant la documentation de column(name, type, options = {})
et lisez la ligne:
Le paramètre
type
est normalement l'un des types natifs de migration, qui est l'un des suivants:: primary_key,: string,: text,: integer,: float,: decimal,: datetime,: time,: date ,: binaire,: booléen.
J'ai obtenu les ides ci-dessus comme je l'ai montré. Voici les métadonnées de la table après avoir exécuté cette 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=#
Et de Rails console:
Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "Apple_id"
>>
Il semble qu'il soit possible de le faire en utilisant cette approche:
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
Cela fera de la colonne widget_id la clé primaire de la classe Widget, puis c'est à vous de remplir le champ lors de la création des objets. Vous devriez pouvoir le faire en utilisant le rappel avant de créer.
Donc, quelque chose dans le sens de
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
Je suis sur Rails 2.3.5 et ma façon suivante fonctionne avec SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t|
t.string :widget_id
# other column definitions
end
Il n'est pas nécessaire de: id => false.
Dans Rails 5 vous pouvez faire
create_table :employees, id: :string do |t|
t.string :first_name
t.string :last_name
end
Voir documentation create_table .
Après presque toutes les solutions qui disent "cela a fonctionné pour moi sur la base de données X", je vois un commentaire de l'affiche originale à l'effet de "n'a pas fonctionné pour moi sur Postgres." Le vrai problème ici peut en fait être le support de Postgres dans Rails, qui n'est pas sans faille, et était probablement pire en 2009 lorsque cette question a été publiée à l'origine. Par exemple, si je me souviens bien, si vous êtes sur Postgres, vous ne pouvez pas obtenir de sortie utile de rake db:schema:dump
.
Je ne suis pas moi-même un ninja Postgres, j'ai obtenu cette info de l'excellente vidéo PeepCode de Xavier Shay sur Postgres. Cette vidéo surplombe en fait une bibliothèque d'Aaron Patterson, je pense que Texticle mais je me souviens peut-être mal. Mais à part ça, c'est plutôt bien.
Quoi qu'il en soit, si vous rencontrez ce problème sur Postgres, voyez si les solutions fonctionnent dans d'autres bases de données. Peut-être utiliser Rails new
pour générer une nouvelle application en tant que sandbox, ou simplement créer quelque chose comme
sandbox:
adapter: sqlite3
database: db/sandbox.sqlite3
pool: 5
timeout: 5000
dans config/database.yml
.
Et si vous pouvez vérifier qu'il s'agit d'un problème de support Postgres et que vous trouvez un correctif, veuillez contribuer les correctifs à Rails ou regrouper vos correctifs dans un joyau, car la base d'utilisateurs Postgres dans le = Rails est assez grande, principalement grâce à Heroku.
J'ai trouvé une solution à cela qui fonctionne avec Rails 3:
Le fichier de migration:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
Et dans le modèle employee.rb:
self.primary_key = :emp_id
L'astuce qui a fonctionné pour moi sur Rails 3 et MySQL était la suivante:
create_table :events, {:id => false} do |t|
t.string :id, :null => false
end
add_index :events, :id, :unique => true
Alors:
Semble que MySQL convertit l'index unique sur une colonne non nulle en une clé primaire!
vous devez utiliser l'option: 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
L'ajout d'index fonctionne pour moi, j'utilise MySql btw.
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
Que diriez-vous de cette solution,
Dans le modèle Employee, pourquoi ne pouvons-nous pas ajouter du code qui vérifiera l'unicité dans coloumn, par exemple: Supposons que l'employé est un modèle en ce que vous avez EmpId qui est une chaîne, puis pour cela, nous pouvons ajouter ": unicité => vrai" à EmpId
class Employee < ActiveRecord::Base
validates :EmpId , :uniqueness => true
end
Je ne suis pas sûr que ce soit une solution mais cela a fonctionné pour moi.
Je sais que c'est un vieux fil que je suis tombé sur ... mais je suis un peu choqué, personne n'a mentionné DataMapper.
Je trouve que si vous avez besoin de vous éloigner de la convention ActiveRecord, j'ai trouvé que c'est une excellente alternative. C'est également une meilleure approche pour l'héritage et vous pouvez prendre en charge la base de données "telle quelle".
Ruby Object Mapper (DataMapper 2) est très prometteur et s'appuie également sur les principes AREL!