Estoy usando migraciones de Rails para administrar un esquema de base de datos y estoy creando una tabla simple en la que me gustaría usar un valor no entero como clave principal (en particular, una cadena). Para abstraerme de mi problema, digamos que hay una tabla employeesdonde los empleados se identifican mediante una cadena alfanumérica, por ejemplo "134SNW".
Intenté crear la tabla en una migración como esta:
create_table :employees, {:primary_key => :emp_id} do |t|
    t.string :emp_id
    t.string :first_name
    t.string :last_name
end
Lo que esto me da es lo que parece que ignoró por completo la línea t.string :emp_idy siguió adelante y la convirtió en una columna entera. ¿Hay alguna otra forma de que los rieles generen la restricción PRIMARY_KEY (estoy usando PostgreSQL) por mí, sin tener que escribir el SQL en una executellamada?
NOTA : Sé que no es mejor usar columnas de cadena como claves primarias, así que no responda simplemente diciendo que agregue una clave primaria entera. Puedo agregar uno de todos modos, pero esta pregunta sigue siendo válida.
fuente

rake db:migrate, la definición de esquema generada automáticamente no contiene esta restricción.Respuestas:
Desafortunadamente, he determinado que no es posible hacerlo sin usar
execute.Por que no funciona
Al examinar la fuente de ActiveRecord, podemos encontrar el código para
create_table:En
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 ... endEntonces podemos ver que cuando intenta especificar una clave primaria en las
create_tableopciones, crea una clave primaria con ese nombre especificado (o, si no se especifica ninguno,id). Para ello, a llamar al mismo método que se puede utilizar dentro de un bloque de definición de tabla:primary_key.En
schema_statements.rb:def primary_key(name) column(name, :primary_key) endEsto solo crea una columna con el nombre de tipo especificado
:primary_key. Esto se establece en lo siguiente en los adaptadores de base de datos estándar:PostgreSQL: "serial primary key" MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY" SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"La solución
Como estamos atascados con estos como tipos de clave principal, tenemos que usar
executepara crear una clave principal que no sea un número entero (PostgreSQLseriales un número entero usando una secuencia):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);"Y como mencionó Sean McCleary , su modelo de ActiveRecord debe establecer la clave principal usando
set_primary_key:class Employee < ActiveRecord::Base set_primary_key :emp_id ... endfuente
self.primary_key=lugar deset_primary_key, ya que este último está en desuso.schema.rbemp_id, será unaintegercolumna de tipo nuevamente. Parece queschema.rbno se puede almacenarexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"de ninguna manera.schema.rbastructure.sqlviaconfig.active_record.schema_formatconfiguración, que puede ser:sqlo:ruby. guides.rubyonrails.org/v3.2.13/…Esto funciona:
create_table :employees, :primary_key => :emp_id do |t| t.string :first_name t.string :last_name end change_column :employees, :emp_id, :stringPuede que no sea bonito, pero el resultado final es exactamente lo que desea.
fuente
drop_table :employeesschema.rbque no está sincronizado ... retendrá la:primary_key => :emp_iddeclaración, pero cuandorake db:schema:loadse llame, como está al comienzo de las pruebas, producirá una columna de entero. Sin embargo, puede darse el caso de que si cambia a usarstructure.sql(opción de configuración), las pruebas retendrán la configuración y usarán ese archivo para cargar el esquema.Tengo una forma de manejar esto. El SQL ejecutado es ANSI SQL, por lo que probablemente funcionará en la mayoría de las bases de datos relacionales que cumplen con ANSI SQL. He probado que esto funciona para MySQL.
Migración:
create_table :users, :id => false do |t| t.string :oid, :limit => 10, :null => false ... end execute "ALTER TABLE users ADD PRIMARY KEY (oid);"En su modelo, haga esto:
class User < ActiveRecord::Base set_primary_key :oid ... endfuente
Lo he probado en Rails 4.2. Para agregar su clave principal personalizada, puede escribir su migración como:
# 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 endMientras mira la documentación
column(name, type, options = {})y lee la línea:Obtuve los idus anteriores como he mostrado. Aquí están los metadatos de la tabla después de ejecutar esta migración:
[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=#Y desde la consola de Rails:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>fuente
schema.rbarchivo que se genera no refleja elstringtipo de la clave principal, por lo que cuando se genera la base de datos con unaload, la clave principal se crea como un número entero.En Rails 5 puedes hacer
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name endConsulte la documentación de create_table .
fuente
before_create :set_idmétodo en el modelo para asignar el valor de la clave principalParece que es posible hacerlo usando este enfoque:
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" endEso hará que la columna widget_id sea la clave principal para la clase Widget, luego depende de usted completar el campo cuando se crean los objetos. Debería poder hacerlo utilizando la devolución de llamada antes de crear.
Así que algo parecido a
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 endfuente
primary_key: truelugar de soloprimarydar en respuestaEstoy en Rails 2.3.5 y mi siguiente forma funciona con SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions endNo es necesario: id => false.
fuente
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.Después de casi todas las soluciones que dicen "esto funcionó para mí en la base de datos X", veo un comentario del póster original que dice "no funcionó para mí en Postgres". De hecho, el problema real aquí puede ser el soporte de Postgres en Rails, que no es impecable, y probablemente fue peor en 2009 cuando se publicó esta pregunta originalmente. Por ejemplo, si recuerdo correctamente, si estás en Postgres, básicamente no puedes obtener resultados útiles de
rake db:schema:dump.Yo mismo no soy un ninja de Postgres, obtuve esta información del excelente video de Xavier Shay sobre PeepCode en Postgres. Ese video en realidad pasa por alto una biblioteca de Aaron Patterson, creo que Texticle, pero podría estar recordando mal. Pero aparte de eso, es bastante bueno.
De todos modos, si se encuentra con este problema en Postgres, vea si las soluciones funcionan en otras bases de datos. Puede usarlo
rails newpara generar una nueva aplicación como sandbox, o simplemente crear algo comosandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000en
config/database.yml.Y si puede verificar que se trata de un problema de soporte de Postgres y encuentra una solución, contribuya con parches a Rails o empaque sus soluciones en una joya, porque la base de usuarios de Postgres dentro de la comunidad de Rails es bastante grande, principalmente gracias a Heroku. .
fuente
Encontré una solución a esto que funciona con Rails 3:
El archivo de migración:
create_table :employees, {:primary_key => :emp_id} do |t| t.string :emp_id t.string :first_name t.string :last_name endY en el modelo employee.rb:
self.primary_key = :emp_idfuente
El truco que funcionó para mí en Rails 3 y MySQL fue este:
create_table :events, {:id => false} do |t| t.string :id, :null => false end add_index :events, :id, :unique => trueEntonces:
¡Parece que MySQL convierte el índice único en una columna no nula en una clave principal!
fuente
tienes que usar la opción: 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 endfuente
¿Qué tal esta solución?
Modelo en el interior de los empleados por qué no se puede añadir código que compruebe la unicidad de Coloumn en, por ejemplo: Supongamos Empleado es el modelo en que tiene EmpId que es la cadena a continuación para que podamos añadir ": singularidad => true" a EmpId
class Employee < ActiveRecord::Base validates :EmpId , :uniqueness => true endNo estoy seguro de que esta sea la solución, pero funcionó para mí.
fuente
Sé que este es un hilo antiguo con el que me topé ... pero estoy un poco sorprendido de que nadie haya mencionado DataMapper.
Creo que si necesita salirse de la convención de ActiveRecord, he descubierto que es una gran alternativa. También es un mejor enfoque para el legado y puede admitir la base de datos "tal cual".
Ruby Object Mapper (DataMapper 2) es muy prometedor y también se basa en los principios de AREL.
fuente
Agregar índice funciona para mí, estoy usando MySql por cierto.
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 => truefuente