Sử dụng Rails, làm cách nào để tôi có thể đặt khóa chính của mình không phải là một cột kiểu số nguyên?


85

Tôi đang sử dụng di chuyển Rails để quản lý một lược đồ cơ sở dữ liệu và tôi đang tạo một bảng đơn giản, nơi tôi muốn sử dụng một giá trị không phải số nguyên làm khóa chính (cụ thể là một chuỗi). Để giải quyết vấn đề của tôi, giả sử có một bảng employeestrong đó các nhân viên được xác định bằng một chuỗi chữ và số, chẳng hạn "134SNW".

Tôi đã thử tạo bảng trong một lần di chuyển như thế này:

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

Điều này mang lại cho tôi là có vẻ như nó hoàn toàn bỏ qua dòng t.string :emp_idvà tiếp tục và biến nó thành một cột số nguyên. Có cách nào khác để rails tạo ràng buộc PRIMARY_KEY (tôi đang sử dụng PostgreSQL) cho tôi mà không cần phải viết SQL trong một executecuộc gọi không?

LƯU Ý : Tôi biết không tốt nhất là sử dụng cột chuỗi làm khóa chính, vì vậy vui lòng không trả lời chỉ nói thêm khóa chính số nguyên. Tôi có thể thêm một câu nào đó, nhưng câu hỏi này vẫn còn giá trị.


Tôi có cùng một vấn đề và tôi rất muốn xem câu trả lời cho điều này. Không có đề xuất nào cho đến nay đã hoạt động.
Sean McCleary

1
Không ai trong số chúng sẽ hoạt động nếu bạn đang sử dụng Postgres. "Đường ray doanh nghiệp" của Dan Chak có một số mẹo để sử dụng khóa tự nhiên / tổ hợp.
Azeem.Butt

Hãy lưu ý rằng khi bạn sử dụng một cái gì đó như: <! - language: lang-rb -> thực thi "ALTER TABLE staff ADD PRIMARY KEY (emp_id);" Mặc dù ràng buộc bảng được đặt đúng sau khi chạy rake db:migrateĐịnh nghĩa lược đồ được tạo tự động không chứa ràng buộc này!
pymkin

Câu trả lời:


107

Thật không may, tôi đã xác định rằng không thể làm điều đó nếu không sử dụng execute.

Tại sao nó không hoạt động

Bằng cách kiểm tra nguồn ActiveRecord, chúng ta có thể tìm thấy mã cho create_table:

Trong 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

Vì vậy, chúng ta có thể thấy rằng khi bạn cố gắng chỉ định một khóa chính trong các create_tabletùy chọn, nó sẽ tạo ra một khóa chính với tên được chỉ định đó (hoặc, nếu không có tên nào được chỉ định, id). Nó làm điều này bằng cách gọi cùng một phương pháp bạn có thể sử dụng bên trong một khối định nghĩa bảng: primary_key.

Trong schema_statements.rb:

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

Điều này chỉ tạo một cột với tên cụ thể của loại :primary_key. Điều này được đặt thành như sau trong bộ điều hợp cơ sở dữ liệu tiêu chuẩn:

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

Cách giải quyết

Vì chúng ta bị mắc kẹt với những thứ này là các loại khóa chính, chúng ta phải sử dụng executeđể tạo khóa chính không phải là số nguyên (PostgreSQL seriallà số nguyên sử dụng một chuỗi):

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

Và như Sean McCleary đã đề cập , mô hình ActiveRecord của bạn nên đặt khóa chính bằng cách sử dụng set_primary_key:

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

15
giải pháp thay thế sẽ tạo rake db: test: clone hoặc rake db: schema: load để tạo emp_id dưới dạng cột số nguyên.
Donny Kurnia

18
thực sự, bây giờ bạn nên sử dụng self.primary_key=thay vì set_primary_key, vì sau này không được dùng nữa.
Gary S. Weaver,

2
Đây là giải pháp duy nhất phù hợp với tôi. Tôi hy vọng nó giúp những người khác: stackoverflow.com/a/15297616/679628
findchris

8
Vấn đề với cách tiếp cận này là khi bạn thiết lập cơ sở dữ liệu của bạn từ đó schema.rb emp_idsẽ lại là một integercột kiểu. Có vẻ như schema.rbkhông thể lưu trữ execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"trong bất kỳ cách nào.
Tintin81

4
@DonnyKurnia @ Tintin81 Bạn có thể chuyển bãi chứa lược đồ từ schema.rbsang structure.sqlthông qua config.active_record.schema_formatcài đặt, có thể là :sqlhoặc :ruby. Guide.rubyonrails.org/v3.2.13/…
Svilen Ivanov

21

Những công việc này:

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

Nó có thể không được đẹp, nhưng kết quả cuối cùng là chính xác những gì bạn muốn.


1
Cuối cùng! Đây là giải pháp duy nhất làm việc cho tôi.
findchris

Chúng ta có phải chỉ định mã để di chuyển xuống không? hoặc Rails có đủ thông minh để biết phải làm gì khi di chuyển xuống không?
amey1908

2
Đối với việc di cư xuống:drop_table :employees
Austin

6
Thật không may, phương thức này cũng để lại cho chúng ta một schema.rbkhông đồng bộ ... nó sẽ giữ lại phần :primary_key => :emp_idkhai báo, nhưng khi rake db:schema:loadđược gọi, như lúc bắt đầu các bài kiểm tra, nó sẽ tạo ra một cột số nguyên. Tuy nhiên, có thể xảy ra trường hợp nếu bạn chuyển sang sử dụng structure.sql(tùy chọn cấu hình) các thử nghiệm sẽ giữ lại các cài đặt và sử dụng tệp đó để tải lược đồ.
Tom Harrison

18

Tôi có một cách để xử lý việc này. SQL được thực thi là ANSI SQL nên nó có thể sẽ hoạt động trên hầu hết các cơ sở dữ liệu quan hệ tuân thủ ANSI SQL. Tôi đã kiểm tra rằng điều này hoạt động cho MySQL.

Di cư:

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

Trong mô hình của bạn, hãy làm điều này:

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


9

Tôi đã thử nó trong Rails 4.2. Để thêm khóa chính tùy chỉnh của mình, bạn có thể viết quá trình di chuyển của mình dưới dạng:

# 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

Trong khi xem tài liệu column(name, type, options = {})và đọc dòng:

Các typetham số thường là một trong những cuộc di cư loại bản địa, đó là một trong những điều sau đây:: primary_key,: string,: văn bản,: số nguyên,: float,: thập phân,: datetime,: thời gian,: ngày,: nhị phân,: boolean .

Tôi nhận được các id ở trên như tôi đã hiển thị. Đây là dữ liệu meta bảng sau khi chạy quá trình di chuyển này:

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

Và từ bảng điều khiển Rails:

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

3
Tuy nhiên, vấn đề xảy ra là schema.rbtệp được tạo không phản ánh stringloại cho khóa chính, vì vậy khi tạo cơ sở dữ liệu bằng a load, khóa chính được tạo dưới dạng số nguyên.
Peter Alfvin

9

Trong Rails 5, bạn có thể làm

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

Xem tài liệu create_table .


Cần lưu ý rằng bạn cần thêm một số loại before_create :set_idphương thức trong mô hình để chỉ định giá trị của khóa chính
FloatingRock

8

Có vẻ như có thể thực hiện bằng cách sử dụng phương pháp này:

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

Điều đó sẽ làm cho cột widget_id trở thành khóa chính cho lớp Widget, sau đó tùy thuộc vào bạn để điền trường khi các đối tượng được tạo. Bạn có thể làm như vậy bằng cách sử dụng lệnh gọi lại trước khi tạo.

Vì vậy, một cái gì đó dọc theo dòng

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

Điều này không hiệu quả với tôi, ít nhất là không có trong PostgreSQL. Không có khóa chính nào được chỉ định.
Rudd Zwolinski,

Hmm thú vị là tôi đã thử nghiệm nó với MySQL trên Rails 2.3.2 - có lẽ cũng có thể liên quan đến phiên bản của đường ray?
paulthenerd 29-07-09

Tôi không chắc - tôi không nghĩ đó là phiên bản của Rails. Tôi đang sử dụng Rails 2.3.3.
Rudd Zwolinski,

Rất tiếc, khóa chính thực tế không được đặt cho bảng bằng cách sử dụng phương pháp này.
Sean McCleary

2
Cách tiếp cận này ít nhất cũng hoạt động với Rails 4.2 và Postgresql. Tôi đã thử nghiệm, nhưng bạn cần phải sử dụng primary_key: truethay vì chỉ primaryđưa ra trong câu trả lời
Anwar

8

Tôi đang sử dụng Rails 2.3.5 và cách sau của tôi hoạt động với SQLite3

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

  # other column definitions
end

Không cần: id => false.


xin lỗi, nhưng widget_id đã thay đổi thành số nguyên khi tôi áp dụng tecnique của bạn ... bạn có giải pháp eny không?
kikicarbonell

1
Điều này không còn hoạt động, ít nhất là không trong ActiveRecord 4.1.4. Nó cung cấp cho các lỗi sau:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Tamer Shlash

4

Sau gần như mọi giải pháp nói rằng "điều này hiệu quả với tôi trên cơ sở dữ liệu X", tôi thấy một nhận xét của người đăng ban đầu có hiệu lực là "không hiệu quả với tôi trên Postgres." Vấn đề thực sự ở đây trên thực tế có thể là sự hỗ trợ của Postgres trong Rails, điều này không hoàn hảo và có lẽ còn tệ hơn vào năm 2009 khi câu hỏi này được đăng ban đầu. Ví dụ, nếu tôi nhớ không lầm, nếu bạn đang ở trên Postgres, về cơ bản bạn không thể nhận được kết quả hữu ích từ đó rake db:schema:dump.

Bản thân tôi không phải là ninja Postgres, tôi nhận được thông tin này từ video PeepCode xuất sắc của Xavier Shay trên Postgres. Đoạn video đó thực sự bỏ qua thư viện của Aaron Patterson, tôi nghĩ là Texticle nhưng tôi có thể nhớ nhầm. Nhưng ngoài điều đó thì nó khá tuyệt.

Dù sao, nếu bạn đang gặp sự cố này trên Postgres, hãy xem các giải pháp có hoạt động trong các cơ sở dữ liệu khác không. Có thể sử dụng rails newđể tạo ứng dụng mới dưới dạng hộp cát hoặc chỉ tạo một cái gì đó như

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

trong config/database.yml.

Và nếu bạn có thể xác minh rằng đó là vấn đề hỗ trợ Postgres và bạn tìm ra cách khắc phục, vui lòng đóng góp các bản vá cho Rails hoặc đóng gói các bản sửa lỗi của bạn trong một viên ngọc, bởi vì cơ sở người dùng Postgres trong cộng đồng Rails là khá lớn, chủ yếu là nhờ Heroku .


2
Phản đối FUD và không chính xác. Tôi đã sử dụng Postgres trên hầu hết các dự án Rails của mình kể từ năm 2007. Hỗ trợ Postgres trong Rails rất tuyệt vời và không có vấn đề gì với trình kết xuất lược đồ.
Marnen Laibow-Koser

2
một mặt, đúng là Postgres không phải là vấn đề ở đây. mặt khác, đây là một lỗi trình kết xuất lược đồ chỉ pg từ rails.lighthororspp.com/projects/8994/tickets/2418 và đây là một lỗi khác rails.lighthoosystempp.com/projects/8994/tickets/2514
Giles Bowkett

Có một vấn đề quyết định với Rails và Postgres xung quanh bản cập nhật Postgres 8.3, nơi họ (chính xác là IMO) đã quyết định chuyển sang tiêu chuẩn SQL cho chuỗi ký tự và trích dẫn. Bộ điều hợp Rails không kiểm tra các phiên bản Postgres và phải được vá lại một thời gian.
Judson

@Judson Thật thú vị. Tôi chưa bao giờ gặp phải điều đó.
Marnen Laibow-Koser

4

Tôi đã tìm thấy một giải pháp cho vấn đề này hoạt động với Rails 3:

Tệp di chuyển:

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

Và trong mô hình worker.rb:

self.primary_key = :emp_id

3

Thủ thuật phù hợp với tôi trên Rails 3 và MySQL là:

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

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

Vì thế:

  1. sử dụng: id => false để không tạo khóa chính số nguyên
  2. sử dụng kiểu dữ liệu mong muốn và thêm: null => false
  3. thêm một chỉ mục duy nhất trên cột đó

Có vẻ như MySQL chuyển đổi chỉ mục duy nhất trên cột không rỗng thành khóa chính!


Trong Rails 3.22 với MySQL 5.5.15, nó chỉ tạo một khóa duy nhất chứ không tạo khóa chính.
lulalala

2

bạn phải sử dụng tùy chọ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

Điều này không hiệu quả với tôi, ít nhất là không có trong PostgreSQL. Không có khóa chính nào được chỉ định.
Rudd Zwolinski,

1
Cách tiếp cận này sẽ bỏ qua cột id nhưng khóa chính sẽ không thực sự được đặt trên bảng.
Sean McCleary

1

Làm thế nào về giải pháp này,

Bên trong mô hình Nhân viên tại sao chúng ta không thể thêm mã sẽ kiểm tra tính duy nhất trong coloumn, ví dụ: Giả sử Nhân viên là Mô hình trong đó bạn có EmpId là chuỗi sau đó chúng ta có thể thêm ": uniqueness => true" vào EmpId

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

Tôi không chắc rằng đây là giải pháp nhưng điều này đã làm việc cho tôi.


1

Tôi biết đây là một chủ đề cũ mà tôi tình cờ gặp phải ... nhưng tôi thực sự bị sốc khi không ai đề cập đến DataMapper.

Tôi thấy nếu bạn cần thoát ra khỏi quy ước ActiveRecord, tôi thấy rằng nó là một sự thay thế tuyệt vời. Nó cũng là một cách tiếp cận tốt hơn cho di sản và bạn có thể hỗ trợ cơ sở dữ liệu "nguyên trạng".

Ruby Object Mapper (DataMapper 2) có rất nhiều hứa hẹn và xây dựng dựa trên các nguyên tắc AREL!


1

Thêm chỉ mục hoạt động cho tôi, tôi đang sử dụng 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.