よくわかる基本的な 1 : n のリレーションシップスクラークか
プロにめっちゃ丁寧に教えてもらったので、改めて復習。
ルーティングについて
基本的に、generate scaffold した場合、routes.rbではこんな感じでルーティングされる。
[ routes.rb ]
resources :artists resources :musics
[ path ]
# /artists/1 # /musics/1
複数テーブルのリレーショナルデータベースを作るのであれば、まずは1:nの形を考えるべき。
"artist" と "music" のテーブルで考えた場合、ユーザの情報追加する動作を考えると、
「アーティストを入れる」→「そのアーティストの曲を入れる」
というのがまず、シンプルに考えられるフロー。
そして、まずは1人のアーティストが複数の曲を歌っている、と考え
artist : music = 1 : n
とする。
(itunesとか見ても、artistが複数紐付いてるのってないよね。feat.とかで1つにまとめてる)
このとき、musicはartistの入れ子になっているので、resourcesはネストすることで、分かりやすい階層構造とすることができる。
[ routes.rb ]
resources :artists do resources :musics end
[ path ]
# /artists/1 # /artists/1/musics/new
artistsからmusicsへのアクセスが分かりやすくなるし、関係が分かりやすい。
モデルについて
artist : music = 1 : n
であることから、artists側からmusicテーブルへアクセスするために、has_many メソッドによって関連付け(アソシエーション)する。
また、アソシエーション先のモデルも一緒に更新するためには、
accepts_nasted_attributes_for
をくっつける必要があるみたい。
ってもmusicコントローラに飛ばしてるからいらないと思うけど、一応いれとく。(参考)
[ artist.rb ]
class Artist < ActiveRecord::Base ... has_many :musics accepts_nasted_attributes_for :artists ... end
※musics側からartistsテーブルへアクセスするには、belongs_toメソッドを使えばよいが、
まだそこはやらないのでカッツ・アイ。
コントローラについて
artistsのパラメータをうまくmusicsへ渡すビューをつくるために、コントローラを整理しましょう。
[ musics_controller.rb ]
class MusicsController ... # Artistsからネストされたmusicsで登録することにするので、 # ユーザのGETリクエストは以下のような形でくるようにビューを設定すべし # GET /artists/xx/musics/yy # このとき、musicsはartistの子になっているので、パラメータは以下で取得 # => params = { artist_id: xx, id: yy } def show @artist = Artist.find(params[:artist_id]) #Artistインスタンス変数 @music = @artist.musics.find(params[:id]) #Musicインスタンス変数(大文字にならない) end # POST /artists/xx/musics/yy def create @artist = Artist.find(params[:artist_id]) #対象のArtistをセット @music = @artist.musics.new(music_params) #newメソッドでレコードを新規作成 if @music.save # 成功したときの操作 else # 失敗したときの操作 end end end
※モデルと同じく、まずはmusics -> artistsへはアクセスしないようにするので、Artistsコントローラはカッツ・アイ。
ビューについて
scaffoldで作成したnew.html.erb、及びedit.html.erbは部分テンプレートを利用しており、実際のフォームは_form.html.erbにある。
artistsテーブルに対するフォームでは、そのままだとmusicsテーブルへ一緒に書き込む方法がない。(というのに気付いたのが前回)
方法としては、
- musics/newなどに飛ばす
- artists/newに直接記載する
同時に更新、という点では2だが、別に同時にする必要はないし、変にフォームをいじるよりも、既にあるmusicsのフォームに飛ばす方がいろいろとスマートだと思うので、1の方法で実施する。
あと、プロはhamlを使ってるけど、私はerbを使ってるのでそのへんはうまくやる。
まずはartistのページから音楽を追加する方法。
[ views/artists/show.html.haml ]
...
# リンクを張る
# オプション属性が "artist_id" だけど、この場所がartistsなので普通に "id" だけでいいかも?
# …と思ったけど、ルートが通らなくなった。
# たぶんmusicsコントローラのidと認識されて辻褄合わなくなってしまうっぽい。
# => GET /artists/#{@artist.id}/musics/new
= link_to '音楽を追加', (controller: :musics, action: :new, artist_id: @artist.id)
で、音楽を追加するときのビュー。
[ views/musics/new.html.haml ]
...
# => POST /artists/1/musics
= form_for @music do
#追加する
end
# フォームの内容を格納
- @artist.musics.each do |music|
= music.title
(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙(。・ω・)ノ゙
整理おわり。ふぅ。
ちなみに、m:nをやろうとした場合、例えばplaylistを例にすると、以下のような形になる。
# "Music x PlayList" で構築 PlayList[1] = musics: [ music[3], music[1] ], name: "ドライブの曲" PlayList[2] = musics: [ music[1], music[2] ], name: "朝の曲"
class PlayListEntry #中間テーブル belongs_to :playlist belongs_to :music # order(integer) end
# GET /playlists/1/playlist_entries/0/music => music[3] # GET /playlists/1/playlist_entries/1/music => music[1] # GET /playlists/1/playlist_entries => { name: 'ドライブの曲' } ...
という感じで、パス構成がかなーり複雑になる。
ので、できるだけ1:nまで分解しましょう、ということでした。