■環境
- macOS 10.13
- Rails 5.1
- Ruby 2.4
- mysql 5.7
■やりたかったこと
「accepts_nested_attributes_for」と、「allow_destroy: true」
で、以下のような事例を、うまいこと解決したい!
■目標
商品を製造するときに、使用している原材料を複数の種類で登録したい。
- 部位によって、登録する原材料が異なるため、分類して登録しなければならない。
- 商品によっては、登録する分類と登録しない分類がある。
サンプル
▼商品
- 商品1:みたらし団子
- 商品2:こしあん団子
- 商品3:ごまあん団子
▼原材料
- 材料1:白玉粉A
- 材料2:白玉粉B
- 材料3:砂糖
- 材料4:あずき
- 材料5:ごま
- 材料6:しょうゆ
- 材料7:しお
▼登録する分類
- お団子の主材料(分類:10)
- あん用の主材料(分類:20)
- あん用の副材料(分類:30)
*** ## ■商品と原材料の組み合わせ例
「みたらし団子」のとき
- お団子の主材料
- 白玉粉A
- あん用の主材料
- しょうゆ
- 砂糖
- あん用の副材料
- しお
「こしあん団子」のとき
- お団子の主材料
- 白玉粉B
- あん用の主材料
- あずき
- 砂糖
- あん用の副材料
- しお
「ごまあん団子」のとき
- お団子の主材料
- 白玉粉B
- あん用の主材料
- ごま
- あん用の副材料
- 砂糖
- しょうゆ
***
■用意したテーブルの構造(ver.1.0)
[ products ] 商品マスタ
- id
- name(商品名)
[ product_classifications ] 原材料マスタ
- id
- name(原材料名)
[ main_product_classification_products ] お団子主材料の中間テーブル
- id
- product_id(商品テーブルのid)
- product_classification_id(原材料テーブルのid)
[ sub_product_classification_products ] あん用主材料の中間テーブル
- id
- product_id(商品テーブルのid)
- product_classification_id(原材料テーブルのid)
[ other_product_classification_products ] あん用副材料の中間テーブル
- id
- product_id(商品テーブルのid)
- product_classification_id(原材料テーブルのid)
※便宜上、main、sub、otherと付けています。
#### ここまでは、ほぼ、レイルに乗せた標準の作りかなー、と思いながら....
■ふと、コードを組んでいて思ったこと
商品と原材料をつなぐ分類ごとの中間テーブルを作ったところまでは、よかったのですが、、、
「登録する分類ごとに中間テーブルを用意するのは、どうなんだろう?」
「登録する分類ごとに、ループで回してnewしたり、buildするのは、ソースが煩雑になりそうだなぁ。。。」
「なんとか1個の中間テーブルに分類を設けて、綺麗にソースを書けないかなぁ?」
というのが、事の始まりでした。
まぁ、、、要は、楽できて綺麗にできたらいいな。と(笑)
■用意したテーブルの構造(ver.2.0)
[ products ] 商品マスタ
- id
- name(商品名)
[ product_classifications ] 原材料マスタ
- id
- name(原材料名)
[ product_classification_products ] 商品と原材料の中間テーブル
- id
- product_id(商品テーブルのid)
- product_classification_id(原材料テーブルのid)
- specify(分類明細)
Modelの作りの見直し
原材料マスタmodel
class ProductClassification < ActiveRecord::Base
belongs_to :product
end
中間テーブルmodel
class ProductClassificationProduct < ActiveRecord::Base
belongs_to :product
belongs_to :product_classification
end
main用の中間テーブルmodelをextendしたmodelを作る
class MainStuff < ProductClassificationProduct
end
sub用の中間テーブルmodelをextendしたmodelを作る
class SubStuff < ProductClassificationProduct
end
other用の中間テーブルmodelをextendしたmodelを作る
class OtherStuff < ProductClassificationProduct
end
拡張modelを使用して、商品マスタのhas_manyとaccepts_nested_attributes_forを設定する
商品マスタmodel
class Product < ActiveRecord::Base
has_many :main_stuffs, -> { where(specify: 10) }
has_many :main_product_classifications, through: :main_stuffs, source: :product_classification
accepts_nested_attributes_for :main_stuffs, allow_destroy: true
has_many :sub_stuffs, -> { where(specify: 20) }
has_many :sub_product_classifications, through: :sub_stuffs, source: :product_classification
accepts_nested_attributes_for :sub_stuffs, allow_destroy: true
has_many :other_stuffs, -> { where(specify: 30) }
has_many :other_stuff_product_classifications, through: :other_stuffs, source: :product_classification
accepts_nested_attributes_for :other_stuffs, allow_destroy: true
end
けつまづいたこと
「specify」をどうやって、設定するの?
「accepts_nested_attributes_for」まかせにしたから、newやbuildみたいに設定するソースコードがない!
....うーん
....うーん、うーん
....うーん、うーん、うーん
「ひょっとして...before_saveなら、accepts_nested_attributes_forでも、走るんじゃない?」
ということで、、、
##### 中間テーブルの各extendしたmodelを以下のようにしてみました。
class MainStuff < ProductClassificationProduct
before_save :set_specify
def set_specify
self.specify = 10
end
end
class SubStuff < ProductClassificationProduct
before_save :set_specify
def set_specify
self.specify = 20
end
end
class OtherStuff < ProductClassificationProduct
before_save :set_specify
def set_specify
self.specify = 30
end
end