82937


# 前提

以下のような多対多のテーブルを有識者の方のアドバイスをもとに作成しました。  
（[前回質問時](https://ja.stackoverflow.com/q/82737) と若干(PKが)変わっていて申し訳ないです）

[![多対多](https://i.stack.imgur.com/CnZ3m.png)](https://i.stack.imgur.com/CnZ3m.png)

しかし、私の知識が浅く、コントローラの作り方が分からなくなってしまいました。

フレームワークは Rails 6 系、DBは PostgreSQL 13 系です。

# モデルの状態

[code]

    # app/models/song.rb
    class Song < ApplicationRecord
      has_many :composer_songs, dependent: :destroy
      has_many :composer_song_artists, through: :composer_songs
    
      has_many :lyricist_songs, dependent: :destroy
      has_many :lyricist_song_artists, through: :lyricist_songs
    
      has_many :arranger_songs, dependent: :destroy
      has_many :arranger_song_artists, through: :arranger_songs
    end
    
    # app/models/artist.rb
    class Artist < ApplicationRecord
      has_many :composer_songs, dependent: :destroy
      has_many :composer_song_songs, through: :composer_songs
    
      has_many :lyricist_songs, dependent: :destroy
      has_many :lyricist_song_songs, through: :lyricist_songs
    
      has_many :arranger_songs, dependent: :destroy
      has_many :arranger_songs_song, through: :arranger_songs
    end
    
    # app/models/lyricist_song.rb
    class LyricistSong < ApplicationRecord
      belongs_to :lyricist, class_name: "Artist", foreign_key: "artist_id"
      belongs_to :song
    end
    
    # app/models/composer_song.rb
    class ComposerSong < ApplicationRecord
      belongs_to :composer, class_name: "Artist", foreign_key: "artist_id"
      belongs_to :song
    end
    
    # app/models/arranger_song.rb
    class ArrangerSong < ApplicationRecord
      belongs_to :arranger, class_name: "Artist", foreign_key: "artist_id"
      belongs_to :song
    end
    
[/code]

# スキーマの状態

[code]

    # db/schema.b
    
    ActiveRecord::Schema.define(version: 2021_09_29_115047) do
    
      # These are extensions that must be enabled in order to support this database
      enable_extension "plpgsql"
    
      create_table "arranger_songs", force: :cascade do |t|
        t.bigint "arranger_id"
        t.bigint "song_id"
        t.index ["arranger_id"], name: "index_arranger_songs_on_arranger_id"
        t.index ["song_id"], name: "index_arranger_songs_on_song_id"
      end
    
      create_table "artists", force: :cascade do |t|
        t.string "name"
        t.datetime "created_at", precision: 6, null: false
        t.datetime "updated_at", precision: 6, null: false
      end
    
      create_table "composer_songs", force: :cascade do |t|
        t.bigint "composer_id"
        t.bigint "song_id"
        t.index ["composer_id"], name: "index_composer_songs_on_composer_id"
        t.index ["song_id"], name: "index_composer_songs_on_song_id"
      end
    
      create_table "lyricist_songs", force: :cascade do |t|
        t.bigint "lyricist_id"
        t.bigint "song_id"
        t.index ["lyricist_id"], name: "index_lyricist_songs_on_lyricist_id"
        t.index ["song_id"], name: "index_lyricist_songs_on_song_id"
      end
    
      create_table "songs", force: :cascade do |t|
        t.string "name"
        t.datetime "created_at", precision: 6, null: false
        t.datetime "updated_at", precision: 6, null: false
      end
    
      add_foreign_key "arranger_songs", "artists", column: "arranger_id"
      add_foreign_key "arranger_songs", "songs"
      add_foreign_key "composer_songs", "artists", column: "composer_id"
      add_foreign_key "composer_songs", "songs"
      add_foreign_key "lyricist_songs", "artists", column: "lyricist_id"
      add_foreign_key "lyricist_songs", "songs"
    end
    
[/code]

# 試したこと

以下のような操作はちゃんとできました。

[code]

    Song.create(name: "test1")
    Song.create(name: "test2")
    Artist.create(name: "a1")
    Artist.create(name: "a2")
    
[/code]

中間テーブルを多分ちゃんと覗けています。。。  
今はまだ中間テーブルは空です。

[code]

    irb(main):012:0> Song.first.composer_songs
      Song Load (0.6ms)  SELECT "songs".* FROM "songs" ORDER BY "songs"."id" ASC LIMIT $1  [["LIMIT", 1]]
      ComposerSong Load (0.7ms)  SELECT "composer_songs".* FROM "composer_songs" WHERE "composer_songs"."song_id" = $1  [["song_id", 1]]
    => []
    
[/code]

# 実現したいこと

以下のような状態を実現できるコントローラーを記述したいです。

[![実現](https://i.stack.imgur.com/2DyVi.png)](https://i.stack.imgur.com/2DyVi.png)

しかし、どのようにして`Song.create(name: "test1")`のような  
`create`や`update`の処理を記述したらいいのかわかりません。

私のような多対多のテーブルにおける、コントローラ(CRUD)の記述の仕方を教えていただきたいです。  
よろしくお願いします。

# 追記

回答が得られなかったので、テラテイルの方でも質問させていただいています。  
<https://teratail.com/questions/362228>


CC BY-SA 4.0
2021-09-29T16:29:40.397
82763
2021-10-08T07:20:27.930
2021-10-01T05:38:21.603
47477
47477
question
ruby-on-rails postgresql
Rails で多対多のコントローラの作り方が分からない


`has_many :composer_song_artists, through: :composer_songs`

という記述は  
composer_songs という中間リレーションを通して  
composer_song_artists と関連するという定義ですが  
composer_songs の中に composer_song_artists というリレーションは定義されていません

composer_song にあるのは composer です  
ちなみに artist_id というカラムはなく composer というリレーションを使えば自動的に foreign_key は composer_id
になるので指定は入りません

逆に Artist モデル内では composer_song の  
どのカラムが artist なのかわからないので foreign_key の指定が必要です

song <=> composer_songs <=> artist だけの関連をまとめると以下のようになります

[code]

    class Song < ApplicationRecord
      has_many :composer_songs, dependent: :destroy
      has_many :composers, through: :composer_songs
    end
    
    class Artist < ApplicationRecord
      has_many :composer_songs, dependent: :destroy, foreign_key: "composer_id"
      has_many :songs, through: :composer_songs
    end
    
    class ComposerSong < ApplicationRecord
      belongs_to :composer, class_name: "Artist"
      belongs_to :song
    end
    
[/code]

* * *

これで以下のように build を使って関連レコードを生成すると  
親を save した時点で関連モデルも生成されます

[code]

    # song:1 に composer:10 を作って関連させる
    @song = Song.find_by(id:1) || Song.new(id: 1)
    @song.composers.build(id: 10)
    @song.save
    
    # song:1 の composer:11 を追加する
    @song = Song.find_by(id:1) || Song.new(id: 1)
    @song.composers.build(id: 11)
    @song.save
    
    # song:1 に関連する composer を表示
    pp @song.composers
    
    # artist:10 に関連する song を表示
    pp Artist.find_by(id:10).songs
    
[/code]

* * *

どうしても composer_song_artists のような名前を使いたければ

`has_many :composer_song_artists, through: :composer_songs, source: :composer`

というように source を使えば別名をつけることができます

[code]

    @song = Song.find_by(id:1) || Song.new(id: 1)
    @song.composer_song_artists.build(id: 10)
    @song.save
    
    @song = Song.find_by(id:1) || Song.new(id: 1)
    @song.composer_song_artists.build(id: 11)
    @song.save
    
    pp @song.composer_song_artists
    
    pp Artist.find_by(id:10).songs
    
[/code]

arranger や lyricist も同様なので自分で作ってみてください

あと実際に動かしたわけではないので複数形単数形とかミスがあるかもしれません


CC BY-SA 4.0
2021-10-07T09:10:51.747
82937
2021-10-08T07:20:27.930
2021-10-08T07:20:27.930
82763
answer