やりたいこと
- 通貨の交換を管理する際、交換モデルから通貨モデルを外部キー設定したい。
- 外部キーは買い通貨と売り通貨の2種類
- 通貨モデルは親通貨モデルCurrencyを継承したActiveRecordで実装
- つまり、参照側はされる側のスーパークラスを参照したい
参照する側(交換モデルの実装)
マイグレーション
CeateExchanges.rb
create_table :exchanges do |t|
t.references :buy_currency, foreign_key: { to_table: :currencies }
t.references :sell_currency, foreign_key: { to_table: :currencies }
end
- ポイントは実際のテーブル名と異なる属性名を使用している
- その際は
to_table
キーの値で実際のテーブル名を指定
モデル
Exchange.rb
class Exchange < ApplicationRecord
belongs_to :buy_currency, class_name: "Currency"
belongs_to :sell_currency, class_name: "Currency"
end
- ポイントは
belongs_to
には参照したい名称を指定 - 実際のクラス名は
class_name
キーの値で指定 -
belongs_to :currency_jpy
とか子通貨テーブルの名称を関連付けに設定してしまうと、exchangeテーブルにレコードを挿入できなくなります。 - なぜなら、実際には親通貨モデルを外部キーとして設定しているにも関わらず、子通貨モデルを関連付けることで、レコード挿入時には子通貨の値がないはずで、バリデーションで子通貨がblankだと怒られるからです
参照される側(親通貨モデルの実装)
マイグレーション
CeateCurrencies.rb
create_table :currencies do |t|
t.string :name, null: false
end
モデル
Currency.rb
class Currency < ApplicationRecord
self.table_name_prefix = "currency_"
has_many :exchanges
def buy(amounts)
self.amounts += amounts
end
def sell(amounts)
self.amounts -= amounts
end
end
- 本記事の主旨からズレますが、ダックタイピングを使ってます。総量を表すamountsは親通貨モデルには定義していません。親クラスから継承した子通貨モデル側で総量を定義していれば、問題なく動作します。
db/seeds.rb
Currency.create(name: "JPY")
Currency.create(name: "USD")
Currency.create(name: "BTC")
- このseedのように親クラスでは扱える通貨一覧を管理する目的でレコードを保有
- その心は
collection_select
フォームヘルパーを使用したいからです
参照される側(子通貨モデルの実装)
マイグレーション
CeateCurrencyJpy.rb
create_table :currency_jpy do |t|
t.float :amounts, null: false, default: 0
end
- モデルの生成で自動作成されるマイグレーションには複数形のテーブル名が付与されるので、通貨としては扱いづらいために書き換えてます
- 実用的にはユーザーモデルとの関連付けが必要だと思います
モデル
コンソール
$ rails g model Currency::Jpy
- この記事では通貨はcurrency名前空間の配下に作成して少し複雑なケースでまとめます
currency/jpy.rb
class Currency::Jpy < Currency
end
まとめ
Railsの基本的な使い方をしていると気付かないような動きを確認することができました。
アソシエーションを設定することで普段は気にしていない動作が走っていることなど、勉強になりました。
個人的にはモデルのnamespaceは動きが読めなくて使いづらく、おススメできないです。