Rails ファイル添付機能 複数モデルでの共通化

## はじめに
DRYの考え方を意識しはじめ、効率的なコーディングだと感じたので備忘録としてまとめます。

## 構成
UserモデルとPostモデルの両方にファイル添付機能を持たせたいとき、
Attachment_fileモデルに添付ファイルを格納して、
中間テーブルとしてUser_attachment_fileモデルとPost_attachment_fileモデルを設ける。

* Attachment_fileモデル
* Userモデル
* User_attachment_fileモデル
* Postモデル
* Post_attachment_fileモデル

## Gemfile
下記を書いて、bundle install

```Gemfile
gem 'carrierwave'
```

## コーディング
###　Model

* Attachment_fileモデル
* Userモデル
* User_attachment_fileモデル
* Postモデル
* Post_attachment_fileモデル

```attachment_file.rb
mount_uploader :file, FileUploader
after_destroy :destory_intermediate

has_many :user_attachment_files
has_many :users, through: :user_attachment_files

has_many :post_attachment_files
has_many :posts, through: :post_attachment_files

def destory_intermediate
  user_attachment_files.destroy if user_attachment_files.present?
  post_attachment_files.destroy if post_attachment_files.present?
end
```

```user.rb
class User < ActiveRecord::Base
  has_many :user_attachment_files, dependent: :destroy
  has_many :attachment_files, through: :user_attachment_files, dependent: :destroy
end
```

```user_attachment_file.rb
class UserAttachmentFile < ActiveRecord::Base
  belongs_to :user
  belongs_to :attachment_file
end
```

```post.rb
class Post < ActiveRecord::Base
  has_many :post_attachment_files, dependent: :destroy
  has_many :attachment_files, through: :post_attachment_files, dependent: :destroy
end
```

```post_attachment_file.rb
class PostAttachmentFile < ActiveRecord::Base
  belongs_to :post
  belongs_to :attachment_file
end
```

### Controller

* Attachment_fileモデル


* Attachment_fileモデル

```attachment_files_controller.rb
class AttachmentFilesController < ApplicationController
  def create
    @attachment_file = AttachmentFile.create!(file_params)

    respond_to do |format|
      format.html
      format.json {render json: @attachment_file }
    end
  end

  def destroy
    @attachment_file = AttachmentFile.find(params[:id])
    if @attachment_file.destroy
      render :json => { result: 'success' }
    else
      render :json => { result: 'error' }
    end
  end

  private

  def file_params
    params.require(:attachment_file).permit(:id, :file)
  end
end
```

### View

Dropzoneを使っています。ここでは詳細は割愛します。

（Dropzone.jsで複数ファイルアップロード後、削除したいファイルを指定したい：https://qiita.com/saekis/items/207379a056af73f143b7）

```users/new.html.slim
tr
  th = label :user, :user_file
  td
    #user_file_uploader.dropzone                  / ポイント
    table.files
      - for file in @user.attachment_files do
        tr id="file_row_#{file.id}"
          td
            = file.original_filename
            = hidden_field_tag nil, file.id, name:'user[attachment_file_ids][]'
          td
            = link_to file.file_url, download: file.original_filename do
              = fa_icon "download"
              |ダウンロード
          td
            = link_to '#', onclick: "#", file_id: file.id, remote: true do
              |削除
```



### Table
中間テーブル（User_attachment_file、Post_attachment_file）を持っているため、UserモデルとPostモデルではカラムを設ける必要はありません。

* Userモデル
* User_attachment_fileモデル
* Postモデル
* Post_attachment_fileモデル
* Attachment_fileモデル

```schema.rb
# Userモデル
create_table "users", force: :cascade, options: do |t|
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
end

# User_attachment_fileモデル
create_table "user_attachment_files", force: :cascade, options: do |t|
  t.integer  "user_id",            limit: 4, null: false          # ポイント
  t.integer  "attachment_file_id", limit: 4, null: false          # ポイント
end

# Postモデル
create_table "posts", force: :cascade, options: do |t|
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
end

# Post_attachment_fileモデル
create_table "post_attachment_files", force: :cascade, options: do |t|
  t.integer  "post_id",            limit: 4, null: false          # ポイント
  t.integer  "attachment_file_id", limit: 4, null: false          # ポイント
end

# Attachment_fileモデル
create_table "attachment_files", force: :cascade, options: do |t|
  t.string   "file",              limit: 255                      # ポイント
  t.string   "original_filename", limit: 255                      # ポイント
  t.datetime "created_at",                    null: false
  t.datetime "updated_at",                    null: false
end
```


## まとめ
先輩のコードを見てこの方法を知りました。
人のコードを読むことの重要性を強く感じたので、コーディング　→ リファクタリング　のサイクルを大切にしたいです。

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/