質問編集履歴

1

コードの追記

2019/11/12 14:08

投稿

Hiro191110
Hiro191110

スコア5

test CHANGED
File without changes
test CHANGED
@@ -1,7 +1,5 @@
1
1
  ### 実現したいこと
2
2
 
3
-
4
-
5
3
  Railsで商品の出品、売買ができるフリマアプリを作っております。
6
4
 
7
5
  accepts_nested_attributes_forを使って複数モデルの同時保存(update)を行いたいのですが、
@@ -32,8 +30,6 @@
32
30
 
33
31
  ### 前提
34
32
 
35
-
36
-
37
33
  ・商品と画像は別々のテーブルを作成
38
34
 
39
35
  ・accepts_nested_attributes_forを使って、画像テーブル(子)を商品テーブル(親)へネスト
@@ -98,6 +94,20 @@
98
94
 
99
95
  ```Ruby
100
96
 
97
+ .items-sell-wrapper.edit_item
98
+
99
+ = render "items/header"
100
+
101
+ %main.items-sell-main
102
+
103
+ %section.items-sell-content__section
104
+
105
+ %h2.items-sell-content__section__header
106
+
107
+ 商品の情報を入力
108
+
109
+ .items-sell-container
110
+
101
111
  = form_with model: @item, local: true, class: "dropzone", id: "item-dropzone" do |f|
102
112
 
103
113
  %section.items-sell-content__section
@@ -124,12 +134,324 @@
124
134
 
125
135
  %p.image-upload-text ここをクリックして画像をアップロード
126
136
 
127
- = item_image.file_field :image, style: "display: none;", name: "item[item_images_attributes][#{@item.item_images.length}][image]", id: "upload-image-0", class: "upload-image", 'data-image': 0
128
-
129
- ```
137
+ = item_image.file_field :image, style: "display: none;", name: "item[item_images_attributes][#{@item.item_images.length}][image]", id: "upload-image-0", class: "upload-image {validation-error if @item.errors.full_messages_for(:item_images).present?}", 'data-image': 0
138
+
139
+ ```
140
+
141
+ **Controller**
142
+
143
+ items_controller.rb
144
+
145
+
146
+
147
+ ```Ruby
148
+
149
+ def update
150
+
151
+ @item = Item.find(params[:id])
152
+
153
+ # 登録済画像のidの配列を生成
154
+
155
+ ids = @item.item_images.map{|image| image.id}
156
+
157
+ # 登録済画像のうち、編集後も残っている画像のidの配列を生成
158
+
159
+ exist_ids = registered_image_params[:ids].map(&:to_i)
160
+
161
+ # 登録済画像が残っていない場合(配列に0が格納されている)、配列を空にする
162
+
163
+ exist_ids.clear if exist_ids[0] == 0
164
+
165
+ if (exist_ids.length != 0 || new_image_params[:images][0] != " ") && @item.update!(create_params)
166
+
167
+
168
+
169
+ # 登録済画像のうち削除ボタンが押された画像を削除
170
+
171
+ unless ids.length == exist_ids.length
172
+
173
+ # 削除する画像のidの配列を生成
174
+
175
+ delete_ids = ids - exist_ids
176
+
177
+ delete_ids.each do |id|
178
+
179
+ @item.item_images.find(id).destroy
180
+
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ private
190
+
191
+ def create_params
192
+
193
+ params.require(:item).permit(:name, :description, :category_id, :item_state_id, :deliver_expend_id, :deliver_method_id, :prefecture_id, :deliver_day_id, :amount, item_images_attributes: [:image])
194
+
195
+ end
196
+
197
+
198
+
199
+ def registered_image_params
200
+
201
+ params.require(:registered_images_ids).permit({ids: []})
202
+
203
+ end
204
+
205
+ ```
206
+
207
+
208
+
209
+
130
210
 
131
211
  ```javascript
132
212
 
213
+ $(document).on("turbolinks:load", function() {
214
+
215
+ var input_area = $(".input_area")
216
+
217
+ var preview = $(".preview")
218
+
219
+ // editでのみ発火するよう制限
220
+
221
+ var re = new RegExp('/items/[0-9]+/edit$');
222
+
223
+ if(!re.test(location.pathname)) {
224
+
225
+ return;
226
+
227
+ }
228
+
229
+
230
+
231
+ for (var i = preview.length; i > 1; i--) {
232
+
233
+ preview[i-1].remove();
234
+
235
+ }
236
+
237
+ preview = $('.preview');
238
+
239
+
240
+
241
+
242
+
243
+ // 登録済画像データのidを格納する配列を作成
244
+
245
+ // ブラウザ上で削除された画像のidをコントローラへ送り返す
246
+
247
+ var registered_images_ids = [];
248
+
249
+ // 新規追加画像データ用の配列(DB用)
250
+
251
+ var new_image_files = [];
252
+
253
+
254
+
255
+ gon.item_images.forEach(function(image, index) {
256
+
257
+ var img = $(`<div class = "img-view">
258
+
259
+ <img>
260
+
261
+ <div class="btn-wrapper">
262
+
263
+ <div class="btn-edit">編集</div><!--
264
+
265
+ --><div class="btn-delete">削除</div>
266
+
267
+ </div>
268
+
269
+ </div>`);
270
+
271
+
272
+
273
+ img.data("image", index)
274
+
275
+ binary_data = gon.item_images_binary_datas[index]
276
+
277
+ // 登録済画像データをsrcプロパティへ付与
278
+
279
+ img.find("img").attr({
280
+
281
+ src: binary_data
282
+
283
+ });
284
+
285
+
286
+
287
+ // 登録済画像データのidを配列へ格納
288
+
289
+ registered_images_ids.push(image.id)
290
+
291
+ // 登録済画像データを持たせたHTMLタグを、ビューへ追加してプレビューを表示させる
292
+
293
+ preview.append(img);
294
+
295
+ });
296
+
297
+
298
+
299
+ // プレビュー表示されている画像の枚数に応じて、ラベルの大きさを調整
300
+
301
+ inputs_length = $('[type="file"].upload-image').length;
302
+
303
+ $(`.items-sell-container__dropzone0`).attr('class',`items-sell-container__dropzone${inputs_length}`);
304
+
305
+
306
+
307
+ // プレビュー画像の表示をラベル要素の前に移動させる
308
+
309
+ $('.dropzone-box').before($('.img-view'))
310
+
311
+
312
+
313
+ // 画像を新規追加
314
+
315
+ //inputの中身の変更時に発生
316
+
317
+ $(document).on('change', '[type="file"].upload-image', function(event) {
318
+
319
+
320
+
321
+ // 変更を行ったinputを取得する
322
+
323
+ changed_input = $(this);
324
+
325
+ changed_id = changed_input.data('image');
326
+
327
+ // 現時点でのfile用inputタグを全取得する
328
+
329
+ inputs_length = $('[type="file"].upload-image').length;
330
+
331
+ //変更されたinputが末尾のinput(空欄からの追加)の場合、inputを更に追加
332
+
333
+ if(changed_id === inputs_length - 1 && inputs_length <= 10) {
334
+
335
+ var new_input = $(`<input class="upload-image" type="file" style="display: none;">`);
336
+
337
+ input_area.append(new_input);
338
+
339
+ }
340
+
341
+
342
+
343
+ $(`.items-sell-container__dropzone${inputs_length - 1}`).attr('class',`items-sell-container__dropzone${inputs_length}`);
344
+
345
+
346
+
347
+ //変更されたidに対応するimg-viewを取得
348
+
349
+ var image_view = $(".img-view[data-image="+changed_id+"]").first();
350
+
351
+ //idに対応するimg_viewが存在しないときは追加
352
+
353
+ if(!image_view[0]) {
354
+
355
+ image_view = $(`<div class = "img-view">
356
+
357
+ <img>
358
+
359
+ <div class="btn-wrapper">
360
+
361
+ <div class="btn-edit">編集</div><!--
362
+
363
+ --><div class="btn-delete">削除</div>
364
+
365
+ </div>
366
+
367
+ </div>`);
368
+
369
+ preview.append(image_view);
370
+
371
+ }
372
+
373
+
374
+
375
+ // プレビュー画像の表示をラベル要素の前に移動させる
376
+
377
+ $('.dropzone-box').before($('.img-view'))
378
+
379
+
380
+
381
+ //input,image_viewそれぞれにindexを再割り当て
382
+
383
+ reorder_data_image();
384
+
385
+
386
+
387
+ // アップロードされた画像ファイル(ファイルオブジェクト)の属性値(filesプロパティ)を取得する
388
+
389
+ var file = changed_input.prop('files')[0];
390
+
391
+ // FileReaderオブジェクトをインスタンス化する
392
+
393
+ var reader = new FileReader();
394
+
395
+ // ファイル読み込み後の処理
396
+
397
+ reader.onload = function(e) {
398
+
399
+ // img_view内のimgタグのsrcプロパティへ、読み込みが完了した画像を入れ込む
400
+
401
+ image_view.find('img').attr('src', e.target.result);
402
+
403
+ };
404
+
405
+
406
+
407
+ // FileReaderオブジェクトへ属性値(filesプロパティ)を代入する
408
+
409
+ reader.readAsDataURL(file);
410
+
411
+ });
412
+
413
+
414
+
415
+ // data-imageをの番号を再割り当てする
416
+
417
+ function reorder_data_image() {
418
+
419
+ //input,image_viewそれぞれにindexを再割り当て
420
+
421
+ $('[type="file"]').each(function(index, input) {
422
+
423
+ $(input).attr({
424
+
425
+ 'data-image': index,
426
+
427
+ id: 'upload-image-' + index,
428
+
429
+ name: 'item[item_images_attributes][' + index + '][image]',
430
+
431
+ });
432
+
433
+ $(input).prop('disabled', index >= 10);
434
+
435
+ })
436
+
437
+ $('.img-view').each(function(index, image) {
438
+
439
+ $(image).attr('data-image', index);
440
+
441
+ });
442
+
443
+ //dropzone-boxに対応付くinputを末尾のinputに再割り当て
444
+
445
+ $('.dropzone-box').attr('for','upload-image-' + ($('[type="file"].upload-image').length - 1));
446
+
447
+ $('.items-sell-container__dropzone' + ($('[type="file"].upload-image').length)).attr('class', 'items-sell-container__dropzone' + ($('[type="file"].upload-image').length - 1));
448
+
449
+ }
450
+
451
+
452
+
453
+ // 編集後のデータ送信
454
+
133
455
  $('.edit_item').on('submit', function(e) {
134
456
 
135
457
  e.preventDefault();
@@ -164,8 +486,6 @@
164
486
 
165
487
  }
166
488
 
167
-
168
-
169
489
  $.ajax({
170
490
 
171
491
  url: '/items/' + gon.item.id,
@@ -182,27 +502,31 @@
182
502
 
183
503
  });
184
504
 
185
- });
505
+ });
186
-
506
+
507
+
508
+
509
+
510
+
187
- ```
511
+ ```
188
-
189
- 登録済み画像が2枚ある状態で、新規画像を追加した際の
512
+
190
-
191
- params[:item][:item_images_attributes]の中身
513
+ params全体
192
514
 
193
515
  ```Terminal
194
516
 
195
- <ActionController::Parameters {"0"=>{"image"=>#<ActionDispatch::Http::UploadedFile:0x00007fb9bea614c0
196
-
197
- @tempfile=#<Tempfile:/var/folders/2g/j8x_fnq55gx4s21hb925hfcr0000gn/T/RackMultipart20191111-26990-8nvacq.jpg>,
198
-
199
- @original_filename="9.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"item[item_images_attributes][0][image]\";
200
-
201
- filename=\"9.jpg\"\r\nContent-Type: image/jpeg\r\n">, "id"=>"173"}, "1"=>{"id"=>"176"}} permitted: false>
202
-
203
- ```
204
-
205
-
517
+ [1] pry(#<ItemsController>)> params
518
+
519
+ => <ActionController::Parameters {"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"1yaPCT8BvWJWGYnz/aqP2lh4Hdq5X/dA9Fn60xVmcwB94b4H0J/WarKft/GqSBkmeWw649eINI1xvYOkSpqkZg==",
520
+
521
+ "item"=>{"item_images_attributes"=>{"0"=>{"image"=>#<ActionDispatch::Http::UploadedFile:0x00007fb9be21b8d8 @tempfile=#<Tempfile:/var/folders/2g/j8x_fnq55gx4s21hb925hfcr0000gn/T/RackMultipart20191112-26990-i8yvzd.jpg>,
522
+
523
+ @original_filename="9.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"item[item_images_attributes][0][image]\"; filename=\"9.jpg\"\r\nContent-Type: image/jpeg\r\n">, "id"=>"173"},
524
+
525
+ "1"=>{"id"=>"176"}}, "name"=>"取り引い", "description"=>"テスト", "category"=>"メンズ", "category_id"=>"156", "item_state_id"=>"3", "deliver_expend_id"=>"1", "deliver_method_id"=>"3", "prefecture_id"=>"2", "deliver_day_id"=>"3", "amount"=>"456789"},
526
+
527
+ "registered_images_ids"=><ActionController::Parameters {"ids"=>["173", "176"]} permitted: false>, "controller"=>"items", "action"=>"update", "id"=>"1"} permitted: false>
528
+
529
+ ```
206
530
 
207
531
  ### 試したこと
208
532