My name is Kusano. This post is about the clone application product editing function of the flea market EC site that was developed by the team at the programming school. The text is poor because it is a memo for myself, but I hope it will help beginners as much as possible. As the title says, the content is about preview editing and DB update. I think there are many immature points. Please point out any deficiencies. I will improve it from time to time. By the way, I have graduated from the school itself, and I will post it as a review of what I learned.
--Update_done route setting (transition screen when update is successful)
--edit method settings --update method setting --Error handling --Image deletion --update_dane method setting
--Call preview image --Adjusting category calls --Transition screen when update is successful
--Generate and delete preview images and input tags --Display of sales commissions and profits
Update_done Generate a route. This is the routing to display the transition screen when the update is successful.
config/routes.rb
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' #Add this
end
end
The description of the controller edited this time is as follows.
app/controller/items_controller.rb
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]
#Omission
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] = 'Could not update [Please insert one or more images]'
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] = 'Could not update'
render :edit
end
end
end
def update_done
@item_update = Item.order("updated_at DESC").first
end
#Omission
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
First, set the edit method. Since the instance variable used in other methods is used, there is a call from before_action for refactoring. The instance variables used are as follows. (2) An array to which the name and id of the parent category are assigned ③ category All child categories in the model ④ Array to which the name and id of the child category are assigned ⑤ category All grandchild categories in the model ⑥ Array to which the name and id of the grandchild category are assigned ⑦ Applicable product information ⑧ category All parent categories in the model ⑨ Image of the corresponding product ⑩ Category_id (grandchild number) of the corresponding product
app/controller/items_controller.rb
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]
#Omission
def edit
#▼ ① Here, substitute the child / grandchild category of the corresponding product into a variable.
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
#② ▼ Array assignment of parent category name and id
@parent_array = []
@parent_array << @item.category.parent.parent.name
@parent_array << @item.category.parent.parent.id
end
#③ ▼ Assign all child categories to instance variables
@category_children_array = Category.where(ancestry: child.ancestry)
#④ ▼ Array assignment of child category name and id
@child_array = []
@child_array << child.name #Get name / id based on the variable generated in ⑤
@child_array << child.id
#⑤ ▼ Assign all grandchild categories to instance variables
@category_grandchildren_array = Category.where(ancestry: grandchild.ancestry)
#⑥ ▼ Array assignment of grandchild category name and id
@grandchild_array = []
@grandchild_array << grandchild.name #Get name / id based on the variable generated in ⑤
@grandchild_array << grandchild.id
end
end
#Omission
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]) #⑦ Assign the corresponding product information to the instance variable
end
def category_parent_array
@category_parent_array = Category.where(ancestry: nil) #⑧ Assign all parent categories to instance variables
end
def show_all_instance
@user = User.find(@item.user_id)
@images = Image.where(item_id: params[:id]) #⑨ Substitute the image of the corresponding product into the instance variable
@images_first = Image.where(item_id: params[:id]).first
@category_id = @item.category_id #⑩ Get the category id from the record of the corresponding product and assign it to the instance variable (the id obtained at this time is the grandchild category id)
@category_parent = Category.find(@category_id).parent.parent
@category_child = Category.find(@category_id).parent
@category_grandchild = Category.find(@category_id)
end
If you classify and rearrange each, it will be as follows.
** For displaying product information as an initial value in the input tag ** ⑦ Applicable product information ** For displaying the product image as the initial value in the preview ** ⑨ Image of the corresponding product ** For displaying the category as the initial value in the input tag **
--Information to get parent / child / grandchild name / id and use in collection_select on view side ⑩ Category_id (grandchild number) of the corresponding product (2) An array to which the name and id of the parent category are assigned ④ Array to which the name and id of the child category are assigned ⑥ Array to which the name and id of the grandchild category are assigned
--Information used by collection_select on the view side when re-entering ⑧ category All parent categories in the model ③ category All child categories in the model ⑤ category All grandchild categories in the model
Next is the setting of the update method. As with edit, the product information you want to update is called before_action.
app/controller/items_controller.rb
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
#Omission
def update
# ①
if item_params[:images_attributes].nil?
flash.now[:alert] = 'Could not update [Please insert one or more images]'
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] = 'Could not update'
render :edit
end
end
end
#Omission
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
First of all, this description describes error handling with an if statement so that it cannot be updated when there is no image. The description of item_params [: images_attributes] .nil? Checks if the images in params are empty. If the .nil? method is empty, it will be ture, render will return to the edit screen, and flash.now [: alert] will display an error message.
①
if item_params[:images_attributes].nil?
flash.now[:alert] = 'Could not update [Please insert one or more images]'
render :edit
else
If the if statement above is false, ② will move. The contents described are the id of the image entered when exit_ids presses the update button, and the id of the image before update saved in the DB. exit_Generate an array called ids and item_params[:images_attributes]I want to retrieve the id value contained in the multidimensional array, so I expand the key and value in order with each statement. (|a,b|→ a key, b value) And the dig method is used to retrieve the value from the multidimensional array. item_params [: images_attributes](multidimensional array) .dig (: "# {a (parent key)}" ,: id (child key)) .to_i (numerical value) Extract the id and assign it to the array I will. Then, get the corresponding record before update from the DB for ids, extract the id with the map method and substitute it.
②
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 }
By the way, if you check the contents of item_params [: images_attributes] using binding.pry, it will be displayed as follows. I clicked the update button with one image. The value you want to assign to exit_ids in ② is the value "322" in the child array.
Terminal (console start)
[1] pry(#<ItemsController>)> item_params[:images_attributes]
=> <ActionController::Parameters {"0"=><ActionController::Parameters {"id"=>"322"} permitted: true>} permitted: true>
In ③, the exit_ids and ids are compared, and when the image called on the edit screen as the initial value is deleted from the DB, the corresponding data in the DB is deleted. By subtracting exit_ids from ids, you can leave only the deleted ids. Assign it to delete__db, retrieve the record from the DB based on it, and delete it using the destroy_all method. The reason for using _all is so that multiple records can be deleted.
③
delete__db = ids - exit_ids
Image.where(id:delete__db).destroy_all
In ④, product information is updated. If it can be updated by error handling of the if statement, it will transition to the screen that tells the update success through the update_dine route. If it is not updated, the screen returns to the edit screen and an error message is displayed. @ Item.touch described in the very first line is for updating including the update_at column (update date and time) of the items table. The reason for writing this will be explained later.
④
@item.touch
if @item.update(item_params)
redirect_to update_done_items_path
else
flash.now[:alert] = 'Could not update'
render :edit
end
Next is the setting of the update_done method. A link to the updated product detail page is set up on the screen that informs the update success. The update_at column (update date and time) of the items table was updated by describing @ item.touch in ④ of the update method earlier. Use the order method and first method, and assign the first one to @item_update in descending order in the update_at column.
app/controller/items_controller.rb
def update_done
@item_update = Item.order("updated_at DESC").first
end
It will be long if all the descriptions are included, so I will omit them here.
haml:app/views/items/_form_edit.html.haml
#▼ Description of product image
.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
Exhibition image
.lavel__Required
[Mandatory]
#image-box-1{class:"#{@images.last.id}"}
#▼ ① Display of preview image
- @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 delete
%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
#Omission
#▼ Description of category
.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"}
#abridgement
The preview image is displayed with the following description. .item-image__operetion--edit__delete__hidden The point is the description hidden of deletion. I will explain when editing js.
①
- @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 delete
The categories are displayed in the description below. The if statement is conditional on the case without grandchildren and the case with grandchildren. The contents {} are described in the collection_select tag, but this is described in relation to the order of the arguments when describing the options when assigning the id.
Category
.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"}
The following description is a view file of the transition screen when the update is successful.
haml:app/views/items/_form_edit.html.haml
= render "top/header"
.done#fullsize
.done__title
Product information has been updated
.done__backlink
= link_to 'Back to top page', root_path, class: 'link'
.done__backlink
= link_to 'Check for updated products', item_path(@item_update), class: 'link'
.done__backlink
= link_to 'View the list of products on sale', users_path, class: 'link'
= render "top/lower-photo"
= render "top/footer"
= render "top/btn"
Edit the js file as follows.
app/assets/javascript/edit_items.js
$(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){
//Read the File object specified by readAsDataURL of FileReader
var fileReader = new FileReader();
//Add file to DataTransfer object
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);
//When the number of images reaches 10, delete the box when it exceeds
if (num == 10){
$('#image-box__container').css('display', 'none')
}
//When loading is complete, store the URL of file in src
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'>Delete</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__Insert html before the container element
$('.img-label').before(html);
$('#append-js-edit').append(buildFileField1(num));
};
//image-box__Change the class of container and change the size of the drop box with CSS.
$('#image-box__container').attr('class', `item-num-${num}`)
});
});
// ④
//Delete the box when 10 sheets have been registered
$(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(){
//Get preview element pressed to delete
var target_image = $(this).parent().parent();
//Get the filename of the preview image that was pressed to delete
var target_id = $(target_image).attr('id');
var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
//Delete preview
target_image.remove()
target_image_file.remove()
//image-box__Change div tag class with container class every time you delete
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(){
//Get preview element pressed to delete
var target_image = $(this).parent().parent();
var target_id = Number($(target_image).attr('id'));
//Get the filename of the preview image that was pressed to delete
var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
//Delete preview
target_image.remove()
target_image_file.remove()
//image-box__Change div tag class with container class every time you delete
var num = $('.item-image').length
$('#image-box__container').show()
$('#image-box__container').attr('class', `item-num-${num}`)
})
Get the last saved image id in the description on the first line and assign it to the image_id variable. In the second line, count the number of tags assigned id = 1 in the div in the view file and assign them to the append_div_count variable. Add it on the third line, Assign to the noreset_id variable. noreset_id is for setting to the value option of the input tag that is newly displayed when an image is added. Use this to perform the delete operation. Also, set the id option to delete the div tag that is the parent of the preview image, aiming for the same numerical value. (②)
①
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
The following description is the HTML of the preview image generated by firing the event when the image data is input to the input tag.
②
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'>Delete</div>
</div>
</div>`
The following description is the HTML of the input tag generated by firing the event when the image data is input to the input tag.
③
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>`;
The description below is that the event will be fired when the screen is fully loaded by the ready method, and the box for inputting images will be deleted when the number of preview images is 10.
④
//Delete the box when 10 sheets have been registered
$(document).ready(function(){
var image_num = $('.item-image').length
if (image_num==10){
$('#image-box__container').css('display', 'none')
}
});
The following description is the description that the event is fired and the input tag is generated when the screen is loaded by the ready method. If you do not do this, the input to the first input tag will be input to the existing displayed input tag and will be misaligned, so it must be generated when the screen is loaded.
⑤
$(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));
});
The following description deletes the input tag and preview image in which the image data called by edit is input when you click Delete displayed at the lower left of the preview image.
⑥
$(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){
//Get preview element pressed to delete
var target_image = $(this).parent().parent();
//Get the filename of the preview image that was pressed to delete
var target_id = $(target_image).attr('id');
var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
//Delete preview
target_image.remove()
target_image_file.remove()
//image-box__Change div tag class with container class every time you delete
var num = $('.item-image').length
$('#image-box__container').show()
$('#image-box__container').attr('class', `item-num-${num}`)
})
In the description below, click Delete displayed at the bottom left of the preview image for the input tag where the called image data is input and the newly generated preview image generated when a new image is input to the input tag. It is deleted when you do.
⑦
$(document).on("click", '.item-image__operetion--edit__delete__file', function(){
//Get preview element pressed to delete
var target_image = $(this).parent().parent();
var target_id = Number($(target_image).attr('id'));
//Get the filename of the preview image that was pressed to delete
var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
//Delete preview
target_image.remove()
target_image_file.remove()
//image-box__Change div tag class with container class every time you delete
var num = $('.item-image').length
$('#image-box__container').show()
$('#image-box__container').attr('class', `item-num-${num}`)
})
The following description calculates and outputs the sales commission and sales profit when the price is entered. The edited part is the content described in the 2nd and 4th columns, and the sales commission and sales profit are calculated and displayed when the screen is loaded by the ready method.
app/assets/javascript/sales_commission.js
$(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("Circle");
}
});
$(document).ready(function(){
feeIncluded.text(Math.floor($("#item_exhibition_price").val() * fee).toLocaleString());
if($('sales_commission_price').present!=0){
sales_commission_price.append("Circle");
}
});
});
$(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("Circle");
}
});
$(document).ready(function(){
salesProfit.text(Math.ceil($("#item_exhibition_price").val() * tax).toLocaleString());
if($('sales_commission_price').present!=0){
sales_profit_proce.append("Circle");
}
});
});
This completes the editing function. Thank you for reading this far.
Recommended Posts