Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
144
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@k-shogo

Railsで規約に沿わない古いデータを扱う

背景

railsではrails g scaffoldなどでModelを作成すれば、
自動的にidが付与されます。
しかもprimary_keyでauto_incrementでかつindexも張られるので、
普段はidを気にする必要はありません。

railsを使い、自分でデータ構造を決める場合はrailsの流儀に則った方が楽で、問題も起こりません。
しかし、古いデータを活用した場合、流儀にそぐわない事もあり得ます。

今回は規約に沿わない場合の対応について大きく分けて2つの場合について説明します。
なお環境は以下の物で検証しています。
* ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0]
* Rails 4.0.0
* composite_primary_keys (6.0.0)

主キーがidではない

主キーがidではない場合として、
具体的に商品(Item)テーブルの主キーが文字列である場合を考えます。

主キーがidで無い場合のポイントは
create_tabelid: falseです。
これによってidが自動生成されることはなくなります。
他のオプションでprimary_keyと言う物もありますが、
これはinteger型で名前をidから変更するだけの物です。

下記の例ではnull: falseとする事でnot nullな制約を追加し、
add_index :items, :code, unique: trueでユニーク制約を定義しています。

Migrationファイル

class CreateItems < ActiveRecord::Migration

  def change
    create_table :items, id: false do |t|
      # 商品コード
      t.string :code, limit: 8, null: false
      # 商品名
      t.string :name
    end
    add_index :items, :code, unique: true
  end

end

モデル側にはself.primary_keyで主キーを設定します。
これによってfindメソッドで主キーによる取得が可能になります。

Item Model

class Item < ActiveRecord::Base
  # 主キー設定
  self.primary_key = :code
end

複合主キーを扱いたい

次は複合主キーを扱う場合についてです。
具体的に先ほど使った商品(Item)について、
各商品に任意のタグ(Tag)がついているとします。
タグは商品コード(item_code)とタグコード(code)の複合主キーを持っています。

railsで複合主キーを扱う場合、
composite_primary_keysのgemを使用します。
composite_primary_keysの準備としてGemfilegem 'composite_primary_keys'を追加しbundle installします。

Migrationファイルのポイントは、
:item_code:codeの組み合わせがユニークである制約の定義です。
複合主キーがユニークである制約はadd_index :tags, [:item_code, :code], unique: trueだけでも十分です。
複合主キーが2つ3つと長くなる場合、インデックス名が長すぎてエラーが発生する事もあるので、
その場合はnameでインデックス名を指定してあげましょう。

Migrationファイル

class CreateTags < ActiveRecord::Migration
  def change
    create_table :tags, id: false do |t|
      # 商品コード
      t.string :item_code, limit: 8
      # タグコード
      t.string :code, limit: 8
      # タグ名称
      t.string :name
    end
    add_index :tags, [:item_code, :code], unique: true, name: 'composite_index'
  end
end

モデルに関しては、
先ほどはself.primary_keyを使いましたが、
複合主キーの場合はself.primary_keysを使います。
これによってTag.find 'item_code', 'code'と、
findメソッドで複合主キーを扱うことが可能になります。

また、foreign_keyによって、どのカラムが外部キーなのかを指定することで、
関連を定義することが可能です。

Tag Model

class Tag < ActiveRecord::Base
  # 主キー設定
  self.primary_keys = :item_code, :code

  # 商品との関連
  belongs_to :item, foreign_key: :item_code
end

Item Model

class Item < ActiveRecord::Base
  # 主キー設定
  self.primary_key = :code

  # タグとの関連
  has_many :tags, foreign_key: :item_code
end

問題

composite_primary_keysによって複合主キーを取り扱うことが出来るようになりました。
しかし、少し問題が発生する場合もあります。
それはModelに対してto_jsonを呼び出した場合などで、以下のエラーが発生します。

TypeError: ["item_code", "code"] is not a symbol

根本ではserializable_hashを呼び出すところでのエラーなので、
モデルでserializable_hashをオーバーライドする事で解決します。

def serializable_hash(options={})
  options = {
    :only => [:item_code, :code, :name_code]
  }.update(options)
  super(options)
end
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
144
Help us understand the problem. What are the problem?