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

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

Rails6.0 | 仕事売買サイトの構築 | 29 | 申し込み

[28]仕事のリクエスト << [ホームに戻る] >> [30]仕事を受ける


お仕事を売る人がリクエストに申し込み出来るようにします。


オファーモデルを作成します。


コマンド
一文です。
rails g model Offer note:text amount:bigint days:bigint status:bigint request:references user:references --no-test-framework


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


記述編集 db\migrate\20200527004950_create_offers.rb
7行目に「, default: 0」の記述を追加しています。

class CreateOffers < ActiveRecord::Migration[6.0]
  def change
    create_table :offers do |t|
      t.text :note
      t.bigint :amount
      t.bigint :days
      t.bigint :status, default: 0
      t.references :request, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end



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


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


記述編集 app\models\offer.rb

class Offer < ApplicationRecord
  belongs_to :request
  belongs_to :user

  enum status: [:pending, :accepted, :rejected]
  validates :amount, :days, numericality: { only_integer: true, message: "数字でなければなりません" }
end



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


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

has_many :offers



app\models\user.rb

class User < ApplicationRecord

  has_many :gigs
  has_many :requests
  has_many :offers
  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

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, 
         :omniauthable, :confirmable

  validates :full_name, presence: true, length: {maximum: 50}      

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

    if user
      if !user.provider
        user.update(uid: auth.uid, provider: auth.provider, image: auth.info.image)
      end
      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



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


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

has_many :offers, dependent: :delete_all



app\models\request.rb

class Request < ApplicationRecord

  belongs_to :user
  belongs_to :category

  has_many :offers, dependent: :delete_all

  has_one_attached :attachment_file

  validates :title, presence: { message: "空白にはできません" }
  validates :description, presence: { message: "空白にはできません" }
  validates :delivery, numericality: { only_integer: true, message: "数字でなければなりません" }
  
end



オファーコントローラを作成します。


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


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

class OffersController < ApplicationController
    before_action :authenticate_user!
    before_action :set_offer, only: [:accept, :reject]
    before_action :is_authorised, only: [:accept, :reject]

    def create
        req = Request.find(offer_params[:request_id])

        if req && req.user_id == current_user.id
            redirect_to request.referrer, alert: "自分のリクエストに申し込みはできません。"
        
        elsif Offer.exists?(user_id: current_user.id, request_id: offer_params[:request_id])
            redirect_to request.referrer, alert: "申し込みできるのは1回だけです"
        
        else
            @offer = current_user.offers.build(offer_params)
            if @offer.save
                redirect_to request.referrer, notice: "保存しました"
                
            else
                redirect_to request.referrer, flash: {error: @offer.errors.full_messages.join(', ')}
            end
        end
    end

    def accept

    end

    def reject

    end

    private

    def set_offer
        @offer = Offer.find(params[:id])
    end

    def is_authorised
        redirect_to root_path, alert: "権限がありません。" unless current_user.id == @offer.request.user_id
    end

    def offer_params
        params.require(:offer).permit(:amount, :days, :note, :request_id, :status)
    end
end



ルートの設定をします。


記述追加 config\routes.rb
19行目に「post '/offers', to: 'offers#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'
  get '/selling_orders', to: 'orders#selling_orders'
  get '/buying_orders', to: 'orders#buying_orders'
  get '/all_requests', to: 'requests#list'

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

  put '/orders/:id/complete', to: 'orders#complete', as: 'complete_order'

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

  resources :requests

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



「app\views\requests\show.html.erb」ファイルに以下の記述を追加します。


記述追加 app\views\requests\show.html.erb(105行目)

<div class="card">
    <%= form_for Offer.new do |f| %>
        
        <div class="card-content">
            <%= f.hidden_field :request_id, value: @request.id %>
            <div class="field">
                <label for="" class="label">価格 (円)</label>
                <%= f.text_field :amount, class: "input" %>
            </div>
            <div class="field">
                <label for="" class="label">期日 (日)</label>
                <%= f.text_field :days, class: "input" %>
            </div>
            <div class="field">
                <label for="" class="label">申し込みコメント</label>
                <%= f.text_area :note, class: "textarea" %>
            </div>
            <%= f.submit '申し込みする', class: "button is-primary is-fullwidth" %>

        </div>

    <% end %>
</div>



app\views\requests\show.html.erb

<section class="section">
    <div class="container">
        <div class="columns">

            <!-- 左側 -->
            <div class="column is-two-thirds">
                <div class="columns is-multiline">

                    <!-- タイトル -->
                    <div class="column is-full">
                        <p class="title is-4"><%= @request.title %></p>
                        <article class="media">
                            <div class="media-content">
                                <p>
                                    カテゴリー:<%= @request.category.name %>
                                    <span class="is-pulled-right">リクエスト日: <%= I18n.l(@request.created_at, format: :full_date) %></span>
                                </p>
                                期日: <%= @request.delivery %> 日 | 申し込み: 0 | 予算: <%= number_to_currency(@request.budget) %>
                            </div>
                        </article>
                    </div>

                    <!-- 内容 -->
                    <div class="column is-full">
                        <div class="card">
                            <div class="card-content">

                                <strong>内容</strong>
                                <hr>
                                <%= @request.description %>
                                <% if @request.attachment_file.attached? %>
                                    <p>
                                        <%= link_to url_for(@request.attachment_file), 
                                                class: "tag small is-warning m-t-20",
                                                download: "Attachment_#{@request.attachment_file.id}" do %>
                                            <i class="fas fa-paperclip fa-lg p-r-5"></i><%= @request.attachment_file.filename %>
                                        <% end %>
                                    </p>
                                <% end %>
                            </div>
                        </div>
                    </div>

                    <!-- 買い手について -->
                    <div class="column">
                        <div class="card">
                            <div class="card-content">

                                <strong>買い手について</strong> 
                                <hr/>
                                <div class="content">
                                    <div class="columns">

                                        <!-- 買い手のアバター -->
                                        <div class="column">
                                            <div class="card-content is-horizontal-center is-flex">
                                                <figure class="image is-256x256">
                                                    <%= image_tag avatar_url(@request.user), class: "is-rounded" %>
                                                </figure>
                                            </div>
                                            <div class="content has-text-centered">
                                                <p class="title is-5"><%= @request.user.full_name %></p>
                                                <button class="button is-black is-outlined is-fullwidth">メッセージを送る</button>
                                            </div>
                                        </div>

                                        <!-- 買い手のプロフィール -->
                                        <div class="column f-15">
                                            <article class="media">
                                                <div class="media-content">
                                                    <i class="fas fa-user m-r-5"></i> アカウント登録日
                                                </div>
                                                <div class="media-right">
                                                    <%= I18n.l(@request.user.created_at, format: :full_date) %>
                                                </div>
                                            </article>
                                            <article class="media">
                                                <div class="media-content">
                                                    <i class="fas fa-map-marker-alt m-r-5"></i> 出身地
                                                </div>
                                                <div class="media-right">
                                                    <%= @request.user.from %>
                                                </div>
                                            </article>
                                            <article class="media">
                                                <div class="media-content">
                                                    <%= @request.user.about %>
                                                </div>
                                            </article>
                                            
                                        </div>
                                    </div>
                                </div>
                                    
                            </div>
                        </div>
                    </div>
                    
                </div>
            </div>

            <!-- 右側 -->
            <div class="column">

                <div class="card">
                    <%= form_for Offer.new do |f| %>
                        
                        <div class="card-content">
                            <%= f.hidden_field :request_id, value: @request.id %>
                            <div class="field">
                                <label for="" class="label">価格 (円)</label>
                                <%= f.text_field :amount, class: "input" %>
                            </div>
                            <div class="field">
                                <label for="" class="label">期日 (日)</label>
                                <%= f.text_field :days, class: "input" %>
                            </div>
                            <div class="field">
                                <label for="" class="label">申し込みコメント</label>
                                <%= f.text_area :note, class: "textarea" %>
                            </div>
                            <%= f.submit '申し込みする', class: "button is-primary is-fullwidth" %>
                        </div>
                    <% end %>
                </div>            

            </div>
        </div>
    </div>
</section>



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


申込みができるか確認します。

申し込みを確認
申し込みを確認


「app\views\requests\list.html.erb」ファイルを以下のように編集します。


記述編集 app\views\requests\list.html.erb(52行目)

<td class="has-text-right"><%= r.offers.count %></td>



app\views\requests\list.html.erb

<section class="section">
    <div class="container">
        <p class="title">
            全てのリクエスト
        </p>
        <div class="card">
            <div class="card-header">
                <%= form_tag '', method: :get do %>
                    <div class="field p-10">
                        <div class="select">
                            <%= select_tag 'category', content_tag(:option, '全てのカテゴリー', value: "") + 
                                                        options_for_select(@categories.map { |c| [c.name, c.id] }, 
                                                        selected: @category_id),
                                                        onchange: "this.form.submit();" %>
                        </div>
                    </div>
                <% end %>
            </div>
            <div class="card-content">
                <table class="table is-fullwidth">
                    <thead>
                        <tr>
                            <th>リクエスト日</th>
                            <th>買い手</th>
                            <th>リクエスト</th>
                            <th class="has-text-centered">申し込み</th>
                            <th class="has-text-centered">期日</th>
                            <th class="has-text-centered">予算</th>
                        </tr>
                    </thead>
                    <tbody>
                        <% if @requests.blank? %>
                            <tr>
                                <td colspan="6" class="has-text-centered"><h1>表示できるリクエストがありません</h1></td>
                            </tr>
                        <% end %>
                        <% @requests.each do |r| %>
                            <tr>
                                <td><%= I18n.l(r.created_at, format: :full_date) %></td>
                                <td>
                                    <figure class="image is-48x48">
                                        <%= image_tag avatar_url(r.user), class: "is-rounded" %>
                                    </figure>
                                </td>
                                <td>
                                    <%= link_to request_path(r), class: "tootip" do %>
                                        <label for="" class="tooltip" data-tooltip="<%= r.description %>">
                                            <%= r.title.truncate(25, seperator: ' ') %>
                                        </label>
                                    <% end %>
                                </td>
                                <td class="has-text-right"><%= r.offers.count %></td>
                                <td class="has-text-right"><%= r.delivery %></td>
                                <td class="has-text-right"><%= number_to_currency(r.budget) %></td>
                            </tr>
                        <% end %>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</section>



ブラウザ確認
http://localhost:3000/all_requests


申込数が表示されるようになりました。

申し込み数表示
申し込み数表示


「app\views\requests\show.html.erb」ファイルに以下の記述を追加します。


記述追加 app\views\requests\show.html.erb(18行目)

期日: <%= @request.delivery %> 日 | 申し込み: <%= @request.offers.count %> | 予算: <%= number_to_currency(@request.budget) %>



app\views\requests\show.html.erb

<section class="section">
    <div class="container">
        <div class="columns">

            <!-- 左側 -->
            <div class="column is-two-thirds">
                <div class="columns is-multiline">

                    <!-- タイトル -->
                    <div class="column is-full">
                        <p class="title is-4"><%= @request.title %></p>
                        <article class="media">
                            <div class="media-content">
                                <p>
                                    カテゴリー:<%= @request.category.name %>
                                    <span class="is-pulled-right">リクエスト日: <%= I18n.l(@request.created_at, format: :full_date) %></span>
                                </p>
                                期日: <%= @request.delivery %> 日 | 申し込み: <%= @request.offers.count %> | 予算: <%= number_to_currency(@request.budget) %>
                            </div>
                        </article>
                    </div>

                    <!-- 内容 -->
                    <div class="column is-full">
                        <div class="card">
                            <div class="card-content">
                                <strong>内容</strong>
                                <hr>
                                <%= @request.description %>
                                <% if @request.attachment_file.attached? %>
                                    <p>
                                        <%= link_to url_for(@request.attachment_file), 
                                                class: "tag small is-warning m-t-20",
                                                download: "Attachment_#{@request.attachment_file.id}" do %>

                                            <i class="fas fa-paperclip fa-lg p-r-5"></i><%= @request.attachment_file.filename %>
                                        <% end %>
                                    </p>
                                <% end %>
                            </div>
                        </div>
                    </div>

                    <!-- 買い手について -->
                    <div class="column">
                        <div class="card">
                            <div class="card-content">
                                <strong>買い手について</strong> 
                                <hr/>

                                <div class="content">
                                    <div class="columns">

                                        <!-- 買い手のアバター -->
                                        <div class="column">
                                            <div class="card-content is-horizontal-center is-flex">
                                                <figure class="image is-256x256">
                                                    <%= image_tag avatar_url(@request.user), class: "is-rounded" %>
                                                </figure>
                                            </div>
                                            <div class="content has-text-centered">
                                                <p class="title is-5"><%= @request.user.full_name %></p>
                                                <button class="button is-black is-outlined is-fullwidth">メッセージを送る</button>
                                            </div>
                                        </div>

                                        <!-- 買い手のプロフィール -->
                                        <div class="column f-15">

                                            <article class="media">
                                                <div class="media-content">
                                                    <i class="fas fa-user m-r-5"></i> アカウント登録日
                                                </div>
                                                <div class="media-right">
                                                    <%= I18n.l(@request.user.created_at, format: :full_date) %>
                                                </div>
                                            </article>
                                            <article class="media">
                                                <div class="media-content">
                                                    <i class="fas fa-map-marker-alt m-r-5"></i> 出身地
                                                </div>
                                                <div class="media-right">
                                                    <%= @request.user.from %>
                                                </div>
                                            </article>
                                            <article class="media">
                                                <div class="media-content">
                                                    <%= @request.user.about %>
                                                </div>
                                            </article>
                                            
                                        </div>

                                    </div>
                                </div>

                                    
                            </div>
                        </div>
                    </div>
                    
                </div>
            </div>

            <!-- 右側 -->
            <div class="column">

                <div class="card">
                    <%= form_for Offer.new do |f| %>
                        
                        <div class="card-content">
                            <%= f.hidden_field :request_id, value: @request.id %>
                            <div class="field">
                                <label for="" class="label">価格 (円)</label>
                                <%= f.text_field :amount, class: "input" %>
                            </div>
                            <div class="field">
                                <label for="" class="label">期日 (日)</label>
                                <%= f.text_field :days, class: "input" %>
                            </div>
                            <div class="field">
                                <label for="" class="label">申し込みコメント</label>
                                <%= f.text_area :note, class: "textarea" %>
                            </div>
                            <%= f.submit '申し込みする', class: "button is-primary is-fullwidth" %>

                        </div>

                    <% end %>
                </div>


            </div>

        </div>
    </div>
</section>



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


申込数表示
申込数表示


リクエストの確認でも申し込み数を表示できるようにします。


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


記述追加 app\controllers\requests_controller.rb(53行目)

  def offers
    @offers = @request.offers
  end



app\controllers\requests_controller.rb

class RequestsController < ApplicationController

  before_action :authenticate_user!
  before_action :set_request, except: [:new, :create, :index, :list]
  before_action :is_authorised, only: [:edit, :update, :destroy]
  before_action :set_categories, only: [:new, :edit, :list]
 
  def index
    @requests = current_user.requests
  end

  def new
    @request = current_user.requests.build
  end

  def create
    @request = current_user.requests.build(request_params)
    if @request.save
      redirect_to requests_path, notice: "保存しました"
    else
      redirect_to request.referrer, flash: {error: @request.errors.full_messages.join(', ')}
    end
  end

  def edit
  end

  def update
    if @request.update(request_params)
      redirect_to requests_path, notice: "保存しました"
    else
      redirect_to request.referrer, flash: {error: @request.errors.full_messages.join(', ')}
    end
  end

  def show
  end

  def destroy
    @request.destroy
    redirect_to requests_path, notice: "削除しました"
  end

  def list
    @category_id = params[:category]
    if @category_id.present?
      @requests = Request.where(category_id: @category_id)
    else
      @requests = Request.all
    end
  end

  def offers
    @offers = @request.offers
  end  

  private

  def set_categories
    @categories = Category.all
  end

  def set_request
    @request = Request.find(params[:id])
  end

  def is_authorised
    redirect_to root_path, alert: "あなたに権限はありません。" unless current_user.id == @request.user_id
  end

  def request_params
    params.require(:request).permit(:description, :category_id, :delivery, :budget, :attachment_file, :title)
  end
  
end



ルートを設定します。


記述追加 config\routes.rb
17行目に「get '/request_offers/:id', to: 'requests#offers', as: 'request_offers'」の記述を追加します。

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'
  get '/selling_orders', to: 'orders#selling_orders'
  get '/buying_orders', to: 'orders#buying_orders'
  get '/all_requests', to: 'requests#list'
  get '/request_offers/:id', to: 'requests#offers', as: 'request_offers'

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

  put '/orders/:id/complete', to: 'orders#complete', as: 'complete_order'

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

  resources :requests

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



「app\views\request」フォルダに「offers.html.erb」ファイルを新規作成します。


app\views\requests\offers.html.erb(新規作成したファイル)

<section class="section">
    <div class="container">
        <p class="title">お申込みリスト</p>
        <table class="table is-fullwidth">
            <thead>
                <tr>
                    <th>申込み日</th>
                    <th>売り主</th>
                    <th>申し込みコメント</th>
                    <th>価格</th>
                    <th>期日</th>
                    <th>ステータス</th>
                    <th>アクション</th>
                </tr>
            </thead>
            <tbody>
                <% if @offers.blank? %>
                    <tr>
                        <td colspan="7" class="has-text-centered"><h1>表示できる申込みはありません。</h1></td>
                    </tr>
                <% end %>
                <% @offers.each do |o| %>
                    <tr>
                        <td><%= I18n.l(o.created_at, format: :full_date) %></td>
                        <td>
                            <figure class="image is-48x48">
                                <%= image_tag avatar_url(o.user), class: "is-rounded" %>
                            </figure>
                        </td>
                        <td><%= o.note %></td>
                        <td><%= number_to_currency(o.amount) %></td>
                        <td><%= o.days %></td>
                        <td>
                            <span class="tag <%= 'is-warning' if o.pending? %> 
                                            <%= 'is-success' if o.accepted? %>
                                            <%= 'is-danger' if o.rejected? %> ">
                                <% if o.pending? %>
                                    選考中
                                <% elsif o.accepted? %>
                                    お仕事をお願いしました
                                <% else %>
                                    お断りしました
                                <% end %>
                            </span>
                        </td>
                        <td>
                            <%= link_to 'お仕事をお願いする', nil, class: "button is-small is-success" %>
                            <%= link_to 'お断りする', nil, class: "button is-small is-danger" %>
                        </td>
                    </tr>
                <% end %>
            </tbody>
        </table>
    </div>
</section>



「app\views\requests\index.html.erb」ファイルに以下の記述を追加します。


記述追加 app\views\requests\index.html.erb(27行目)

<td class="has-text-right">
    <%= link_to r.offers.count, request_offers_path(r) %>
</td>



app\views\requests\index.html.erb

<section class="section">
    <div class="container">
        <p class="title">
            リクエストの確認
            <%= link_to '新しいリクエストを登録する', new_request_path, class: "button is-primary is-pulled-right"%>
        </p>
        <table class="table is-fullwidth">
            <thead class="is-centered">
                <tr>
                    <td>リクエスト日</td>
                    <td>タイトル</td>
                    <td class="has-text-right">申し込み</td>
                    <td class="has-text-right">アクション</td>
                </tr>
            </thead>
            <tbody>
                <% if @requests.blank? %>
                    <tr>
                        <td colspan="4" class="has-text-centered"><h1>表示できるリクエストがありません</h1></td>
                    </tr>
                <% end %>
                <% @requests.each do |r| %>
                    <tr>
                        <td><%= I18n.l(r.created_at, format: :full_date) %></td>
                        <td><%= link_to r.title, request_path(r) %></td>

                        <td class="has-text-right">
                            <%= link_to r.offers.count, request_offers_path(r) %>
                        </td>

                        <td class="has-text-right">
                            <%= link_to edit_request_path(r), class: "m-r-5" do %>
                                <span class="icon has-text-success">
                                    <i class="far fa-edit fa-lg"></i>
                                </span>
                            <% end %>
                            <%= link_to r, method: :delete, data: {confirm: "削除してよろしいですか?"} do %>
                                <span class="icon has-text-danger">
                                    <i class="far fa-trash-alt fa-lg"></i>
                                </span>
                            <% end %>
                        </td>
                    </tr>
                <% end %>
            </tbody>
        </table>
    
    </div>
</section>



ブラウザ確認
http://localhost:3000/requests


申し込みの確認ができるリンクができました。

申し込み確認リンク
申し込み確認リンク


リンクをクリックすると申込みのリストが表示されます。
まだアクションは機能しません。

申し込みリスト
申し込みリスト



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


[28]仕事のリクエスト << [ホームに戻る] >> [30]仕事を受ける