Railsを使用して、主キーを整数型の列ではないように設定するにはどうすればよいですか?


85

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を完全に無視して先に進み、整数列にしたように見えることです。execute呼び出しでSQLを記述せずに、railsにPRIMARY_KEY制約(PostgreSQLを使用しています)を生成させる他の方法はありますか?

:文字列列を主キーとして使用するのは最善ではないことを私は知っているので、整数の主キーを追加するというだけの答えはありません。とにかく追加するかもしれませんが、この質問はまだ有効です。


私も同じ問題を抱えており、その答えを見てみたいと思います。これまでのところ、どの提案も機能していません。
Sean McCleary

1
Postgresを使用している場合、それらはいずれも機能しません。DanChakの「EnterpriseRails」には、ナチュラルキー/コンポジットキーを使用するためのヒントがいくつかあります。
azeem.Butt 2010

次のようなものを使用する場合は注意してください:<!-言語:lang-rb->「ALTERTABLEemployees ADD PRIMARYKEY(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 2012

18
後者は非推奨であるため、実際self.primary_key=にはset_primary_key、の代わりにを使用する必要があります。
ゲイリーS.ウィーバー

2
これが私のために働いた唯一の解決策です。私はそれが他の人に役立つことを願っています: stackoverflow.com/a/15297616/679628
findchris 2014年

8
このアプローチの問題は、データベースをセットアップschema.rb emp_idすると、integer再び型列になることです。どうやらschema.rb保存できないexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"ようです。
tintin81 2015年

4
@DonnyKurnia @ Tintin81スキーマダンプschema.rbstructure.sqlからconfig.active_record.schema_format設定を介して切り替えることができます。設定は、:sqlまたはのいずれか:rubyです。guides.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 2014年

移行するためのコードも指定する必要がありますか?または、Railsは移行時に何をすべきかを知るのに十分スマートですか?
amey1908 2014

2
ダウンマイグレーションの場合:drop_table :employees
オースティン

6
残念ながら、このメソッドschema.rbは同期されていないaも残します...:primary_key => :emp_id宣言は保持されますが、rake db:schema:loadが呼び出されると、テストの開始時のように、整数列が生成されます。ただし、structure.sql(configオプション)テストの使用に切り替えると、設定が保持され、そのファイルを使用してスキーマがロードされる場合があります。
トムハリソン

18

これを処理する方法が1つあります。実行されるSQLはANSISQLであるため、ほとんどのANSISQL準拠のリレーショナルデータベースで機能する可能性があります。これが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


9

Rails4.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、:文字列、テキスト、整数、:フロート、10進:日時:タイム:日付、バイナリ、ブール型パラメータは、通常、次のいずれかである移行ネイティブ型の一つであります。

私が示したように、私は上記のアイデアを得ました。この移行を実行した後のテーブルメタデータは次のとおりです。

[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主キーのタイプを反映していないため、を使用してデータベースを生成するときloadに、主キーが整数として作成されることです。
Peter Alfvin 2015年

9

Rails5では次のことができます

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

create_tableのドキュメントを参照してください。


before_create :set_id主キーの値を割り当てるには、モデルに何らかのメソッドを追加する必要があることに注意してください
FloatingRock

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がWidgetクラスの主キーになり、オブジェクトの作成時にフィールドに入力するのはユーザーの責任です。beforecreateコールバックを使用してこれを行うことができるはずです。

だからの線に沿って何か

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 2009

興味深いことに、MySQL on Rails 2.3.2でテストしました。おそらく、Railsのバージョンに関連している可能性がありますか?
paulthenerd 2009

よくわかりません。Railsのバージョンではないと思います。Rails2.3.3を使用しています。
Rudd Zwolinski 2009

残念ながら、このアプローチを使用すると、実際の主キーはテーブルに設定されません。
Sean McCleary

2
このアプローチは、少なくともRails4.2とPostgresqlで機能します。私はテストしましたが、答えを出すprimary_key: true代わりに使用する必要がありますprimary
Anwar

8

私はRails2.3.5を使用しており、次の方法はSQLite3で機能します

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

  # other column definitions
end

:id => falseの必要はありません。


申し訳ありませんが、テクニックを適用するとwidget_idが整数に変更されました... enyソリューションはありますか?
kikicarbonell 2013年

1
これは、少なくともActiveRecord4.1.4では機能しなくなりました。それは次のエラーを与える:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
テイマーShlash

4

「これはXデータベースでは機能しました」というほぼすべての解決策の後で、元の投稿者による「Postgresでは機能しなかった」というコメントが表示されます。ここでの本当の問題は、実際にはRailsでのPostgresのサポートである可能性がありますが、これは完璧ではなく、この質問が最初に投稿された2009年にはおそらく悪化していました。たとえば、私が正しく覚えていれば、Postgresを使用していると、基本的にから有用な出力を取得できませんrake db:schema:dump

私自身はPostgresの忍者ではありません。この情報は、Postgresに関するXavierShayの優れたPeepCodeビデオから入手しました。そのビデオは実際にはアーロン・パターソンの図書館を見落としています。テキスタイルだと思いますが、私は間違ったことを覚えている可能性があります。しかしそれ以外はかなり素晴らしいです。

とにかく、Postgresでこの問題が発生した場合は、ソリューションが他のデータベースで機能するかどうかを確認してください。rails newサンドボックスとして新しいアプリを生成するために使用するか、次のようなものを作成するために使用するかもしれません

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

config/database.yml

そして、それがPostgresサポートの問題であることを確認でき、修正がわかった場合は、Railsにパッチを提供するか、修正をgemにパッケージ化してください。Railsコミュニティ内のPostgresユーザーベースは、主にHerokuのおかげでかなり大きいためです。 。


2
FUDと不正確さに対する反対票。2007年以来、ほとんどのRailsプロジェクトでPostgresを使用しています。RailsでのPostgresのサポートは優れており、スキーマダンパーに問題はありません。
Marnen Laibow-Koser 2012

2
一方では、Postgresがここで問題ではなかったのは事実です。一方、2009年のrails.lighthouseapp.com/projects/8994/tickets/2418からのpgのみのスキーマダンパーのバグと、別の1つのrails.lighthouseapp.com/projects/8994/tickets/2514
Giles Bowkett

RailsとPostgresには、Postgres 8.3アップデートを取り巻く決定的な問題があり、文字列リテラルと引用符のSQL標準に切り替えることを決定しました(正しくはIMO)。RailsアダプタはPostgresのバージョンをチェックせず、しばらくの間モンキーパッチを適用する必要がありました。
ジャドソン2012年

@ジャドソン興味深い。私はそれに遭遇したことはありません。
Marnen Laibow-Koser 2012

4

Rails3で機能するこれに対する解決策を見つけました。

移行ファイル:

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

そしてemployee.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がnull以外の列の一意のインデックスを主キーに変換しているようです!


MySQL5.5.15を使用するRails3.22では、一意のキーのみが作成され、主キーは作成されません。
lulalala 2012年

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 2009

1
このアプローチではid列が省略されますが、主キーは実際にはテーブルに設定されません。
Sean McCleary

1

このソリューションはどうですか、

Employeeモデル内で、列の一意性をチェックするコードを追加できないのはなぜですか。例:EmployeeがModelであり、文字列であるEmpIdがあるとすると、EmpIdに「:uniqueness => true」を追加でき ます。

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

これが解決策かどうかはわかりませんが、これでうまくいきました。


1

これは私が偶然見つけた古いスレッドであることを知っています...しかし、DataMapperについて誰も言及していないことにちょっとショックを受けました。

ActiveRecordの慣習から逸脱する必要がある場合は、それが優れた代替手段であることがわかりました。また、レガシーに対するより優れたアプローチであり、データベースを「現状のまま」サポートできます。

Ruby Object Mapper(DataMapper 2)も多くの可能性を秘めており、ARELの原則に基づいています。


1

インデックスの追加は私にとってはうまくいきます、私はMySqlところで使用しています。

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.