Estou usando migrações do Rails para gerenciar um esquema de banco de dados e estou criando uma tabela simples onde gostaria de usar um valor não inteiro como a chave primária (em particular, uma string). Para abstrair do meu problema, digamos que haja uma tabela employees
onde os funcionários são identificados por uma string alfanumérica, por exemplo "134SNW"
.
Tentei criar a tabela em uma migração como esta:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
O que isso me dá é o que parece ignorar completamente a linha t.string :emp_id
e ir em frente e torná-la uma coluna inteira. Existe alguma outra maneira de fazer rails gerar a restrição PRIMARY_KEY (estou usando PostgreSQL) para mim, sem ter que escrever o SQL em uma execute
chamada?
NOTA : Eu sei que não é melhor usar colunas de string como chaves primárias, portanto, não responda apenas dizendo para adicionar uma chave primária inteira. Posso adicionar um de qualquer maneira, mas esta questão ainda é válida.
fonte
rake db:migrate
A definição do esquema gerado automaticamente não contém esta restrição!Respostas:
Infelizmente, determinei que não é possível fazer isso sem usar
execute
.Por que não funciona
Examinando a fonte do ActiveRecord, podemos encontrar o código para
create_table
:Em
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
Portanto, podemos ver que quando você tenta especificar uma chave primária nas
create_table
opções, ele cria uma chave primária com esse nome especificado (ou, se nenhum for especificado,id
). Ele faz isso chamando o mesmo método que você pode usar dentro de um bloco de definição de tabela:primary_key
.Em
schema_statements.rb
:def primary_key(name) column(name, :primary_key) end
Isso apenas cria uma coluna com o nome de tipo especificado
:primary_key
. Isso é definido da seguinte forma nos adaptadores de banco de dados padrão:PostgreSQL: "serial primary key" MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY" SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"
A solução alternativa
Já que estamos presos a estes como os tipos de chave primária, temos que usar
execute
para criar uma chave primária que não seja um inteiro (o PostgreSQLserial
é um inteiro usando uma sequência):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);"
E como Sean McCleary mencionou , seu modelo ActiveRecord deve definir a chave primária usando
set_primary_key
:class Employee < ActiveRecord::Base set_primary_key :emp_id ... end
fonte
self.primary_key=
vez deset_primary_key
, já que o último está obsoleto.schema.rb
emp_id
será umainteger
coluna de tipo novamente. Parece queschema.rb
não pode armazenar deexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
forma alguma.schema.rb
parastructure.sql
viaconfig.active_record.schema_format
configuração, que pode ser:sql
ou:ruby
. guias.rubyonrails.org/v3.2.13/…Isso 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
Pode não ser bonito, mas o resultado final é exatamente o que você deseja.
fonte
drop_table :employees
schema.rb
que não está em sincronia ... ele manterá a:primary_key => :emp_id
declaração, mas quandorake db:schema:load
for chamado, como é no início dos testes, produzirá uma coluna inteira. No entanto, pode ser o caso de que, se você alternar para o uso destructure.sql
(opção de configuração), os testes retenham as configurações e usem esse arquivo para carregar o esquema.Eu tenho uma maneira de lidar com isso. O SQL executado é ANSI SQL, portanto, provavelmente funcionará na maioria dos bancos de dados relacionais compatíveis com ANSI SQL. Eu testei se isso funciona para MySQL.
Migração:
create_table :users, :id => false do |t| t.string :oid, :limit => 10, :null => false ... end execute "ALTER TABLE users ADD PRIMARY KEY (oid);"
Em seu modelo, faça o seguinte:
class User < ActiveRecord::Base set_primary_key :oid ... end
fonte
No Rails 5 você pode fazer
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name end
Veja a documentação de create_table .
fonte
before_create :set_id
método no modelo para atribuir o valor da chave primáriaEu tentei no Rails 4.2. Para adicionar sua chave primária personalizada, você pode escrever sua migração 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
Enquanto olha a documentação
column(name, type, options = {})
e lê a linha:Eu tenho as ides acima como mostrei. Aqui estão os metadados da tabela após a execução desta migração:
[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=#
E do console Rails:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>
fonte
schema.rb
arquivo gerado não reflete ostring
tipo da chave primária, portanto, ao gerar o banco de dados com umload
, a chave primária é criada como um inteiro.Parece que é possível fazer usando esta abordagem:
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
Isso tornará a coluna widget_id a chave primária para a classe Widget, então cabe a você preencher o campo quando os objetos forem criados. Você deve ser capaz de fazer isso usando o callback before create.
Então, algo na linha 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
fonte
primary_key: true
vez de apenasprimary
dar uma respostaEstou no Rails 2.3.5 e minha seguinte maneira funciona com SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end
Não há necessidade de: id => false.
fonte
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Depois de quase todas as soluções que dizem "isso funcionou para mim no banco de dados X", vejo um comentário do autor da postagem original no sentido de "não funcionou para mim no Postgres". O verdadeiro problema aqui pode ser o suporte Postgres em Rails, que não é perfeito, e provavelmente foi pior em 2009, quando esta questão foi postada originalmente. Por exemplo, se bem me lembro, se você está no Postgres, basicamente não consegue obter resultados úteis de
rake db:schema:dump
.Eu não sou um ninja do Postgres, peguei essa informação do excelente vídeo PeepCode de Xavier Shay no Postgres. Na verdade, esse vídeo ignora uma biblioteca de Aaron Patterson, acho Texticle, mas posso estar me lembrando errado. Mas, fora isso, é muito bom.
De qualquer forma, se você está tendo esse problema no Postgres, veja se as soluções funcionam em outros bancos de dados. Pode ser usado
rails new
para gerar um novo aplicativo como uma sandbox ou apenas criar algo comosandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000
no
config/database.yml
.E se você pode verificar que é um problema de suporte do Postgres, e descobrir uma correção, por favor contribua com patches para Rails ou empacote suas correções em um gem, porque a base de usuários Postgres dentro da comunidade Rails é muito grande, principalmente graças ao Heroku .
fonte
Eu encontrei uma solução para isso que funciona com Rails 3:
O arquivo de migração:
create_table :employees, {:primary_key => :emp_id} do |t| t.string :emp_id t.string :first_name t.string :last_name end
E no modelo employee.rb:
self.primary_key = :emp_id
fonte
O truque que funcionou para mim no Rails 3 e no MySQL foi este:
create_table :events, {:id => false} do |t| t.string :id, :null => false end add_index :events, :id, :unique => true
Então:
Parece que o MySQL converte o índice único de uma coluna não nula em uma chave primária!
fonte
você tem que usar a opção: 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
fonte
Que tal esta solução,
Dentro do modelo Employee, por que não podemos adicionar o código que verificará a exclusividade na coluna, por exemplo: Assume que Employee é o modelo em que você tem EmpId, que é string, então, para isso, podemos adicionar ": uniqueness => true" a EmpId
class Employee < ActiveRecord::Base validates :EmpId , :uniqueness => true end
Não tenho certeza se esta é a solução, mas funcionou para mim.
fonte
Eu sei que este é um tópico antigo que encontrei ... mas estou meio chocado que ninguém mencionou o DataMapper.
Descobri que se você precisa se desviar da convenção do ActiveRecord, descobri que é uma ótima alternativa. Também é uma abordagem melhor para legado e você pode suportar o banco de dados "como está".
Ruby Object Mapper (DataMapper 2) é muito promissor e também se baseia nos princípios do AREL!
fonte
Adicionar índice funciona para mim, estou usando o 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
fonte