ドグサレ初心者のへっぽこビッグウェーブ

地球の底辺にいるゴミがプログラミングとか音楽とかを語るクソブログ

よくわかる基本的な 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メソッドを使えばよいが、
まだそこはやらないのでカッツ・アイ。
https://pbs.twimg.com/media/CHyaWZ9UsAAI_ez.jpg

コントローラについて

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コントローラはカッツ・アイ。
http://pbs.twimg.com/media/CBlwshVUAAA2zXF.jpg

ビューについて

scaffoldで作成したnew.html.erb、及びedit.html.erbは部分テンプレートを利用しており、実際のフォームは_form.html.erbにある。
artistsテーブルに対するフォームでは、そのままだとmusicsテーブルへ一緒に書き込む方法がない。(というのに気付いたのが前回)

方法としては、

  1. musics/newなどに飛ばす
  2. 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まで分解しましょう、ということでした。