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 employees
donde 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_id
y 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 execute
llamada?
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 ... end
Entonces podemos ver que cuando intenta especificar una clave primaria en las
create_table
opciones, 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) end
Esto 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
execute
para crear una clave principal que no sea un número entero (PostgreSQLserial
es 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 ... end
fuente
self.primary_key=
lugar deset_primary_key
, ya que este último está en desuso.schema.rb
emp_id
, será unainteger
columna de tipo nuevamente. Parece queschema.rb
no se puede almacenarexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
de ninguna manera.schema.rb
astructure.sql
viaconfig.active_record.schema_format
configuración, que puede ser:sql
o: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, :string
Puede que no sea bonito, pero el resultado final es exactamente lo que desea.
fuente
drop_table :employees
schema.rb
que no está sincronizado ... retendrá la:primary_key => :emp_id
declaración, pero cuandorake db:schema:load
se 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 ... end
fuente
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 end
Mientras 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.rb
archivo que se genera no refleja elstring
tipo 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 end
Consulte la documentación de create_table .
fuente
before_create :set_id
mé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" end
Eso 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 end
fuente
primary_key: true
lugar de soloprimary
dar 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 end
No 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 new
para generar una nueva aplicación como sandbox, o simplemente crear algo comosandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000
en
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 end
Y en el modelo employee.rb:
self.primary_key = :emp_id
fuente
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 => true
Entonces:
¡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 end
fuente
¿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 end
No 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 => true
fuente