3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

商品出品機能のコード全文

Posted at

フリマアプリの商品出品機能

最終課題で実装した商品出品ページの全コードを記録用に書きます!

機能

・ancestry:多階層カテゴリー
・ActiveStorage:画像投稿
・jp_prefecture:都道府県を扱うGem
・active_hash:静的データ作成

Active Storageのセットアップ

下記をみて導入方法を確認する
Rails ガイド

ルーティング

routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  devise_scope :user do
    get 'sending_destinations', to: 'users/registrations#newSendingDestination'
    post 'sending_destinations', to: 'users/registrations#createSendingDestination'
  end
  root "items#index"

  resources :items, only: [:new, :create, :update] do
    collection do
      get 'get_category_child', to: 'items#get_category_child', defaults: { format: 'json' }
      get 'get_category_grandchild', to: 'items#get_category_grandchild', defaults: { format: 'json' }
    end
  end
end

コントローラー

items_controller
class ItemsController < ApplicationController
  
  def index
    @items_category = Item.where("buyer_id IS NULL AND trading_status = 0 AND category_id < 200").order(created_at: "DESC")
    @items_brand = Item.where("buyer_id IS NULL AND  trading_status = 0 AND brand_id = 1").order(created_at: "DESC")
  end

  def new
    @item = Item.new
    @item.item_imgs.new
    @category_parent = Category.where(ancestry: nil)
      # 親カテゴリーが選択された後に動くアクション
    def get_category_child
      @category_child = Category.find("#{params[:parent_id]}").children
      render json: @category_child
      #親カテゴリーに紐付く子カテゴリーを取得
    end
      # 子カテゴリーが選択された後に動くアクション
    def get_category_grandchild
      @category_grandchild = Category.find("#{params[:child_id]}").children
      render json: @category_grandchild
      #子カテゴリーに紐付く孫カテゴリーの配列を取得
    end
  end

  def create
    @item = Item.new(item_params)
    unless @item.valid?
      @item.item_imgs.new
      render :new and return
    end
   
    @item.save
    redirect_to root_path
  end


  private

  def item_params
    params.require(:item).permit(:name, :introduction, :price, :prefecture_code, :brand_id, :pref_id, :size_id, :item_condition_id, :postage_payer_id, :preparation_day_id, :postage_type_id, :category_id, :trading_status, item_imgs_attributes: [:url, :id]).merge(seller_id: current_user.id)
  end

Haml

sell.html.haml
.sell-container
  = form_for @item do |f|
    -# 画像部分
    .sell-container__content
      .sell-title
        %h3.sell-title__text
          出品画像
          %span.sell-title__require
            必須
      .sell-container__content__box
        %ul.output-box
          %div#image-input
            = f.fields_for :item_imgs do |image|
              = image.label :url, id: 'image-input__label' do
                = image.file_field :url, accept: "image/*", class: "js-file", data: {index: image.index}, style: 'display: none;'
                %pre
                  %i.fas.fa-camera.fa-lg
      .error-messages
        %p
          =@item.errors.messages[:item_imgs][0]
    -# 商品概要部分
    .sell-container__content
      .sell-title
        %h3.sell-title__text
          商品名
          %span.sell-title__require
            必須
      = f.text_field :name, {class: 'sell-container__content__name', maxlength: '40', placeholder: '商品名(必須 40文字まで)'}
      .error-messages
        %p
          =@item.errors.messages[:name][0]

      .sell-title
        %h3.sell-title__text
          商品の説明
          %span.sell-title__require
            必須
      = f.text_area :introduction,{class: 'sell-container__content__description', rows: '7', maxlength: '1000', placeholder: '商品説明'}
      .sell-container__content__word-count
        %p.error-messages
          =@item.errors.messages[:introduction][0]
        %span#word-count
          0&#47;1000

    -# 詳細部分
    .sell-container__content
      %h3.sell-sub-head 商品の詳細
      .sell-container__content__details
        .sell-title
          %h3.sell-title__text
            カテゴリー
            %span.sell-title__require
              必須
        .sell-collection_select.category
          .select_collection_select-category
            = f.collection_select :category_id, @category_parent,:id, :name, {prompt: "--"},{class: 'sell-collection_select__label', id:'parent_category'} 
        .error-messages
          %p
            =@item.errors.messages[:category_id][0]

        .sell-title
          %h3.sell-title__text
            サイズ
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :size_id, Size.all, :id, :value, {prompt: "--"} ,{class: 'sell-collection_select__label'}
        .error-messages
          %p
            =@item.errors.messages[:size_id][0]

        .sell-title
          %h3.sell-title__text
            ブランド
            %span.sell-title__require.arbitrary
              任意
        .sell-collection_select
          = f.collection_select :brand_id, Brand.all, :id, :name, {prompt: "--"} ,{class: 'sell-collection_select__label'}
        .error-messages

        .sell-title
          %h3.sell-title__text
            商品の状態
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :item_condition_id, ItemCondition.all, :id, :name, {prompt: "--"}, {class: 'sell-collection_select__label'}
        .error-messages
          %p
            =@item.errors.messages[:item_condition_id][0]

    -# 配送部分
    .sell-container__content
      %h3.sell-sub-head
        %p 配送について
      .sell-container__content__details
        .sell-title
          %h3.sell-title__text
            配送方法
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :postage_type_id, PostageType.all, :id, :name, {prompt: "--"}, {class: 'sell-collection_select__label'}
        .error-messages
          %p
            =@item.errors.messages[:postage_type_id][0]

        .sell-title
          %h3.sell-title__text
            配送料の負担
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :postage_payer_id, PostagePayer.all.map, :id, :name, {prompt: "--"}, {class: 'sell-collection_select__label'}
        .error-messages
          %p
            =@item.errors.messages[:postage_payer_id][0]

        .sell-title
          %h3.sell-title__text
            発送元の地域
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :code, :name, {include_blank: '--'}, class: 'sell-collection_select__label'
        .error-messages
          %p
            =@item.errors.messages[:prefecture_code][0]

        .sell-title
          %h3.sell-title__text
            発送までの日数
            %span.sell-title__require
              必須
        .sell-collection_select
          = f.collection_select :preparation_day_id, PreparationDay.all, :id, :value, {prompt: "--"}, {class: 'sell-collection_select__label'}
        .error-messages
          %p
            =@item.errors.messages[:preparation_day_id][0]
    -# 価格部分
    .sell-container__content
      %h3.sell-sub-head
        %p 販売価格(3009,999,999)
      .sell-container__content__price
        .sell-title
          %h3.sell-title__text
            販売価格
            %span.sell-title__require
              必須
        .sell-container__content__price__form
          = f.label :price, class: 'sell-container__content__price__form__label' do
            ¥
            = f.number_field :price, {placeholder: '0', value: '', autocomplete:"off", class: 'sell-container__content__price__form__box'}
      .error-messages#error-price
        =@item.errors.messages[:price][0]

      .sell-container__content__commission
        .sell-container__content__commission__left
          販売手数料 10%
        .sell-container__content__commission__right 
      .sell-container__content__commission
        .sell-container__content__commission__left
          販売利益
        .sell-container__content__commission__right 

      .submit-btn
        = f.submit '出品する', class: 'submit-btn--Btn'
      .submit-btn
        = link_to 'もどる', root_path, class: 'submit-btn--Btn return'
      .attention-box
        %p
          禁止されている行為および出品物を必ずご確認ください。偽ブランド品や盗品物
          などの販売は犯罪であり、法律により処罰される可能性があります。また、出品をもちまして加盟店規約に同意したことになります。

必須項目が見入力の場合、エラーメッセージが出るように設定しています。
また、住所はjp_prefectureという都道府県を扱うGemを使用しました!

*ポイント*
ボックスはf.collectionに統一して記述する。
f.selectを使用するとvalueが文字でDBに送られる為、idが0で登録される為、後々の商品詳細ページでデータを表示させる時にエラーが出ます。

JS

items.js
$(document).on('turbolinks:load', ()=> {
  // 画像用のinputを生成する関数
  const buildFileField = (index)=> {
    const html = `<input accept="image/*" class="js-file" data-index="${index}" style="display: none;", type="file" name="item[item_imgs_attributes][${index}][url]" id="item_item_imgs_attributes_${index}_url">`;
    return html;
  }
  // プレビュー用のimgタグを生成する関数
  const buildImg = (index, url)=> {
    const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`;
    return html;
  }

  // file_fieldnameに動的なindexをつける為の配列
  let fileIndex = [1,2,3,4,5,6,7,8,9,10];

  $('#image-input').on('change', '.js-file', function(e) {
    // labelタグのfor属性を変更
    $('#image-input__label').attr('for', 'item_item_imgs_attributes_' + fileIndex[0] + '_url');
    // fileIndexの先頭の数字を使ってinputを作る
    $('#image-input').append(buildFileField(fileIndex[0]));
    fileIndex.shift();
    // 末尾の数に1足した数を追加する
    fileIndex.push(fileIndex[fileIndex.length - 1] + 1)
    const targetIndex = $(this).parent().data('index');
    // ファイルのブラウザ上でのURLを取得する
    const file = e.target.files[0];
    const blobUrl = window.URL.createObjectURL(file);
    $('#image-input').before(buildImg(targetIndex, blobUrl));
  });
});

JSでは大きく分けて2つ。
①画像のプレビュー
②Inputタグの生成

get_category_child.json.jbuilder
json.array! @category_child do |child|
  json.id child.id
  json.name child.name
end
get_category_grandchild.json.jbuilder
json.array! @category_grandchild do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

モデルは割愛します。

今回は以上です。

間違いなどあればご指摘いただけますと幸いです^^

3
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?