J'utilise les migrations Rails pour gérer un schéma de base de données, et je crée une table simple dans laquelle je voudrais utiliser une valeur non entière comme clé primaire (en particulier, une chaîne). Pour résumer mon problème, disons qu'il existe un tableau 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 un autre moyen pour que les rails génèrent la contrainte PRIMARY_KEY (j'utilise PostgreSQL) pour moi, sans avoir à écrire le SQL dans un execute
appel?
REMARQUE : 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 disant simplement d'ajouter une clé primaire entière. Je peux quand même en ajouter un, mais cette question est toujours d'actualité.
la source
rake db:migrate
La définition de schéma générée automatiquement ne contient pas cette contrainte!Réponses:
Malheureusement, j'ai déterminé qu'il n'était pas possible de le faire sans utiliser
execute
.Pourquoi ça ne marche pas
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 les
create_table
options, cela crée une clé primaire avec ce nom spécifié (ou, si aucune n'est spécifiée,id
). Elle le fait en appelant la même méthode que vous pouvez utiliser à l' intérieur d' un bloc de définition de la 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 de type spécifié
:primary_key
. Ceci 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"
La solution de contournement
Puisque nous sommes bloqués avec ceux-ci comme types de clés primaires, nous devons utiliser
execute
pour créer une clé primaire qui n'est pas un entier (PostgreSQLserial
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 doit définir la clé primaire en utilisant
set_primary_key
:class Employee < ActiveRecord::Base set_primary_key :emp_id ... end
la source
self.primary_key=
place deset_primary_key
, car ce dernier est obsolète.schema.rb
emp_id
sera à nouveau uneinteger
colonne de type. Il semble queschema.rb
cela ne peutexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
en aucun cas stocker .schema.rb
àstructure.sql
via leconfig.active_record.schema_format
paramètre, qui peut être soit:sql
ou:ruby
. guides.rubyonrails.org/v3.2.13/…Cela marche:
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.
la source
drop_table :employees
schema.rb
qui n'est pas synchronisé ... elle conservera la:primary_key => :emp_id
déclaration, mais lorsqu'ellerake db:schema:load
est appelée, comme c'est le cas au début des tests, elle produira une colonne entière. Cependant, il se peut que si vous passez à l'utilisationstructure.sql
(option de configuration), les tests conservent les paramètres et utilisent ce fichier pour charger le schéma.J'ai une façon de gérer cela. Le SQL exécuté est du SQL ANSI, il fonctionnera donc probablement sur la plupart des bases de données relationnelles compatibles 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
la source
Je l'ai essayé dans Rails 4.2. Pour ajouter votre clé primaire personnalisée, vous pouvez écrire votre migration sous la forme:
# 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:J'ai eu les ides ci-dessus comme je l'ai montré. Voici les métadonnées du tableau après l'exécution de 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 depuis la console Rails:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>
la source
schema.rb
fichier qui est généré ne reflète pas lestring
type de la clé primaire, donc lors de la génération de la base de données avec aload
, la clé primaire est créée sous forme d'entier.Dans Rails 5, vous pouvez faire
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name end
Voir la documentation de create_table .
la source
before_create :set_id
méthode dans le modèle pour attribuer la valeur de la clé primaireIl semble qu'il est possible de 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, alors c'est à vous de remplir le champ lors de la création des objets. Vous devriez pouvoir le faire en utilisant le callback before create.
Donc, quelque chose du genre
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
la source
primary_key: true
au lieu de simplementprimary
donner en réponseJe suis sur Rails 2.3.5 et ma méthode suivante fonctionne avec SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end
Il n'y a pas besoin de: id => false.
la source
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Après presque chaque solution qui dit "cela a fonctionné pour moi sur la base de données X", je vois un commentaire de l'affiche originale indiquant que "n'a pas fonctionné pour moi sur Postgres". Le vrai problème ici peut en fait être le support Postgres dans Rails, qui n'est pas parfait, 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 pratiquement pas obtenir de sortie utile
rake db:schema:dump
.Je ne suis pas un ninja de Postgres moi-même, j'ai obtenu cette information de l'excellente vidéo PeepCode de Xavier Shay sur Postgres. Cette vidéo donne en fait sur une bibliothèque d'Aaron Patterson, je pense que Texticle mais je me souviens peut-être mal. Mais à part ça, c'est plutôt génial.
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 bac à sable, ou simplement créer quelque chose commesandbox: 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 aux correctifs à Rails ou empaqueter vos correctifs dans un joyau, car la base d'utilisateurs Postgres au sein de la communauté Rails est assez importante, principalement grâce à Heroku .
la source
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
la source
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
Donc:
Il semble que MySQL convertit l'index unique d'une colonne non nulle en clé primaire!
la source
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
la source
Et cette solution,
À l'intérieur du 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 alors pour cela, nous pouvons ajouter ": uniqueness => true" à 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.
la source
Je sais que c'est un vieux fil sur lequel je suis tombé par hasard ... mais je suis un peu choqué que personne n'ait mentionné DataMapper.
Je trouve que si vous devez vous éloigner de la convention ActiveRecord, j'ai trouvé que c'était une excellente alternative. C'est aussi 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!
la source
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
la source