学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Rails6.1 | 仕事売買アプリ作成 | 30 | Order

↓↓クリックして頂けると励みになります。



29 | Application Helper】 << 【ホーム】 >> 【31 | Orderの確認



クライアントが仕事を注文できるように実装します。
まずはpostgresで「uuid」を使えるように設定します。


コマンド
rails generate migration enable_pgcrypto_extension


作成された「db\migrate\20200526015502_enable_pgcrypto_extension.rb」ファイルを以下のように編集します。


記述編集 db\migrate\20200526015502_enable_pgcrypto_extension.rb
3行目に「enable_extension 'pgcrypto'」の記述を追加しています。

class EnablePgcryptoExtension < ActiveRecord::Migration[6.1]
  def change
    enable_extension 'pgcrypto'
  end
end



マイグレーションを適用します。
コマンド
rails db:migrate

Orderモデル作成



注文モデルを作成します。


コマンド
一文です。
rails g model Order due_date:date title amount:float status:bigint seller_name buyer_name gig:references buyer:references seller:references


「db\migrate\20200526020610_create_orders.rb」ファイルを以下のように編集します。


記述編集 db\migrate\20200526020610_create_orders.rb
内容を大幅に変えていますのでコードをコピーしてファイルを置き換えてください。

class CreateOrders < ActiveRecord::Migration[6.1]
  def change
    create_table :orders, id: :uuid do |t|
      t.date :due_date
      t.string :title
      t.float :amount
      t.bigint :status, default: 0
      t.string :seller_name
      t.string :buyer_name
      t.references :gig, null: true, foreign_key: true
      t.references :buyer, foreign_key: { to_table: :users }
      t.references :seller, foreign_key: { to_table: :users }

      t.timestamps
    end
  end
end



コマンド マイグレーション適用
rails db:migrate


「app\models\order.rb」ファイルを以下のように編集します。


記述編集 app\models\order.rb

class Order < ApplicationRecord
    belongs_to :gig, required: false

    belongs_to :buyer, class_name: "User"
    belongs_to :seller, class_name: "User"

    enum status: [:inprogress, :completed]
end



「app\models\gig.rb」ファイルに以下の記述を追加します。


記述追加 app\models\gig.rb(6行目)

has_many :orders



app\models\gig.rb

class Gig < ApplicationRecord
  belongs_to :user
  belongs_to :category

  has_many :pricings
  has_many :orders
  
  has_many_attached :photos

  accepts_nested_attributes_for :pricings

  validates :title, presence: { message: '空白にはできません' }
end



「app\models\user.rb」ファイルに以下の記述を追加します。


記述追加 app\models\user.rb(4行目)

  has_many :buying_orders, foreign_key: "buyer_id", class_name: "Order"
  has_many :selling_orders, foreign_key: "seller_id", class_name: "Order"



app\models\user.rb

class User < ApplicationRecord

  has_many :gigs
  has_many :buying_orders, foreign_key: "buyer_id", class_name: "Order"
  has_many :selling_orders, foreign_key: "seller_id", class_name: "Order"

  has_one_attached :avatar

  validates :full_name, presence: true, length: {maximum: 50}
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
          :confirmable, :omniauthable

  def self.from_omniauth(auth)
    user = User.where(email: auth.info.email).first

    if user
      return user
    else
      where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
        user.email = auth.info.email
        user.password = Devise.friendly_token[0, 20]
        user.full_name = auth.info.name   # ユーザーモデルに名前があると仮定
        user.image = auth.info.image # ユーザーモデルに画像があると仮定

        user.uid = auth.uid
        user.provider = auth.provider

      end
    end
  end
end


Orderコントローラー



注文コントローラを作成していきます。


「app\controllers」フォルダに「orders_controller.rb」ファイルを新規作成してください。


app\controllers\orders_controller.rb(新規作成したファイル)

class OrdersController < ApplicationController
    before_action :authenticate_user!
    
    def create
        gig = Gig.find(params[:gig_id])
        pricing = gig.pricings.find_by(pricing_type: params[:pricing_type])

        if (pricing && !gig.has_single_pricing) || (pricing && pricing.basic? && gig.has_single_pricing)
            charge(gig, pricing)
        else
            flash[:alert] = "価格が間違っています。"
        end

        redirect_to request.referrer
    end

    def selling_orders
        @orders = current_user.selling_orders
    end

    def buying_orders
        @orders = current_user.buying_orders
    end

    private

    def charge(gig, pricing)
        order = gig.orders.new
        order.due_date = Date.today() + pricing.delivery_time.days
        order.title = gig.title
        order.seller_name = gig.user.full_name
        order.seller_id = gig.user.id
        order.buyer_name = current_user.full_name
        order.buyer_id = current_user.id
        order.amount = pricing.price

        if order.save
            flash[:notice] = "発注しました。"
        else
            flash[:alert] = order.errors.full_messages.join(', ')
        end
    end
end


ルート設定



ルートの設定をします。


記述追加 config\routes.rb
22行目に「resources :orders, only: [:create]」の記述を追加しています。

Rails.application.routes.draw do

  # ルートを app\views\pages\home.html.erb に設定
  root 'pages#home'

  devise_for :users, 
              path: '', 
              path_names: {sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout'},
              controllers: {omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'}

  get 'pages/home'
  get '/dashboard', to: 'users#dashboard'
  get '/users/:id', to: 'users#show'

  post '/users/edit', to: 'users#update'

  resources :gigs do
    member do
      delete :delete_photo
      post :upload_photo
    end
    resources :orders, only: [:create]
  end

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end


ビュー



「app\views\gigs\show.html.erb」ファイルの記述を更新します。
仕事を依頼するボタンを実装します。


記述更新 app\views\gigs\show.html.erb
18行目と68行目の記述を以下に書き換えます。

  <%= form_for([@gig, @gig.orders.new]) do |f| %>
            <%= hidden_field_tag 'pricing_type', p.pricing_type %>
            <%= f.submit "仕事を依頼する(#{number_to_currency(p.price)})", class: "btn btn-danger w-100", data: {confirm: "本当によろしいですか?"} %>
  <% end %>       



app\views\gigs\show.html.erb

<div class="container">
    <div class="mt-4 mb-4">
        <%= render 'shared/categories' %>
    </div>
    <div class="row">
        <!--  左側 -->
        <div class="col-md-4">
            <% if @gig.has_single_pricing %>
                <h5 class="font2 bg-light p-2" style="border-radius: 10px;"><%= @gig.title %></h5>
                <div class="card">
                    <div class="card-body">
                        <% @gig.pricings.each do |p| %>
                            <% if p.title %>
                                <div class="card-title font1">シングルプラン <span class="badge bg-danger"><%= number_to_currency(p.price) %></span></div>                            
                                <div class="font2"><%= p.description %></div>
                                <div><i class="far fa-clock"></i><span class="font2">期日:<%= p.delivery_time %></span></div>                                             
                                <div class="mt-4">
                                    <% if (!user_signed_in? && @gig.active) || (user_signed_in? && @gig.active && @gig.user_id != current_user.id) %>

                                        <%= form_for([@gig, @gig.orders.new]) do |f| %>
                                            <%= hidden_field_tag 'pricing_type', p.pricing_type %>
                                            <%= f.submit "仕事を依頼する(#{number_to_currency(p.price)})", class: "btn btn-danger w-100", data: {confirm: "本当によろしいですか?"} %>
                                        <% end %>                                    

                                    <% else %>
                                        <button class="btn btn-danger" disabled>ご利用できません</button>  
                                    <% end %>
                                </div>
                            <% end %>
                        <% end %>
                    </div>
                </div>
            <% else %>
                <ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
                    <h5 class="font2 bg-light p-2" style="border-radius: 10px;"><%= @gig.title %></h5>            
                    <% Pricing.pricing_types.each do |key, value| %>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link <%= 'active' if value == 0 %>" id="pills-<%= key %>-tab" data-bs-toggle="pill" data-bs-target="#pills-<%= key %>" type="button" role="tab" aria-controls="pills-<%= key %>" aria-selected="<%= 'true' if value == 0 %><%= 'false' if !value == 0 %>">

                                <% if value == 0 %>
                                    ベーシック
                                <% elsif value == 1 %>
                                    スタンダード
                                <% else %>
                                    プレミアム
                                <% end %>

                            </button>
                        </li>
                    <% end %>
                </ul>
                <div class="tab-content" id="pills-tabContent">
                    
                    <% @gig.pricings.each do |p| %>
                        <div class="tab-pane fade <%= 'show active' if p.pricing_type == 'basic' %>" id="pills-<%= p.pricing_type %>" role="tabpanel" aria-labelledby="pills-<%= p.pricing_type %>-tab" tabindex="0">
                        
                            <div class="card">
                                <div class="card-body">
            
                                    <div class="card-title font1"><%= p.title %> <span class="badge bg-danger"><%= number_to_currency(p.price) %></span></div>
                                                                                                        
                                    
                                    <div class="font2"><%= p.description %></div>
                                    <div><i class="far fa-clock"></i><span class="font2">期日:<%= p.delivery_time %></span></div>                                             

                                    <div class="mt-4">
                                    <% if (!user_signed_in? && @gig.active) || (user_signed_in? && @gig.active && @gig.user_id != current_user.id) %>
                                        <%= form_for([@gig, @gig.orders.new]) do |f| %>
                                            <%= hidden_field_tag 'pricing_type', p.pricing_type %>
                                            <%= f.submit "仕事を依頼する(#{number_to_currency(p.price)})", class: "btn btn-danger w-100", data: {confirm: "本当によろしいですか?"} %>
                                        <% end %>       
                                        
                                    <% else %>
                                        <button class="btn btn-danger" disabled>ご利用できません</button>  
                                    <% end %>
                                    </div>
                                    
                                </div>
                            </div>
                        </div>
                    <% end %>
                </div>
            <% end %>           
        </div>

        <!--右側 -->
        <div class="col-md-8">
            <div class="card mt-4 mb-4">
                <div class="card-body">
                    <h5 class="font1">フリーランサー</h5>
                    <div class="mt-2">
                    <%= link_to user_path(@gig.user), style: "text-decoration:none;" do %>
                        <%= image_tag avatar_url(@gig.user), class: "bd-placeholder-img figure-img img-fluid rounded-pill", style: "width: 80px;" %>
                        <span class="font2 text-dark h4"><%= @gig.user.full_name %></span>
                    <% end %>
                    </div>
                    <div class="font2">
                        <%= @gig.summary %>
                    </div>
                </div>
            </div>
            <!-- カルーセル表示 -->
            <div class="card">
                <div class="card-body">

                    <div id="carouselExampleIndicators" class="carousel slide" data-bs-ride="carousel">
                        <div class="carousel-indicators">
                            <% @photos.each do |photo| %>
                                <button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="<%= @i %>" class="<%= 'active' if photo.id == @photos[0].id %>" aria-current="true" aria-label="Slide <%= @i+1 %>"></button>
                                <% @i = @i +1 %>
                            <% end %>
                        </div>
                        <div class="carousel-inner">
                            <%  @gig.photos.each do |photo| %>
                                <div class="carousel-item <%= 'active' if photo.id == @photos[0].id %>">
                                    <%= image_tag url_for(photo), class: "d-block w-100", style: "border-radius: 10px;" %>
                                </div>
                            <% end %>
                        </div>
                        <button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="prev">
                            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
                            <span class="visually-hidden">Previous</span>
                        </button>
                        <button class="carousel-control-next" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="next">
                            <span class="carousel-control-next-icon" aria-hidden="true"></span>
                            <span class="visually-hidden">Next</span>
                        </button>
                    </div>
                </div>
            </div>

            <!-- Youtube表示 -->
            <% if @gig.video.present? %>
            <div class="card mt-4">
                <iframe height="360" src="https://www.youtube.com/embed/<%= @gig.video %>" allowfullscreen></iframe>
            </div>
            <% end %>
        </div>

    </div>
</div>



ブラウザ確認
http://localhost:3000/gigs/1


お仕事を登録したアカウントと別のアカウントでログインして注文してみます。


もしJavascriptが上手く動かないようでしたら、「app/views/layouts/application.html.erb」ファイルのheadタグ内に以下のCDNの記述を追加してください。

<script src="https://cdn.jsdelivr.net/npm/@rails/ujs@7.1.1/app/assets/javascripts/rails-ujs.min.js"></script>


PCレイアウト
PCレイアウト


モバイルレイアウト
モバイルレイアウト



Posticoでオーダーを確認します。

オーダーテーブル
オーダーテーブル



29 | Application Helper】 << 【ホーム】 >> 【31 | Orderの確認


↓↓クリックして頂けると励みになります。