web-dev-qa-db-ja.com

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

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を使用しています)を生成する)を使用する他の方法はありますか?

[〜#〜] note [〜#〜]:文字列列を主キーとして使用するのが最善ではないことを知っているので、整数の主キーを追加するだけで答えないでください。とにかく追加できますが、この質問はまだ有効です。

83
Rudd Zwolinski

残念ながら、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
107
Rudd Zwolinski

これは動作します:

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

それはきれいではないかもしれませんが、最終結果はまさにあなたが望むものです。

21
Austin

これを処理する方法が1つあります。実行される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
18
Sean McCleary

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パラメーターは、通常、次のいずれかの移行ネイティブ型の1つです。: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 console:

_Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "Apple_id"
>>
_
9
Arup Rakshit

このアプローチを使用して実行できるように見えます:

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クラスの主キーになり、オブジェクトの作成時にフィールドを設定するのはユーザー次第です。 before createコールバックを使用してそうすることができるはずです。

だからの線に沿って何か

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
8
paulthenerd

私はRails 2.3.5であり、私の次の方法はSQLite3で動作します

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

  # other column definitions
end

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

8
Trung LE

Rails 5でできること

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

create_table documentation を参照してください。

8
Pavel Chuchuva

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

私はPostgresの忍者ではありません。この情報は、Xavier ShayのPostgresの優れたPeepCodeビデオから得ました。そのビデオは実際にアーロン・パターソンのライブラリを見下ろしていると、Texticleが思うが、間違ったことを覚えているかもしれない。しかし、それ以外はかなり素晴らしいです。

とにかく、もしあなたがPostgresでこの問題に直面しているなら、ソリューションが他のデータベースで動作するかどうかを確認してください。 Rails newを使用して、サンドボックスとして新しいアプリを生成するか、次のようなものを作成します。

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

config/database.ymlで。

そして、それがPostgresサポートの問題であることを確認でき、修正を見つけたら、Railsにパッチを提供するか、修正をgemにパッケージ化してください。 Railsコミュニティは非常に大きく、主にHerokuのおかげです。

4
Giles Bowkett

Rails 3:

移行ファイル:

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
4
shicholas

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

3
Clark

オプション: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
2

インデックスを追加するとうまくいきます。MySqlbtwを使用しています。

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
1
Guster

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

従業員モデルの内部では、なぜ列の一意性をチェックするコードを追加できないのか、例:従業員がモデルであると仮定し、EmpIdが文字列であるため、そのために追加できる":uniqueness => true" to EmpId

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

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

1
Vijay Sali

これは私が偶然見つけた古いスレッドであることを知っています...

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

Ruby Object Mapper (DataMapper 2)は多くの約束を保持し、ARELの原則にも基づいています!

1
engineerDave