event_gakuseis コントローラの作成(1) - 不定期刊 Rails App を作る(36)

RailsApp Rails 2018年 12月 25日

events/event_gakuseis のコントローラは index と update のみとなる. このコントローラでは,AJAX での画面遷移を意識して,id を配置している.

event_gakuseis コントローラの追加

  1. generator で events/event_gakuseis コントローラを作成する.
  2. $ bin/rails g controller events/event_gakuseis
    Running via Spring preloader in process 30317
          create  app/controllers/events/event_gakuseis_controller.rb
          invoke  haml
          create    app/views/events/event_gakuseis
          invoke  rspec
          invoke  assets
          invoke    js
          invoke    css
  3. generator で integration_test を作成する. 前と同様に,event_gakuseis で spec を作成後にフォルダを移動することにする.
  4. $ bin/rails g integration_test event_gakusei
    Running via Spring preloader in process 30351
          invoke  rspec
          create    spec/requests/event_gakuseis_spec.rb
  5. event_gakuseis_spec.rb を spec/requests/events の下に移動する.
  6. mkdir -p spec/requests/events; mv spec/requests/event_gakuseis_spec.rb spec/requests/events
  7. routes に events/event_gakuseis の項目を追加する.
  8. Rails.application.routes.draw do
      (中略)
      resources :events, only: [] do
        resources :event_gakuseis, only: %i[index update], controller: 'events/event_gakuseis'
      end

event_gakuseis_spec の記述(index のみ)

  1. spec/requests/events/event_gakuseis_spec.rb を修正する. ひな形からほぼ自動で作成されるので,index 以外の場所を if false で実行しないようにしておく.
  2. require 'rails_helper'
    
    RSpec.describe :EventGakuseis, type: :request do
      include Devise::Test::IntegrationHelpers
    
      let(:object) { event_gakusei_factory :start_foo }
      let!(:gakuseis) { gakusei_factories %i[foo2301 bar2302] }
      let(:not_mine) { event_gakusei_factory :hr_baz3301 }
      let!(:event) { event_factory :start2300_0406 }
    
      let(:return_path) { event_event_gakuseis_path(edit_mode: true) }
      context 'login by hkob' do
        teacher_login :hkob
    
        describe 'GET #index' do
          context 'owned event' do
            subject { -> { get event_event_gakuseis_path(event) } }
            it_behaves_like :response_status_check, 200
            it_behaves_like :response_body_includes, %w[始業 2301 foo]
          end
    
          context 'not owned event' do
            subject { -> { get event_event_gakuseis_path(not_mine.event) } }
            let(:return_path) { kurasus_path }
            it_behaves_like :response_status_check, 302
            it_behaves_like :response_body_not_includes, %w[始業 2301 foo]
            it_behaves_like :flash_alert_message, 'イベントを表示する権限がありません.'
          end
        end
    
        if false
          (中略)
        end
      end
    
      context 'not login' do
        describe 'GET #index' do
          subject { -> { get event_event_gakuseis_path(event) } }
          it_behaves_like :response_status_check, 302
          it_behaves_like :response_body_not_includes, %w[始業 2301 foo]
        end
      end
    end

events/event_gakuseis_controller の記述(index のみ)

  1. app/controllers/events/event_gakuseis_controller.rb を記述する. 最初の表示の時のみ,@kurasu.gakuseis から event_gakuseis を自動生成するようにしている.
  2. class Events::EventGakuseisController < ApplicationController
      before_action :authenticate_teacher!
      before_action :get_event
    
      def index
        redirect_to(kurasus_path, alert: t('.not_my_event')) and return if @event.nil?
        @kesseki_mode = true if params[:kesseki_mode]
        @event_gakuseis = @event.event_gakuseis.order_number
        if @event_gakuseis.count == 0
          @event_gakuseis = []
          @kurasu = @event.kurasu
          @kurasu.gakuseis.order_number.each do |g|
            @event_gakuseis << @event.event_gakuseis.create(gakusei_id: g.id, shusseki: EventGakusei.shussekis[:unsettled]) if g.event_gakuseis.event_is(@event).count == 0
          end
        end
      end
    
      def get_event
        @event = Event.find_by_id(params[:event_id])
        @event = nil if @event && @event.kurasu.teacher != current_teacher
      end
      private :get_event
    end
  3. event_gakusei で event をフィルタする event_is という scope を用意する.
  4. scope :event_is, -> e { where arel_table[:event_id].eq e.id }

index の view を作成

  1. app/views/events/event_gakuseis/index.html.haml を記述する. 未設定,出席,欠席のそれぞれで学生ごとにIDを設定し,その内部は partial で描画を行う.
  2. - content_for :title do
      - @title = "#{t '.title'}: #{@event.name}, #{@event.kurasu.name}"
    
    .row
      - EventGakusei.shussekis.each do |key_array|
        - key = key_array.first
        .column{class: key}
          - key_title = t_ar EventGakusei, "shusseki.#{key}"
          - mode = key_array.last
          - case mode
          - when 0
            .button.button-outline= key_title
          - when 1
            - if @kesseki_mode
              = link_to key_title, event_event_gakuseis_path(@event), class: 'button button-outline'
            - else
              .button= key_title
          - when 2
            - if @kesseki_mode
              .button= key_title
            - else
              = link_to key_title, event_event_gakuseis_path(@event, kesseki_mode: true), class: 'button button-outline'
          %br
          - @event_gakuseis.each do |eg|
            - g = eg.gakusei
            %div{id: "#{g.id}_#{mode}"}
              = render partial: 'index_sub.html', locals: {event_gakusei: eg, kesseki_mode: @kesseki_mode, mode: mode}
  3. partial の中身はデータが存在するときに,link_to を描画する. link_to のリンク先は,未設定の場合には kesseki_mode の値にしたがって,出席・欠席へのリンクを設定し,出席・欠席の場合には未設定へのリンクを設定する.
  4. - event = event_gakusei.event
    - gakusei = event_gakusei.gakusei
    - if event_gakusei.shusseki_before_type_cast == mode
      = link_to gakusei.number_name, event_event_gakusei_path(event, event_gakusei, event_gakusei: {shusseki: mode == 0 ? kesseki_mode ? :kesseki : :shusseki : :unsettled}, kesseki_mode: kesseki_mode), class: :button, method: :put, remote: true
  5. app/models/gakusei.rb に number_name というコンビニエンスメソッドを追加しておいた.
  6. # @return [String] 学生番号と氏名を返す
    def number_name
      "#{number} #{name}"
    end

locale 文字列の用意

  1. model の locale はいつものように config/locales/models.ja.yml に追加しておく. 今回は,event_gakusei に enum を追加しているため,event_gakuse/shusseki として文字列を追加している. こうすることで,t_ar EventGakusei, 'shusseki.unsettled' のように enum の翻訳文字列へアクセスできる.
  2. ja:
      activerecord:
        models:
          event_gakusei: イベント学生
          (中略)
        attributes:
          event_gakusei:
            shusseki: 出席
          event_gakusei/shusseki:
            unsettled: 未設定
            shusseki: 出席
            kesseki: 欠席
  3. view には locale 用のキーワードしか書いていないので,それらの文字列を config/locales/views.ja.yml に追加しておく.
  4. ja:
      events:
        event_gakuseis:
          index:
            title: 出席簿
            not_my_event: イベントを表示する権限がありません.

guard の結果

  1. guard の結果は以下のようになった
  2. EventGakuseis
      login by hkob
        GET #index
          owned event
            behaves like response_status_check
              response should be 200
            behaves like response_body_includes
              response body includes ["始業", "2301", "foo"]
          not owned event
            behaves like response_status_check
              response should be 302
            behaves like response_body_not_includes
              response body does not include ["始業", "2301", "foo"]
            behaves like flash_alert_message
              should eq "イベントを表示する権限がありません."
      not login
        GET #index
          behaves like response_status_check
            response should be 302
          behaves like response_body_not_includes
            response body does not include ["始業", "2301", "foo"]

長くなったので今日はここまで