Używam migracji Rails do zarządzania schematem bazy danych i tworzę prostą tabelę, w której chciałbym użyć wartości niecałkowitej jako klucza podstawowego (w szczególności ciągu). Aby oderwać się od mojego problemu, załóżmy, że istnieje tabela, w employees
której pracownicy są identyfikowani za pomocą ciągu alfanumerycznego, np "134SNW"
.
Próbowałem utworzyć tabelę w migracji w następujący sposób:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
To, co mi to daje, to coś, co wygląda na to, że całkowicie zignorował linię t.string :emp_id
i poszedł dalej i uczynił go kolumną całkowitą. Czy istnieje inny sposób na wygenerowanie przez szyny ograniczenia PRIMARY_KEY (używam PostgreSQL) bez konieczności pisania kodu SQL w execute
wywołaniu?
UWAGA : Wiem, że nie najlepiej jest używać kolumn łańcuchowych jako kluczy podstawowych, więc nie udzielaj odpowiedzi, mówiąc tylko o dodaniu klucza podstawowego w postaci całkowitej. I tak mogę dodać jedno, ale to pytanie jest nadal aktualne.
źródło
rake db:migrate
Definicja schematu wygenerowanego automatycznie nie zawiera tego ograniczenia!Odpowiedzi:
Niestety stwierdziłem, że nie da się tego zrobić bez użycia
execute
.Dlaczego to nie działa
Badając źródło ActiveRecord, możemy znaleźć kod dla
create_table
:W
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
Widzimy więc, że próbując określić klucz podstawowy w
create_table
opcjach, tworzy klucz podstawowy o tej określonej nazwie (lub, jeśli nie jest określony,id
). Czyni to poprzez wywołanie tej samej metody można używać wewnątrz bloku definicji tabeli:primary_key
.W
schema_statements.rb
:def primary_key(name) column(name, :primary_key) end
Spowoduje to po prostu utworzenie kolumny o określonej nazwie typu
:primary_key
. Jest to ustawione w następujący sposób w standardowych adapterach bazy danych:PostgreSQL: "serial primary key" MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY" SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"
Obejście problemu
Ponieważ utknęliśmy z tymi typami kluczy podstawowych, musimy użyć ich
execute
do utworzenia klucza podstawowego, który nie jest liczbą całkowitą (PostgreSQLserial
to liczba całkowita używająca sekwencji):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);"
Jak wspomniał Sean McCleary , model ActiveRecord powinien ustawić klucz podstawowy za pomocą
set_primary_key
:class Employee < ActiveRecord::Base set_primary_key :emp_id ... end
źródło
self.primary_key=
zamiastset_primary_key
, ponieważ ta ostatnia jest przestarzała.schema.rb
emp_id
będzieinteger
ponownie kolumna typu. Wydaje się, żeschema.rb
nie możnaexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
w żaden sposób przechowywać .schema.rb
celustructure.sql
poprzezconfig.active_record.schema_format
ustawienia, które mogą być:sql
albo:ruby
. guide.rubyonrails.org/v3.2.13/…To działa:
create_table :employees, :primary_key => :emp_id do |t| t.string :first_name t.string :last_name end change_column :employees, :emp_id, :string
To może nie być ładne, ale efekt końcowy jest dokładnie taki, jakiego chcesz.
źródło
drop_table :employees
schema.rb
niezsynchronizowany ... zachowa:primary_key => :emp_id
deklarację, ale gdyrake db:schema:load
zostanie wywołana, tak jak na początku testów, zwróci kolumnę całkowitą. Jednak może się zdarzyć, że jeśli przełączysz się na używaniestructure.sql
(opcja konfiguracji), testy zachowają ustawienia i użyją tego pliku do załadowania schematu.Mam na to jeden sposób. Wykonywany kod SQL to ANSI SQL, więc prawdopodobnie będzie działać na większości relacyjnych baz danych zgodnych z ANSI SQL. Przetestowałem, że to działa dla MySQL.
Migracja:
create_table :users, :id => false do |t| t.string :oid, :limit => 10, :null => false ... end execute "ALTER TABLE users ADD PRIMARY KEY (oid);"
W swoim modelu zrób to:
class User < ActiveRecord::Base set_primary_key :oid ... end
źródło
Wypróbowałem to w Railsach 4.2. Aby dodać niestandardowy klucz podstawowy, możesz zapisać migrację jako:
# 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
Przeglądając dokumentację
column(name, type, options = {})
i czytając wiersz:Otrzymałem powyższe ides, jak pokazałem. Oto metadane tabeli po przeprowadzeniu migracji:
[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=#
Z konsoli Railsów:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>
źródło
schema.rb
, że generowany plik nie odzwierciedlastring
typu klucza podstawowego, więc podczas generowania bazy danych za pomocąload
klucza podstawowego jest tworzony jako liczba całkowita.W Rails 5 możesz to zrobić
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name end
Zobacz dokumentację create_table .
źródło
before_create :set_id
metodę w modelu, aby przypisać wartość klucza podstawowegoWygląda na to, że można to zrobić za pomocą tego podejścia:
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
Spowoduje to, że kolumna widget_id stanie się kluczem podstawowym dla klasy Widget, a następnie wypełnienie pola podczas tworzenia obiektów zależy od Ciebie. Powinieneś być w stanie to zrobić za pomocą wywołania zwrotnego przed utworzeniem.
Więc coś w stylu
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
źródło
primary_key: true
zamiast po prostuprimary
podać w odpowiedziJestem na Railsach 2.3.5 i mój następujący sposób działa z SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end
Nie ma potrzeby: id => false.
źródło
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Po prawie każdym rozwiązaniu, które mówi, że „to działało dla mnie w bazie danych X”, widzę komentarz oryginalnego plakatu, że „nie działa dla mnie na Postgresie”. Prawdziwym problemem może być obsługa Postgres w Railsach, która nie jest bezbłędna i prawdopodobnie była gorsza w 2009 roku, kiedy to pytanie zostało pierwotnie opublikowane. Na przykład, jeśli dobrze pamiętam, jeśli korzystasz z Postgres, w zasadzie nie możesz uzyskać przydatnych wyników
rake db:schema:dump
.Sam nie jestem ninja Postgres, otrzymałem te informacje z doskonałego filmu Xaviera Shay'a PeepCode na Postgres. Ten film faktycznie przeoczył bibliotekę Aarona Pattersona, myślę, że Texticle, ale mogłem źle pamiętać. Ale poza tym jest całkiem niezły.
W każdym razie, jeśli napotkasz ten problem na Postgres, sprawdź, czy rozwiązania działają w innych bazach danych. Może użyj
rails new
do wygenerowania nowej aplikacji jako piaskownicy lub po prostu utwórz coś takiegosandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000
w
config/database.yml
.A jeśli możesz zweryfikować, że jest to problem z obsługą Postgres i znajdziesz rozwiązanie, prześlij poprawki do Railsów lub zapakuj swoje poprawki w klejnot, ponieważ baza użytkowników Postgres w społeczności Rails jest dość duża, głównie dzięki Heroku .
źródło
Znalazłem rozwiązanie, które działa z Railsami 3:
Plik migracji:
create_table :employees, {:primary_key => :emp_id} do |t| t.string :emp_id t.string :first_name t.string :last_name end
A w modelu Employer.rb:
self.primary_key = :emp_id
źródło
Sztuczka, która zadziałała dla mnie na Railsach 3 i MySQL była taka:
create_table :events, {:id => false} do |t| t.string :id, :null => false end add_index :events, :id, :unique => true
Więc:
Wygląda na to, że MySQL konwertuje unikalny indeks w kolumnie innej niż null na klucz podstawowy!
źródło
musisz użyć opcji: 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
źródło
A co z tym rozwiązaniem,
Wewnątrz modelu pracownika, dlaczego nie możemy dodać kodu, który będzie sprawdzał unikalność w kolumnie, na przykład: Załóżmy, że pracownik jest modelem w tym, że masz EmpId, który jest ciągiem znaków, a następnie możemy dodać ": unikalność => prawda" do EmpId
class Employee < ActiveRecord::Base validates :EmpId , :uniqueness => true end
Nie jestem pewien, czy to jest rozwiązanie, ale zadziałało.
źródło
Wiem, że to stary wątek, na który się natknąłem ... ale jestem trochę zszokowany, że nikt nie wspomniał o DataMapper.
Stwierdzam, że jeśli musisz odejść od konwencji ActiveRecord, stwierdziłem, że jest to świetna alternatywa. Jest to również lepsze podejście do starszych wersji i możesz obsługiwać bazę danych „tak jak jest”.
Ruby Object Mapper (DataMapper 2) zawiera wiele obietnic i również opiera się na zasadach AREL!
źródło
Dodanie indeksu działa dla mnie, używam 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
źródło