【Rails】フリマアプリ商品編集機能について（プレビュー編集・DB更新）

# はじめに
草野と申します。
今回の投稿は、プログラミングスクールでチーム開発にて行ったフリーマーケット系ECサイトのクローンアプリ商品編集機能についてです。自分用のメモのため、文章は拙いですが、少しでも初学者の助けになればと考えています。
内容は、表題にもあるとおり、プレビュー編集とDB更新についてです。未熟な点も多いと思います。不備等ありましたらご指摘ください。随時改善して行こうと思います。ちなみに私は、スクール自体は卒業しており、学習した内容の振り返りとして投稿させて頂いております。

# 完成品
#### 商品編集画面（プレビュー画像部）
[![Image from Gyazo](https://i.gyazo.com/2122cd8f3dcd78a474e2a0692aab41f4.gif)](https://gyazo.com/2122cd8f3dcd78a474e2a0692aab41f4)
#### 商品編集画面（カテゴリー部）
[![Image from Gyazo](https://i.gyazo.com/ad8f6257f48af707a31e7047a41fa689.gif)](https://gyazo.com/ad8f6257f48af707a31e7047a41fa689)
#### 商品編集画面（販売手数料・利益部）
[![Image from Gyazo](https://i.gyazo.com/d75f6ba565489f4d92b3ae557698f64f.png)](https://gyazo.com/d75f6ba565489f4d92b3ae557698f64f)
#### 更新成功時の遷移画面
[![Image from Gyazo](https://i.gyazo.com/05de6a9f9622c25b3554fd791b3b78ea.png)](https://gyazo.com/05de6a9f9622c25b3554fd791b3b78ea)

# 実装手順
1.ルーティング編集

 - update_doneのルート設定（更新成功時の遷移画面）

２.コントローラー編集

 - editメソッド設定
 - updateメソッド設定
    - エラーハンドリング
    - 画像削除
 - update_daneメソッド設定

３.ビュー編集・作成

 - プレビュー画像呼び出し
 - カテゴリー呼び出しの調整
 - 更新成功時の遷移画面

4.JS編集

 - プレビュー画像及びinputタグの生成、削除
 - 販売手数料・利益の表示

# 1.ルーティング編集

update_doneルートを生成します。
これは、更新成功時の遷移画面を表示するためのルーティングです。

```rb:config/routes.rb&nbsp;
  resources :items do
    resources :comments,  only: [:create, :destroy]
    resources :favorites, only: [:create, :destroy]
    collection do
      get 'get_category_children', defaults: { fomat: 'json'}
      get 'get_category_grandchildren', defaults: { fomat: 'json'}
      get 'search'
      get 'post_done'
      get 'delete_done'
      get 'detail_search'
      get 'update_done' # これを追加
    end
  end
```

# 2.コントローラー編集
今回編集したコントローラーの記述は下記の通りです。

```rb:app/controller/items_controller.rb&nbsp;
class ItemsController < ApplicationController
before_action :category_parent_array, only: [:new, :create, :edit]
before_action :set_item, only: [:show, :edit, :update, :destroy]
before_action :show_all_instance, only: [:show, :edit, :destroy]

# 中略

  def edit
    grandchild = @item.category
    child = grandchild.parent
    if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
    else
     @parent_array = []
     @parent_array << @item.category.parent.parent.name
     @parent_array << @item.category.parent.parent.id
    end
     @category_children_array = Category.where(ancestry: child.ancestry)
     @child_array = []
     @child_array << child.name
     @child_array << child.id

     @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry)
     @grandchild_array = []
     @grandchild_array << grandchild.name
     @grandchild_array << grandchild.id

  end

  def update
    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を１枚以上入れてください】'
      render :edit
    else
      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }
      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all
      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end
    end
  end

  def update_done
    @item_update = Item.order("updated_at DESC").first
  end

# 中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])
  end

  def category_parent_array
    @category_parent_array = Category.where(ancestry: nil).each do |parent|
    end
  end

  def show_all_instance
    @user = User.find(@item.user_id)
    @images = Image.where(item_id: params[:id])
    @images_first = Image.where(item_id: params[:id]).first
    @category_id = @item.category_id
    @category_parent = Category.find(@category_id).parent.parent
    @category_child = Category.find(@category_id).parent
    @category_grandchild = Category.find(@category_id)
  end
end

```

まずeditメソッドを設定します。
他のメソッドで使用しているインスタンス変数を利用するのでリファクタリングのため、before_actionから呼び出しがあります。
使用しているインスタンス変数は下記の通りです。
② 親カテゴリーのnameとidが代入された配列
③ categoryモデル内の全ての子カテゴリー
④ 子カテゴリーのnameとidが代入された配列
⑤ categoryモデル内の全ての孫カテゴリー
⑥ 孫カテゴリーのnameとidが代入された配列
⑦ 該当商品情報
⑧ categoryモデル内の全ての親カテゴリー
⑨ 該当商品の画像
⑩ 該当商品のcategory_id(孫の数値)




```rb:app/controller/items_controller.rb&nbsp;
class ItemsController < ApplicationController
before_action :category_parent_array, only: [:new, :create, :edit]
before_action :set_item, only: [:show, :edit, :update, :destroy]
before_action :show_all_instance, only: [:show, :edit, :destroy]

#中略

  def edit
    # ▼ ①ここで該当商品の子・孫カテゴリーを変数へ代入
    grandchild = @item.category
    child = grandchild.parent
    if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
    else
     # ② ▼ 親カテゴリーのnameとidを配列代入
     @parent_array = []
     @parent_array << @item.category.parent.parent.name
     @parent_array << @item.category.parent.parent.id
    end
     # ③ ▼ 子カテゴリーを全てインスタンス変数へ代入
     @category_children_array = Category.where(ancestry: child.ancestry)
     # ④ ▼ 子カテゴリーのnameとidを配列代入
     @child_array = []
     @child_array << child.name # ⑤で生成した変数を元にname・idを取得
     @child_array << child.id
     # ⑤ ▼ 孫カテゴリーを全てインスタンス変数へ代入
     @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry) 
     # ⑥ ▼ 孫カテゴリーのnameとidを配列代入
     @grandchild_array = []
     @grandchild_array << grandchild.name # ⑤で生成した変数を元にname・idを取得
     @grandchild_array << grandchild.id
  end
  end

#中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])                                # ⑦ 該当の商品情報をインスタンス変数へ代入
  end

  def category_parent_array
    @category_parent_array = Category.where(ancestry: nil)        # ⑧ 親カテゴリーを全てインスタンス変数へ代入
  end

  def show_all_instance
    @user = User.find(@item.user_id)
    @images = Image.where(item_id: params[:id])                   # ⑨ 該当商品の画像をインスタンス変数へ代入
    @images_first = Image.where(item_id: params[:id]).first
    @category_id = @item.category_id                              # ⑩ 該当商品のレコードからカテゴリーidを取得し、インスタンス変数へ代入（この際に取得するidは孫カテゴリーidです。）
    @category_parent = Category.find(@category_id).parent.parent                    
    @category_child = Category.find(@category_id).parent
    @category_grandchild = Category.find(@category_id)
  end

```
それぞれを分類分けして並べ替えると下記の通りになります。

**商品情報をinputタグに初期値として表示させるためのもの**
　⑦ 該当商品情報
**商品画像をプレビューに初期値として表示させるためのもの**
　⑨ 該当商品の画像
**カテゴリーをinputタグに初期値として表示させるためのもの**

 - 親・子・孫のname・idを取得し、ビュー側のcollection_selectで利用する情報
　  ⑩ 該当商品のcategory_id（孫の数値）
　  ② 親カテゴリーのnameとidが代入された配列
　  ④ 子カテゴリーのnameとidが代入された配列
　  ⑥ 孫カテゴリーのnameとidが代入された配列

 - 再入力時にビュー側のcollection_selectで利用する情報
　  ⑧ categoryモデル内の全ての親カテゴリー
　  ③ categoryモデル内の全ての子カテゴリー
　  ⑤ categoryモデル内の全ての孫カテゴリー




次にupdateメソッドの設定です。
editと同様に更新したい商品情報については、before_actionにて呼び出しを行っています。

```rb:app/controller/items_controller.rb&nbsp;
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]

# 中略

  def update
    # ①
    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を１枚以上入れてください】'
      render :edit
    else
    # ②
      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }
    # ③
      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all
    # ④
      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end
    end
  end


# 中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])
  end
end

```
まずこちらの記述は、画像が１枚もない時に更新できないようにif文でエラーハンドリングを記述しています。
item_params[:images_attributes].nil?の記述でparams内の画像が空か確かめています。
.nil?メソッドで空の場合はtureとなり、renderで編集画面に戻りflash.now[:alert]でエラーメッセージを表示します。

```rb:①
    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を１枚以上入れてください】'
      render :edit
    else
```
先ほどのif文でfalseとなった場合に②が動きます。
記述している内容は、exit_idsが更新ボタンを押した時点で入力されている画像のid、idsがDB内に保存されている更新前画像のidとなります。
exit_idsという配列を生成し、item_params[:images_attributes]という多次元配列内に含まれるidの値を取り出したいのでeach文でキーと値を順番に展開します。（|a,b| → a キーのこと　b 値のこと）
そして多次元配列から値を取り出すために使用するのがdigメソッドです。
item_params[:images_attributes]（多次元配列）.dig(:"#{a（親キー）}",:id（子キー）).to_i（数値にする）という記述でidを取り出し、配列に代入します。
そしてidsにはDBから更新前の該当するレコードを取得し、mapメソッドにてidを抽出し代入します。

```rb:②
      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }
```
ちなみにbinding.pryを使用してitem_params[:images_attributes]の中身を確認すると下記のように表示されます。画像１枚で更新ボタンをクリックしました。②のexit_idsに代入したい値は、子の配列内にある"322"という値です。

```rb:ターミナル（コンソール起動）
[1] pry(#<ItemsController>)> item_params[:images_attributes]
=> <ActionController::Parameters {"0"=><ActionController::Parameters {"id"=>"322"} permitted: true>} permitted: true>
```
③では、先ほどのexit_idsとidsを比較し、DBから初期値として編集画面に呼び出されていた画像を削除した場合にDB内の該当データを削除します。idsからexit_idsを引くことで削除されているidだけ残すことができます。それをdelete__dbに代入し、それを元にDBからレコードを検索し、destroy_allメソッドを使って削除します。
_allとしているのは複数レコードの場合も削除できるようにするためです。

```rb:③
      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all
```
④では、商品情報の更新を行っています。if文のエラーハンドリングにより更新できた場合には、update_dineルートを通り更新成功を伝える画面に遷移します。更新でなかった場合には編集画面に戻り、エラーメッセージを表示します。
一番最初の行に記述している@item.touchはitemsテーブルのupdate_atカラム（更新日時）も含めて更新するためのものです。
これを記述する理由は、後ほどご説明します。

```rb:④
      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end
```
次にupdate_doneメソッドの設定です。
更新成功を伝える画面には更新した商品詳細ページのリンクを設置しています。
先ほどのupdateメソッドの④で@item.touchを記述することによりitemsテーブルのupdate_atカラム（更新日時）を更新しました。orderメソッド、firstメソッドを使い、update_atカラム内を降順に一番目のものを@item_updateに代入します。

```rb:app/controller/items_controller.rb&nbsp;

  def update_done
    @item_update = Item.order("updated_at DESC").first
  end
```

# 3.ビュー編集
全ての記述を載せると長くなってしまうのでここでは割愛して記述させて頂きます。

```haml:app/views/items/_form_edit.html.haml&nbsp;
# ▼ 商品画像についての記載
.new__page__header
  = link_to image_tag("logo/logo.png", alt: "logo"), root_path
= form_for @item do |f|
  = render 'layouts/error_messages', model: f.object
  .name__field#1
    .form__label
      .lavel__name
        出品画像
      .lavel__Required
        [必須]
  #image-box-1{class:"#{@images.last.id}"}
# ▼ ① プレビュー画像の表示
    - @images.each do |img|
      .item-image{id:img.id}
        = image_tag(img.image.url,{width:"188",height:"180"})
        .item-image__operetion
          .item-image__operetion--edit__delete__hidden 削除
    %label.img-label{for: "img-file"}
      #image-box__container{class:"item-num-#{@images.length}"}
        #append-js-edit
          = f.fields_for :images do |image|
            .js-file_group{"data-index" => "#{image.index}"}
              = image.file_field :image, type: 'file', value:"#{image.object.id}",style: "",  id:"img-file", class:'js-file-edit',name: "item[images_attributes][#{@item.images.count}][image]", data:{index:""}
        %i.fas.fa-camera

# 中略

# ▼ カテゴリーについての記載
  .append__category
    .category
      =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"}
    - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
      .category__grandchild#children_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"}
    - else
      .category__child#children_wrapper
        =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"}
      .category__grandchild#grandchildren_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"}

# 省略

```
下記の記述でプレビュー画像を表示しています。
.item-image__operetion--edit__delete__hidden 削除 のhiddenという記述がポイントです。js編集の際にご説明します。

```haml:①
    - @images.each do |img|
      .item-image{id:img.id}
        = image_tag(img.image.url,{width:"188",height:"180"})
        .item-image__operetion
          .item-image__operetion--edit__delete__hidden 削除
```
下記の記述でカテゴリーを表示しています。
if文で孫なしの場合と孫ありの場合で条件分岐させています。
collection_selectタグに中身の内{}を記述していますがこれはidを付与するにあたり、オプションを記述する際の引数の順番の関係で記述しています。

```haml:カテゴリー
  .append__category
    .category
      =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"}
    - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
      .category__grandchild#children_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"}
    - else
      .category__child#children_wrapper
        =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"}
      .category__grandchild#grandchildren_wrapper
        =f. :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"}
```
下記の記述は、更新成功時遷移画面のビューファイルになります。

```haml:app/views/items/_form_edit.html.haml&nbsp;
= render "top/header"
.done#fullsize
  .done__title
    商品情報を更新しました
  .done__backlink
    = link_to 'トップページへ戻る', root_path, class: 'link'
  .done__backlink
    = link_to '更新した商品を確認する', item_path(@item_update), class: 'link'
  .done__backlink
    = link_to '出品中の商品一覧を見る', users_path, class: 'link'
= render "top/lower-photo"
= render "top/footer"
= render "top/btn"
```

# 4.JS編集
下記の通りjsファイルを編集します。

```javascript:app/assets/javascript/edit_items.js&nbsp;
$(function(){
  var dataBox = new DataTransfer();
  var file_field = document.getElementById('img-file')
  $('#append-js-edit').on('change','#img-file',function(){
    $.each(this.files, function(i, file){
      //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
      var fileReader = new FileReader();
      //DataTransferオブジェクトに対して、fileを追加
      dataBox.items.add(file)
      var num = $('.item-image').length + 1 + i
      var aaa = $('.item-image').length + i
// ①
      var image_id = Number($('#image-box-1').attr('class'))
      var append_div_count = Number($('div[id=1]').length) 
      var noreset_id = image_id + append_div_count

      fileReader.readAsDataURL(file);
     //画像が10枚になったら超えたらボックスを削除する
      if (num == 10){
        $('#image-box__container').css('display', 'none')
      }
      //読み込みが完了すると、srcにfileのURLを格納
      fileReader.onloadend = function() {
        var src = fileReader.result
// ②
        var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}">
                    <div class=' item-image__content'>
                      <div class='item-image__content--icon'>
                        <img src=${src} width="188" height="180" >
                      </div>
                    </div>
                    <div class='item-image__operetion'>
                      <div class='item-image__operetion--edit__delete__file'>削除</div>
                    </div>
                  </div>`
        const buildFileField1 = (num)=> {
// ③
          const html = `<div  class="js-file_group" data-index="${num}" id=1>
                          <input class="js-file-edit" type="file"
                          name="item[images_attributes][${append_div_count+9}][image]"
                          id="img-file" data-index="${num}value="${noreset_id}" >
                        </div>`;
          return html;
        }
        $('.js-file-edit').removeAttr('id');
        //image_box__container要素の前にhtmlを差し込む
        $('.img-label').before(html);
        $('#append-js-edit').append(buildFileField1(num));
      };
      //image-box__containerのクラスを変更し、CSSでドロップボックスの大きさを変えてやる。
      $('#image-box__container').attr('class', `item-num-${num}`)
    });
  });
// ④
  // 10枚登録されていた場合にボックスを消す
  $(document).ready(function(){
    var image_num = $('.item-image').length
    if (image_num==10){
      $('#image-box__container').css('display', 'none')
    }
  });
// ⑤
  $(document).ready(function(){
    $('.js-file-edit').removeAttr('id');
    var num = $('.item-image').length - 1
    var image_id = Number($('#image-box-1').attr('class'))
    var append_div_count = Number($('div[id=1]').length) 
    var noreset_id = image_id + append_div_count
    const buildFileField = (num)=> {
      const html = `<div  class="js-file_group" data-index="${num}" id=1>
                      <input class="js-file-edit" type="file"
                      name="item[images_attributes][100][image]"
                      id="img-file" data-index="${num}" value="${noreset_id}" >
                    </div>`;
      return html;
    }
    $('#append-js-edit').append(buildFileField(num));
  });
// ⑥
  $(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    //削除を押されたプレビューimageのfile名を取得
    var target_id = $(target_image).attr('id');
    var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })
// ⑦
  $(document).on("click", '.item-image__operetion--edit__delete__file', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    var target_id = Number($(target_image).attr('id'));
    //削除を押されたプレビューimageのfile名を取得
    var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })
```
１行目の記述で一番最後に保存された画像idを取得しimage_id変数に代入します。２行目でビューファイル内のdivでid=1と付与されているタグの数を数えappend_div_count変数に代入します。３行目でそれを足し合わせ、
noreset_id変数に代入します。noreset_idは、画像を追加した際に新たに表示されるinputタグのvalueオプションにセットするためのものです。これを利用して削除の動作を行います。また、プレビュー画像の親となるdivタグにも同じ数値のものを狙って削除するため、idオプションにセットします。（②）

```javascript:①
      var image_id = Number($('#image-box-1').attr('class'))
      var append_div_count = Number($('div[id=1]').length) 
      var noreset_id = image_id + append_div_count
```
下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるプレビュー画像のHTMLです。
<div class='item-image__operetion--edit__delete__file'>削除の記述でfileという記述がポイントです。ビュー編集時に言っていたhiddenの記述と見分け、新た生成されたinputタグなのか、最初から表示されているinputタグなのか判断します。

```javascript:②
 var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}">
                    <div class=' item-image__content'>
                      <div class='item-image__content--icon'>
                        <img src=${src} width="188" height="180" >
                      </div>
                    </div>
                    <div class='item-image__operetion'>
                      <div class='item-image__operetion--edit__delete__file'>削除</div>
                    </div>
                  </div>`
```
下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるinputタグのHTMLです。

```javascript:③
 const html = `<div  class="js-file_group" data-index="${num}" id=1>
                          <input class="js-file-edit" type="file"
                          name="item[images_attributes][${append_div_count+9}][image]"
                          id="img-file" data-index="${num}value="${noreset_id}" >
                        </div>`;
```
下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、プレビュー画像の数を数えて10枚だった場合に画像を入力するボックスを削除するという記述になります。

```javascript:④
  // 10枚登録されていた場合にボックスを消す
  $(document).ready(function(){
    var image_num = $('.item-image').length
    if (image_num==10){
      $('#image-box__container').css('display', 'none')
    }
  });
```
下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、inputタグが生成される記述になります。これを行わない場合、最初のinputタグへの入力が、既存の表示されているinputタグに入力されてしまいズレが生じてしまうため、画面ロード時に生成する必要があります。

```javascript:⑤
  $(document).ready(function(){
    $('.js-file-edit').removeAttr('id');
    var num = $('.item-image').length - 1
    var image_id = Number($('#image-box-1').attr('class'))
    var append_div_count = Number($('div[id=1]').length) 
    var noreset_id = image_id + append_div_count
    const buildFileField = (num)=> {
      const html = `<div  class="js-file_group" data-index="${num}" id=1>
                      <input class="js-file-edit" type="file"
                      name="item[images_attributes][100][image]"
                      id="img-file" data-index="${num}" value="${noreset_id}" >
                    </div>`;
      return html;
    }
    $('#append-js-edit').append(buildFileField(num));
  });
```
下記の記述は、editで呼び出した画像データが入力されているinputタグとプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。

```javascript:⑥
  $(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    //削除を押されたプレビューimageのfile名を取得
    var target_id = $(target_image).attr('id');
    var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })
```
下記の記述は、新たに画像がinputタグに入力された際に生成呼び出した画像データが入力されているinputタグと新たに生成されたプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。

```javascript:⑦
  $(document).on("click", '.item-image__operetion--edit__delete__file', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    var target_id = Number($(target_image).attr('id'));
    //削除を押されたプレビューimageのfile名を取得
    var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })
```
下記の記述は、価格が入力された際に販売手数料、販売利益を計算して出力するものです。
編集した箇所は、2段目、４段目に記述してる内容で、readyメソッドにより画面ロード時に販売手数料、販売利益を計算し、表示させるというものです。

```javascript:app/assets/javascript/sales_commission.js&nbsp;
$(function() {
  var input=$("#item_exhibition_price"),fee=1/10,feeIncluded=$("#sales_commission_price");
  input.on("input",function(){
    feeIncluded.text(Math.floor($(this).val() * fee).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_commission_price.append("円");
    }
  });
  $(document).ready(function(){
    feeIncluded.text(Math.floor($("#item_exhibition_price").val() * fee).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_commission_price.append("円");
    }
  });
});
  
$(function() {
  var input=$("#item_exhibition_price"),tax=9/10,salesProfit=$("#sales_profit_proce");
  input.on("input",function(){
    salesProfit.text(Math.ceil($(this).val() * tax).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_profit_proce.append("円");
    }
  });
  $(document).ready(function(){
    salesProfit.text(Math.ceil($("#item_exhibition_price").val() * tax).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_profit_proce.append("円");
    }
  });
});
```

これで編集機能完成です。
ここまで読んでくださり、ありがとうございます。