Rails'i kullanarak, birincil anahtarımı tam sayı yazılmış bir sütun olmayacak şekilde nasıl ayarlayabilirim?


86

Bir veritabanı şemasını yönetmek için Rails geçişlerini kullanıyorum ve tamsayı olmayan bir değeri birincil anahtar olarak (özellikle bir dize) kullanmak istediğim basit bir tablo oluşturuyorum. Sorunumdan soyutlamak için, employeesçalışanların alfanümerik bir dizeyle tanımlandığı bir tablo olduğunu varsayalım, örneğin "134SNW".

Tabloyu şu şekilde bir geçişte oluşturmayı denedim:

create_table :employees, {:primary_key => :emp_id} do |t|
    t.string :emp_id
    t.string :first_name
    t.string :last_name
end

Bunun bana verdiği şey, çizgiyi tamamen görmezden geldi t.string :emp_id ve ileri gitti ve onu bir tamsayı sütunu yaptı . Bir executeçağrıda SQL'i yazmak zorunda kalmadan, rayların PRIMARY_KEY kısıtlamasını (PostgreSQL kullanıyorum) benim için oluşturmasını sağlamanın başka bir yolu var mı?

NOT : Dize sütunlarını birincil anahtar olarak kullanmanın en iyisi olmadığını biliyorum, bu nedenle lütfen yalnızca bir tamsayı birincil anahtar eklemeyi söyleyen yanıtlar almayın. Yine de ekleyebilirim, ancak bu soru hala geçerlidir.


Bende de aynı sorun var ve bunun cevabını görmek isterim. Şimdiye kadarki önerilerin hiçbiri işe yaramadı.
Sean McCleary

1
Postgres kullanıyorsanız hiçbiri çalışmayacaktır. Dan Chak'ın "Enterprise Rails" i doğal / bileşik anahtarların kullanımı için bazı ipuçları içerir.
Azeem.Butt

Şu gibi bir şey kullandığınızda dikkatli olun: <! - dil: lang-rb -> "ALTER TABLE çalışanları EKLE PRIMARY KEY (emp_id);" Tablo kısıtlaması çalıştırıldıktan sonra doğru ayarlanmış olsa da rake db:migrateOtomatik oluşturulan şema tanımı bu kısıtlamayı içermiyor!
pymkin

Yanıtlar:


107

Maalesef kullanmadan yapmanın mümkün olmadığını belirledim execute.

Neden çalışmıyor

ActiveRecord kaynağını inceleyerek, aşağıdaki kodu bulabiliriz create_table:

İçinde 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

Dolayısıyla, create_tableseçeneklerde bir birincil anahtar belirtmeye çalıştığınızda , belirtilen adla (veya hiçbiri belirtilmemişse id) bir birincil anahtar oluşturduğunu görebiliriz . Bu bir tablo tanımı bloğunun içinde kullanabileceğiniz aynı yöntemi çağırarak yapar: primary_key.

İçinde schema_statements.rb:

def primary_key(name)
  column(name, :primary_key)
end

Bu sadece belirtilen tür adı ile bir sütun oluşturur :primary_key. Bu, standart veritabanı bağdaştırıcılarında aşağıdaki şekilde ayarlanır:

PostgreSQL: "serial primary key"
MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"

Geçici çözüm

Bunlardan birincil anahtar türleri olarak kaldığımız için, executetamsayı olmayan bir birincil anahtar oluşturmak için kullanmalıyız (PostgreSQL serial, sıra kullanan bir tamsayıdır):

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);"

Ve Sean McCleary'nin bahsettiği gibi, ActiveRecord modeliniz aşağıdakileri kullanarak birincil anahtarı ayarlamalıdır set_primary_key:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end

15
geçici çözüm tamsayı sütunu olarak emp_id oluşturmak için rake db: test: clone veya rake db: schema: load yapacaktır.
Donny Kurnia

18
aslında, ikincisi kullanımdan kaldırıldığı için şimdi self.primary_key=yerine kullanmalısınız set_primary_key.
Gary S. Weaver

2
İşte benim için işe yarayan tek çözüm. Umarım başkalarına yardımcı olur: stackoverflow.com/a/15297616/679628
findchris

8
Bu yaklaşımla ilgili sorun, veritabanınızı buradan kurduğunuzda tekrar schema.rb emp_idbir integertür sütunu olacak olmasıdır . Görünüşe göre hiçbir şekilde schema.rbsaklayamaz execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);".
Tintin81

4
@DonnyKurnia @ Tintin81 Şema dökümünü ayar aracılığıyla olarak schema.rbdeğiştirebilirsiniz , ya da olabilir . guides.rubyonrails.org/v3.2.13/…structure.sqlconfig.active_record.schema_format:sql:ruby
Svilen Ivanov

21

Bu çalışıyor:

create_table :employees, :primary_key => :emp_id do |t|
  t.string :first_name
  t.string :last_name
end
change_column :employees, :emp_id, :string

Güzel olmayabilir, ancak sonuç tam olarak istediğiniz şeydir.


1
En sonunda! Benim için işe yarayan tek çözüm bu.
findchris

Aşağı göç için de kod belirtmek zorunda mıyız? veya Rails aşağı taşındığında ne yapılacağını bilecek kadar akıllı mı?
amey1908

2
Aşağı geçiş için:drop_table :employees
Austin

6
Ne yazık ki, bu yöntem aynı zamanda bizi schema.rbsenkronize olmayan bir ile bırakıyor ... :primary_key => :emp_idbildirimi koruyacak , ancak rake db:schema:loadçağrıldığında, testlerin başında olduğu için bir tamsayı sütunu üretecek. Ancak, structure.sql(yapılandırma seçeneği) testleri kullanmaya geçerseniz , ayarları korur ve şemayı yüklemek için bu dosyayı kullanır.
Tom Harrison

18

Bunu halletmenin bir yolu var. Yürütülen SQL ANSI SQL'dir, bu nedenle ANSI SQL uyumlu ilişkisel veritabanlarının çoğunda çalışacaktır. Bunun MySQL için çalıştığını test ettim.

Göç:

create_table :users, :id => false do |t|
    t.string :oid, :limit => 10, :null => false
    ...
end
execute "ALTER TABLE users ADD PRIMARY KEY (oid);"

Modelinizde şunu yapın:

class User < ActiveRecord::Base
    set_primary_key :oid
    ...
end


10

Rails 5'te şunları yapabilirsiniz:

create_table :employees, id: :string do |t|
  t.string :first_name
  t.string :last_name
end

Create_table belgelerine bakın .


before_create :set_idBirincil anahtarın değerini atamak için modele bir tür yöntem eklemeniz gerekeceğini belirtmek gerekir
FloatingRock

9

Rails 4.2'de denedim. Özel birincil anahtarınızı eklemek için geçişinizi şu şekilde yazabilirsiniz:

# 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

Belgelerine bakarken column(name, type, options = {})ve satırı okurken:

type: Primary_key,: dize,: metin,: tamsayı,: şamandıra,: ondalık,: datetime,: Zaman: tarih: ikili,: parametre normalde göçler yerli aşağıdakilerden biridir türlerinden biridir boolean .

Yukarıdaki kimlikleri gösterdiğim gibi aldım. Bu geçişi çalıştırdıktan sonraki tablo meta verileri:

[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=#

Ve Rails konsolundan:

Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>

3
Sorun şu schema.rbki, oluşturulan dosya stringbirincil anahtarın türünü yansıtmıyor , bu nedenle veritabanını a ile loadoluştururken birincil anahtar bir tam sayı olarak oluşturulur.
Peter Alfvin

8

Bu yaklaşımı kullanarak yapmak mümkün gibi görünüyor:

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

Bu, widget_id sütununu Widget sınıfı için birincil anahtar yapar, ardından nesneler oluşturulduğunda alanı doldurmak size kalmıştır. Geri aramayı oluşturmadan önce bunu kullanarak yapabilmelisiniz.

Yani şu çizgide bir şey

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

Bu benim için çalışmıyor, en azından PostgreSQL'de değil. Hiçbir birincil anahtar belirtilmedi.
Rudd Zwolinski

Hmm ilginç, MySQL on Rails 2.3.2 ile test ettim - belki de rayların sürümüyle ilişkili olabilir?
paulthenerd

Emin değilim - bunun Rails'in versiyonu olduğunu düşünmüyorum. Rails 2.3.3 kullanıyorum.
Rudd Zwolinski

Maalesef bu yaklaşımı kullanan tablo için gerçek bir birincil anahtar ayarlanmamıştır.
Sean McCleary

2
Bu yaklaşım, en azından Rails 4.2 ve Postgresql ile çalışır. Test ettim, ancak primary_key: truesadece primarycevap olarak vermek yerine kullanmanız gerekiyor
Anwar

8

Rails 2.3.5'teyim ve aşağıdaki yolum SQLite3 ile çalışıyor

create_table :widgets, { :primary_key => :widget_id } do |t|
  t.string :widget_id

  # other column definitions
end

Gerek yok: id => false.


üzgünüm, ama widget_id tekniğinizi uyguladığımda tamsayı olarak değişti ... eny çözümünüz var mı?
enrique-carbonell

1
Bu artık çalışmıyor, en azından ActiveRecord 4.1.4'te değil. Aşağıdaki hatayı veriyor:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Tamer Shlash

4

"Bu benim için X veritabanında çalıştı" diyen hemen hemen her çözümden sonra, orijinal göndericinin "Postgres'te benim için çalışmadı" şeklinde bir yorum görüyorum. Buradaki asıl sorun, Rails'deki Postgres desteği olabilir, ki bu kusursuz değildir ve muhtemelen 2009'da bu sorunun ilk yayınlandığı zaman daha kötüydü. Örneğin, doğru hatırlıyorsam, Postgres'teyseniz, temelderake db:schema:dump .

Ben kendim bir Postgres ninja değilim, bu bilgiyi Xavier Shay'in Postgres'teki mükemmel PeepCode videosundan aldım. Bu video aslında Aaron Patterson'un bir kütüphanesine bakıyor, sanırım Texticle ama yanlış hatırlıyor olabilirim. Ama bunun dışında oldukça harika.

Her neyse, Postgres'te bu sorunla karşılaşıyorsanız, çözümlerin başka veritabanlarında çalışıp çalışmadığına bakın. rails newKorumalı alan olarak yeni bir uygulama oluşturmak için kullanabilirsiniz veya yalnızca

sandbox:
  adapter: sqlite3
  database: db/sandbox.sqlite3
  pool: 5
  timeout: 5000

içinde config/database.yml.

Ve bunun bir Postgres destek sorunu olduğunu doğrulayabilirseniz ve bir düzeltme bulursanız, lütfen Rails'e yamalar ekleyin veya düzeltmelerinizi bir mücevherde paketleyin, çünkü Rails topluluğundaki Postgres kullanıcı tabanı, özellikle Heroku sayesinde oldukça büyüktür. .


2
FUD ve yanlışlık için olumsuz oylama. Postgres'i 2007'den beri Rails projemin çoğunda kullandım. Rails'de Postgres desteği mükemmel ve şema damperiyle ilgili bir sorun yok.
Marnen Laibow-Koser

2
bir yandan Postgres'in buradaki problem olmadığı doğru. Öte yandan, işte 2009 rails.lighthouseapp.com/projects/8994/tickets/2418'den yalnızca pg'ye yönelik bir şema boşaltma hatası ve işte bir tane daha rails.lighthouseapp.com/projects/8994/tickets/2514
Giles Bowkett

Postgres 8.3 güncellemesini çevreleyen Rails ve Postgres ile ilgili kararlaştırılmış bir sorun vardı; burada (doğru, IMO) dize değişmezleri ve alıntılar için SQL standardına geçmeye karar verdiler. Rails bağdaştırıcısı Postgres sürümlerini kontrol etmedi ve bir süre programlanmış olması gerekti.
Judson

@Judson İlginç. Ben onunla hiç karşılaşmadım.
Marnen Laibow-Koser

4

Buna Rails 3 ile çalışan bir çözüm buldum:

Taşıma dosyası:

create_table :employees, {:primary_key => :emp_id} do |t|
  t.string :emp_id
  t.string :first_name
  t.string :last_name
end

Employee.rb modelinde:

self.primary_key = :emp_id

3

Rails 3 ve MySQL'de benim için işe yarayan numara şuydu:

create_table :events, {:id => false} do |t|
  t.string :id, :null => false
end

add_index :events, :id, :unique => true

Yani:

  1. tamsayı birincil anahtar oluşturmamak için id => false kullanın
  2. istenen veri türünü kullanın ve şunu ekleyin: null => false
  3. o sütuna benzersiz bir dizin ekleyin

Görünüşe göre MySQL, boş olmayan bir sütundaki benzersiz dizini birincil anahtara dönüştürüyor!


MySQL 5.5.15 ile Rails 3.22'de yalnızca benzersiz bir anahtar oluşturur, ancak birincil anahtar oluşturmaz.
lulalala

2

şu seçeneği kullanmanız gerekir: 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

Bu benim için çalışmıyor, en azından PostgreSQL'de değil. Hiçbir birincil anahtar belirtilmedi.
Rudd Zwolinski

1
Bu yaklaşım, id sütununu çıkaracaktır, ancak birincil anahtar gerçekte tabloda ayarlanmayacaktır.
Sean McCleary

1

Bu çözüme ne dersiniz?

Çalışan modeli içinde neden sütunlarda benzersizliği kontrol edecek bir kod ekleyemiyoruz, örneğin: EmpId'inizde Employee'nin Model olduğunu varsayın ve bunun için EmpId'e ": uniqueness => true" ekleyebiliriz

    class Employee < ActiveRecord::Base
      validates :EmpId , :uniqueness => true
    end

Bunun çözüm olduğundan emin değilim ama bu benim için çalıştı.


1

Bunun karşılaştığım eski bir konu olduğunu biliyorum ... ama kimsenin DataMapper'dan bahsetmediği için şok oldum.

ActiveRecord sözleşmesinden çıkmanız gerekirse, bunun harika bir alternatif olduğunu anladım. Ayrıca miras için daha iyi bir yaklaşımdır ve veritabanını "olduğu gibi" destekleyebilirsiniz.

Ruby Nesne Eşleştiricisi (DataMapper 2) çok fazla vaatte bulunur ve AREL ilkelerini de temel alır!


1

Dizin eklemek benim için çalışıyor, MySql btw kullanıyorum.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.