Railsでアソシエーション機能の実装

参考：https://qiita.com/Kohei_Kishimoto0214/items/cb9a3d3da57708fb52c9


VideoモデルとCategoryモデルを多対多の関係にする

Videoモデルは既に作成されているので既存のものがあるのでそれを使用

Categoryモデルの作成

```
rails g model Category name:string
```

作成されたマイグレーションファイルを編集

```migrate/XXXXX.rb
  def change
    create_table :categories do |t|
      t.string :name, default: "", null: false

      t.timestamps
    end
  end
end
```


マイグレーション実行

```
rails db:migrate
```

中間テーブルの作成
VideoCategoryモデル（Video_Categoriesテーブル）作成

```
rails g model VideoCategory Video:references Category:references
```
マイグレーションファイル編集

```

  def change
    create_table :video_categories do |t|
      t.references :video, index: true, foreign_key: true
      t.references :category, index: true, foreign_key: true

      t.timestamps
    end
  end
```

###indexについて

indexは、データベースにインデックスを付ける、という意味。
たとえば、この列で検索する、あるいはこの列を基準に並べ替える、という場合に、インデックスがあれば、全部のデータを調べずとも高速に行えます。ただし、インデックス自体もデータ容量や更新の際の処理が負荷となりますので、無闇矢鱈に付けても意味は無い。

###foreign_keyについて


foreign_keyは、データベースに外部キー制約をかけるもの。これがあると、「データベースレベルで」video_idにはvideos.idにある値しか入れられなくなります。has_manyやbelongs_toなどを考える上で、「関連付けできないような値は絶対に来ない」という意味で、より安全なシステムを組める。


マイグレーション実行

```
rails db:migrate
```

##モデル同士のアソシエーション設定
・videoモデルを編集
videoは複数のvideo_categoryを持っていて、
video_categoryを通して、複数のcategoryを持っているという風にしたい

```models/video.rb
  has_many :categories, through: :video_categories ⇦追加
  has_many :video_categories ⇦追加


  validates :youtube_id, presence: true
  validates :artist_name, presence: true
  validates :music_name, presence: true
end
```

中間テーブルを通して繋がっているものには
through: :group_usersというkeyをつけます。

・categoryモデルを編集
こちらも同じように
categoryは複数のvideo_categoryを持っていて
video_categoryを通して、複数のvideoを持っているとしたい。

```models/category.rb
【下記項目追加】

  has_many :videos, through: :video_categories
  has_many :video_categories
  accepts_nested_attributes_for :video_categories 

```

###accepts_nested_attributes_forについて

accepts_nested_attributes_for というkeyは、
他のモデルを一括で更新、保存できるようにするもの。
ここではcategoryを保存するのと同時にvideo_categoriesを更新できるようにしています。


・video_category
video_categoryに関しては作成時にアソシエーションを設定しているので、デフォルトのまま使用

```models/vodeo_category.rb
  belongs_to :video
  belongs_to :category
end
```

##videoに配列でcategory_idを渡せるようにする
```videos_controller.rb
 #strongparams
  private
    def video_params
        params.require(:video).permit(:artist_name, :youtube_id, :music_name, { :category_id => [] }) ⇦追加
    end
```

{:category_id => []} の部分を追加

ついでにcreateアクションも編集

```videos_controller.rb
  def create
    @video = Video.new(video_params)
      if @video.save
        @category = Category.find_by(:name params[:category][:name]) ⇦追加
        @video_cate = VideoCategory.new(video_id: @video.id, category_id: @category.id) ⇦追加
         if @video_cate.save ⇦追加
        	redirect_to @video
      	 else
        	render 'new'
      	 end
      end
  	end
```



##アソシエーションが機能しているか確認

Railsコンソールで確認

```コンソール
v = Video.first
v.categories << Category.first
v.categories
v.video_categories
```

上記順番に確認していくが、エラーが出るので上手く機能していない様子。。。

```エラー内容
ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'Video#categories' which goes through 'Video#video_categories' before the through association is defined.
from /Users/tky/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/activerecord-5.1.6/lib/active_record/reflection.rb:975:in `check_validity!'
```

どうやらモデルのhas_manyの順番を入れ替えないといけないみたい。

```models/video.rb
  has_many :video_categories  ⇦順番入れ替え
  has_many :categories, through: :video_categories ⇦順番入れ替え
```

```models/category.rb
  has_many :video_categories ⇦順番入れ替え
  has_many :videos, through: :video_categories ⇦順番入れ替え
```


これで再度コンソールを動かすと問題なく値が返ってくるのでOK!

アソシエーションが問題なく機能していることを確認！完了！


##フォームから保存できない
実際にフォームから保存すると保存できなかったので、ViewとControllerを編集
※コメントアウトしてる部分を削除

```videos_controller.rb
  def create
    @video = Video.new(video_params)
      if @video.save
        # @category = Category.find_by(name: params[:category][:name])#categoryのnameを取得
        # @video_cate = VideoCategory.new(video_id: @video.id, category_id: @category.id)#video_idとcategory_id取得
        # if @video_cate.save
          redirect_to @video
        else
          render 'new'
        end
  end
```

```videos/views.html.erb
<div class="center jumbotron">
<% provide(:title, 'add music') %>
<h1>Add Music</h1>
</div>
  <div class="nav">
    <div class="col-md-6 col-md-offset-3">
      <%= form_for @video do |f| %>
      <%= render 'shared/error_messages' %>

        <%= f.label :youtube_id %>
        <%= f.text_field :youtube_id, placeholder: 'YoutubeId', class: "form-control"%>

        <%= f.label :artist_name %>
        <%= f.text_field :artist_name, class: "form-control"%>

        <%= f.label :music_name %>
        <%= f.text_field :music_name, class: "form-control"%>

        <h2>Category選択</br>
        Category選択のチェックボックス実装</h2>
        <%= field_set_tag 'カテゴリー選択' do %>
              <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name) do |b| %>
               <% b.label { b.check_box + b.text } %>
               <% end %>
        <% end %>

        <div class="actions">
          <%= f.submit "登録" %>
        </div>
      <% end %>
    </div>
  </div>

```

これでフォームからの保存も可能になった!
Controllerに余計な記述が多かったみたいですね。。。

##ポリモフィックリレーション

あと、アソシエーションを実装する際に、ポリモフィックリレーションなる便利な機能があるみたいだが、現状良く分からないので。
こんな機能があるんだなということで。。。
http://ruby-rails.hatenadiary.com/entry/20141207/1417926599