เมื่อใช้ Rails ฉันจะตั้งค่าคีย์หลักไม่ให้เป็นคอลัมน์ที่พิมพ์จำนวนเต็มได้อย่างไร


86

ฉันใช้การโอนย้าย Rails เพื่อจัดการสคีมาฐานข้อมูลและฉันกำลังสร้างตารางง่ายๆที่ฉันต้องการใช้ค่าที่ไม่ใช่จำนวนเต็มเป็นคีย์หลัก (โดยเฉพาะสตริง) ห่างนามธรรมจากปัญหาของฉันสมมติว่ามีตารางที่พนักงานมีการระบุโดยสตริงและตัวเลขเช่นemployees"134SNW"

ฉันได้ลองสร้างตารางในการย้ายข้อมูลดังนี้:

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

สิ่งนี้ให้ฉันคือสิ่งที่ดูเหมือนว่ามันไม่สนใจบรรทัดทั้งหมดt.string :emp_idและดำเนินการต่อและทำให้เป็นคอลัมน์จำนวนเต็ม มีวิธีอื่นอีกไหมที่จะทำให้รางสร้างข้อ จำกัด PRIMARY_KEY (ฉันใช้ PostgreSQL) ให้ฉันโดยไม่ต้องเขียน SQL ในการexecuteโทร?

หมายเหตุ : ฉันรู้ว่าไม่ควรใช้คอลัมน์สตริงเป็นคีย์หลักดังนั้นโปรดอย่าตอบเพียงแค่บอกว่าให้เพิ่มคีย์หลักจำนวนเต็ม ฉันอาจเพิ่มได้ แต่คำถามนี้ยังใช้ได้


ฉันมีปัญหาเดียวกันและฉันชอบที่จะเห็นคำตอบสำหรับเรื่องนี้ ยังไม่มีข้อเสนอแนะใด ๆ ที่ได้ผล
Sean McCleary

1
ไม่มีสิ่งใดที่จะใช้งานได้หากคุณใช้ Postgres "Enterprise Rails" ของ Dan Chak มีเคล็ดลับในการใช้คีย์ธรรมชาติ / คอมโพสิต
Azeem

ระวังว่าเมื่อคุณใช้สิ่งต่างๆเช่น: <! - language: lang-rb -> ดำเนินการ "เปลี่ยนพนักงานตารางเพิ่มคีย์หลัก (emp_id);" แม้ว่าข้อ จำกัด ของตารางจะถูกตั้งค่าอย่างถูกต้องหลังจากรันrake db:migrateคำจำกัดความของสคีมาที่สร้างขึ้นโดยอัตโนมัติไม่มีข้อ จำกัด นี้!
pymkin

คำตอบ:


107

executeน่าเสียดายที่ผมได้พิจารณาแล้วว่าเป็นไปไม่ได้ที่จะทำมันโดยไม่ต้องใช้

ทำไมมันไม่ทำงาน

โดยการตรวจสอบแหล่งที่มา ActiveRecord เราสามารถค้นหารหัสสำหรับcreate_table:

ใน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

ดังนั้นเราจะเห็นว่าเมื่อคุณพยายามระบุคีย์หลักในcreate_tableอ็อพชันคีย์นั้นจะสร้างคีย์หลักที่มีชื่อที่ระบุนั้น (หรือหากไม่มีการระบุid) primary_keyมันเป็นเช่นนี้โดยเรียกวิธีการเดียวกับที่คุณสามารถใช้ภายในบล็อกตารางนิยาม:

ในschema_statements.rb:

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

นี่เป็นการสร้างคอลัมน์ที่มีชื่อประเภทที่:primary_keyระบุ ค่านี้ถูกตั้งค่าดังต่อไปนี้ในอะแด็ปเตอร์ฐานข้อมูลมาตรฐาน:

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

วิธีแก้ปัญหา

เนื่องจากเราติดอยู่กับสิ่งเหล่านี้เป็นประเภทคีย์หลักเราจึงต้องใช้executeเพื่อสร้างคีย์หลักที่ไม่ใช่จำนวนเต็ม (PostgreSQL serialเป็นจำนวนเต็มโดยใช้ลำดับ):

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

และดังที่Sean McCleary กล่าวถึงโมเดล ActiveRecord ของคุณควรตั้งค่าคีย์หลักโดยใช้set_primary_key:

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

15
วิธีแก้ปัญหาจะทำให้ rake db: test: clone หรือ rake db: schema: load เพื่อสร้าง emp_id เป็นคอลัมน์จำนวนเต็ม
Donny Kurnia

18
จริงๆแล้วตอนนี้คุณควรใช้self.primary_key=แทนset_primary_keyเนื่องจากตัวหลังเลิกใช้งานแล้ว
Gary S. Weaver

2
นี่คือทางออกเดียวที่ใช้ได้ผลสำหรับฉัน ฉันหวังว่ามันจะช่วยคนอื่นได้: stackoverflow.com/a/15297616/679628
findchris

8
ปัญหาของแนวทางนี้คือเมื่อคุณตั้งค่าฐานข้อมูลจากschema.rb emp_idจะเป็นintegerคอลัมน์ประเภทอีกครั้ง ดูเหมือนว่าschema.rbไม่สามารถจัดเก็บได้execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"แต่อย่างใด
ติ

4
@DonnyKurnia @ Tintin81 คุณสามารถเปลี่ยนการถ่ายโอนข้อมูลสคีมาจากschema.rbเป็นstructure.sqlผ่านconfig.active_record.schema_formatการตั้งค่าซึ่งอาจเป็น:sqlหรือ:ruby. guide.rubyonrails.org/v3.2.13/…
Svilen Ivanov

21

ใช้งานได้:

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

มันอาจจะไม่สวย แต่ผลลัพธ์ที่ได้คือสิ่งที่คุณต้องการ


1
ในที่สุด! นี่เป็นทางออกเดียวที่ใช้ได้ผลสำหรับฉัน
findchris

เราต้องระบุรหัสสำหรับการย้ายข้อมูลลงด้วยหรือไม่? หรือ Rails ฉลาดพอที่จะรู้ว่าต้องทำอย่างไรเมื่อย้ายข้อมูลลง
amey1908

2
สำหรับการโยกย้ายลง:drop_table :employees
Austin

6
น่าเสียดายที่วิธีนี้ยังทำให้เรามี a schema.rbที่ไม่ตรงกัน ... มันจะยังคงการ:primary_key => :emp_idประกาศไว้ แต่เมื่อrake db:schema:loadมีการเรียกใช้เนื่องจากเป็นช่วงเริ่มต้นของการทดสอบจะสร้างคอลัมน์จำนวนเต็ม อย่างไรก็ตามอาจเป็นกรณีที่หากคุณเปลี่ยนไปใช้structure.sqlการทดสอบ (config option) จะคงการตั้งค่าไว้และใช้ไฟล์นั้นเพื่อโหลดสคีมา
Tom Harrison

18

ฉันมีวิธีหนึ่งในการจัดการสิ่งนี้ SQL ที่เรียกใช้งานคือ ANSI SQL ดังนั้นจึงน่าจะทำงานบนฐานข้อมูลเชิงสัมพันธ์ที่สอดคล้องกับ ANSI SQL ส่วนใหญ่ ฉันทดสอบแล้วว่าใช้ได้กับ MySQL

การโยกย้าย:

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

ในโมเดลของคุณให้ทำสิ่งนี้:

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


10

ใน Rails 5 คุณสามารถทำได้

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

ดูเอกสาร create_table


เป็นที่น่าสังเกตว่าคุณต้องเพิ่มbefore_create :set_idวิธีการบางอย่างในแบบจำลองเพื่อกำหนดค่าของคีย์หลัก
FloatingRock

9

ฉันได้ลองใช้ใน Rails 4.2 หากต้องการเพิ่มคีย์หลักที่กำหนดเองคุณสามารถเขียนการย้ายข้อมูลเป็น:

# 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

ขณะดูเอกสารcolumn(name, type, options = {})และอ่านบรรทัด:

โดยtypeปกติพารามิเตอร์เป็นหนึ่งในประเภทเนทีฟของการโอนย้ายซึ่งเป็นหนึ่งในประเภทต่อไปนี้: primary_key,: string,: text,: integer,: float,: decimal,: datetime,: time,: date,: binary,: boolean .

ฉันได้รับปัจจัยข้างต้นตามที่ได้แสดงไว้ นี่คือข้อมูลเมตาของตารางหลังจากเรียกใช้การย้ายข้อมูลนี้:

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

และจากคอนโซล Rails:

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

3
ปัญหาเกิดขึ้นคือschema.rbไฟล์ที่สร้างขึ้นไม่ได้แสดงถึงstringประเภทของคีย์หลักดังนั้นเมื่อสร้างฐานข้อมูลด้วย a loadคีย์หลักจะถูกสร้างเป็นจำนวนเต็ม
Peter Alfvin

8

ดูเหมือนว่าจะทำได้โดยใช้แนวทางนี้:

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

ซึ่งจะทำให้คอลัมน์ widget_id เป็นคีย์หลักสำหรับคลาสวิดเจ็ตจากนั้นคุณจะต้องเติมข้อมูลในฟิลด์เมื่อวัตถุถูกสร้างขึ้น คุณควรจะทำได้โดยใช้ before create callback

ดังนั้นบางสิ่งบางอย่างตามแนวของ

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

สิ่งนี้ไม่ได้ผลสำหรับฉันอย่างน้อยก็ไม่ใช่ใน PostgreSQL ไม่มีการระบุคีย์หลักเลย
Rudd Zwolinski

อืมน่าสนใจฉันได้ทดสอบกับ MySQL บน Rails 2.3.2 - อาจเกี่ยวข้องกับเวอร์ชันของรางด้วยหรือไม่?
paulthenerd

ฉันไม่แน่ใจ - ฉันไม่คิดว่ามันเป็นเวอร์ชันของ Rails ฉันใช้ Rails 2.3.3
Rudd Zwolinski

ขออภัยไม่ได้ตั้งค่าคีย์หลักจริงสำหรับตารางโดยใช้วิธีนี้
Sean McCleary

2
วิธีนี้ใช้ได้กับ Rails 4.2 และ Postgresql เป็นอย่างน้อย ฉันได้ทดสอบแล้ว แต่คุณต้องใช้primary_key: trueแทนการprimaryให้คำตอบ
อันวาร์

8

ฉันใช้ Rails 2.3.5 และวิธีต่อไปนี้ใช้ได้กับ SQLite3

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

  # other column definitions
end

ไม่จำเป็นสำหรับ: id => false


ขออภัย แต่ widget_id เปลี่ยนเป็นจำนวนเต็มเมื่อฉันใช้ tecnique ของคุณ ... คุณมีโซลูชัน eny หรือไม่?
enrique-carbonell

1
สิ่งนี้ใช้ไม่ได้อีกต่อไปอย่างน้อยก็ไม่ใช่ใน ActiveRecord 4.1.4 มันทำให้เกิดข้อผิดพลาดดังต่อไปนี้:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
Tamer Shlash

4

หลังจากเกือบทุกวิธีแก้ปัญหาที่ระบุว่า "สิ่งนี้ใช้ได้ผลกับฉันบนฐานข้อมูล X" ฉันเห็นความคิดเห็นจากผู้โพสต์ต้นฉบับบอกว่า "ไม่ได้ผลสำหรับฉันใน Postgres" ปัญหาที่แท้จริงที่นี่ในความเป็นจริงอาจเป็นการสนับสนุน Postgres ใน Rails ซึ่งไม่ได้ไร้ที่ติและอาจแย่ลงในปี 2009 เมื่อคำถามนี้โพสต์ครั้งแรก ตัวอย่างเช่นถ้าฉันจำไม่ผิดถ้าคุณอยู่ใน Postgres คุณจะไม่สามารถรับผลลัพธ์ที่เป็นประโยชน์rake db:schema:dumpได้

ฉันไม่ใช่นินจา Postgres ด้วยตัวเองฉันได้รับข้อมูลนี้จากวิดีโอ PeepCode ที่ยอดเยี่ยมของ Xavier Shay ใน Postgres วิดีโอนั้นมองเห็นห้องสมุดของ Aaron Patterson ฉันคิดว่า Texticle แต่ฉันจำผิด แต่นอกเหนือจากนั้นมันเยี่ยมมาก

อย่างไรก็ตามหากคุณพบปัญหานี้ใน Postgres ให้ดูว่าวิธีแก้ปัญหาทำงานในฐานข้อมูลอื่นหรือไม่ อาจใช้rails newเพื่อสร้างแอปใหม่เป็นแซนด์บ็อกซ์หรือสร้างสิ่งที่ต้องการ

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

ในconfig/database.yml.

และหากคุณสามารถตรวจสอบได้ว่าเป็นปัญหาการสนับสนุนของ Postgres และคุณคิดหาวิธีแก้ไขได้โปรดส่งแพตช์ไปยัง Rails หรือแพ็กเกจการแก้ไขของคุณในอัญมณีเนื่องจากฐานผู้ใช้ Postgres ในชุมชน Rails นั้นค่อนข้างใหญ่โดยส่วนใหญ่ต้องขอบคุณ Heroku .


2
การลงคะแนนสำหรับ FUD และความไม่ถูกต้อง ฉันใช้ Postgres กับโครงการ Rails ส่วนใหญ่ตั้งแต่ปี 2550 การสนับสนุน Postgres ใน Rails นั้นยอดเยี่ยมมากและไม่มีปัญหากับรถเทสคีมา
Marnen Laibow-Koser

2
ในแง่หนึ่งเป็นความจริงที่ว่า Postgres ไม่ใช่ปัญหาที่นี่ ในทางกลับกันนี่คือข้อผิดพลาดของ schema dumper เท่านั้นจาก 2009 rails.lighthouseapp.com/projects/8994/tickets/2418และนี่คืออีกหนึ่งrails.lighthouseapp.com/projects/8994/tickets/2514
Giles Bowkett

มีปัญหาในการตัดสินใจเกี่ยวกับ Rails และ Postgres โดยรอบการอัปเดต Postgres 8.3 ซึ่งพวกเขา (อย่างถูกต้อง IMO) ตัดสินใจที่จะเปลี่ยนเป็นมาตรฐาน SQL สำหรับตัวอักษรสตริงและการอ้างอิง อะแดปเตอร์ Rails ไม่ได้ตรวจสอบเวอร์ชันของ Postgres และต้องมีการแพตช์ลิงสักพัก
Judson

@Judson น่าสนใจ ฉันไม่เคยเจอสิ่งนั้น
Marnen Laibow-Koser

4

ฉันพบวิธีแก้ปัญหานี้ที่ใช้ได้กับ Rails 3:

ไฟล์การโยกย้าย:

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

และในแบบจำลอง Employ.rb:

self.primary_key = :emp_id

3

เคล็ดลับที่ใช้ได้ผลกับฉันใน Rails 3 และ MySQL คือ:

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

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

ดังนั้น:

  1. ใช้: id => false เพื่อไม่ให้สร้างคีย์หลักจำนวนเต็ม
  2. ใช้ประเภทข้อมูลที่ต้องการและเพิ่ม: null => false
  3. เพิ่มดัชนีเฉพาะในคอลัมน์นั้น

ดูเหมือนว่า MySQL จะแปลงดัชนีเฉพาะในคอลัมน์ที่ไม่ใช่ศูนย์เป็นคีย์หลัก!


ใน Rails 3.22 พร้อม MySQL 5.5.15 จะสร้างคีย์เฉพาะเท่านั้น แต่ไม่ใช่คีย์หลัก
lulalala

2

คุณต้องใช้ตัวเลือก: 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

สิ่งนี้ไม่ได้ผลสำหรับฉันอย่างน้อยก็ไม่ใช่ใน PostgreSQL ไม่มีการระบุคีย์หลักเลย
Rudd Zwolinski

1
วิธีนี้จะละเว้นคอลัมน์ id แต่คีย์หลักจะไม่ถูกตั้งค่าบนตาราง
Sean McCleary

1

วิธีแก้ปัญหานี้

Inside Employee model ทำไมเราไม่สามารถเพิ่มโค้ดที่จะตรวจสอบความเป็นเอกลักษณ์ใน coloumn ได้เช่นสมมติว่า Employee เป็น Model ที่คุณมี EmpId ซึ่งเป็นสตริงดังนั้นเราจึงสามารถเพิ่ม ": uniqueness => true"ให้กับ EmpId

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

ฉันไม่แน่ใจว่านี่เป็นวิธีแก้ปัญหา แต่มันใช้ได้ผลสำหรับฉัน


1

ฉันรู้ว่านี่เป็นกระทู้เก่าที่ฉันเจอ ... แต่ฉันตกใจมากที่ไม่มีใครพูดถึง DataMapper

ฉันพบว่าคุณจำเป็นต้องหลุดออกจากการประชุม ActiveRecord หรือไม่ฉันพบว่ามันเป็นทางเลือกที่ดี นอกจากนี้ยังเป็นแนวทางที่ดีกว่าสำหรับระบบเดิมและคุณสามารถรองรับฐานข้อมูล "ตามสภาพ" ได้

Ruby Object Mapper (DataMapper 2) มีคำมั่นสัญญามากมายและสร้างตามหลักการ AREL ด้วย!


1

การเพิ่มดัชนีใช้ได้ผลสำหรับฉันฉันใช้ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.