📘 hkob-astro-notion-blog

これまではてなブログにて情報発信をしていましたが、令和5年3月22日より、こちらでの情報発信を始めました。2019年以前の古い記事は過去の Middleman 時代のものなので、情報が古いです。記録のためだけに残しています。

spec template の設置と Kurasu モデルの作成 - 不定期刊 Rails App を作る(14)

💡
この記事は Middleman 時代に書いた古いものです。記録のため、astro-notion-blog に移行していますが、あまり参考にしないでください。
model_spec.rb の設置

昨日手動で設置した model の spec を生成するためのテンプレートを設置する.

  1. lib/templates/rspec/model フォルダを作成する.
    mkdir -p lib/templates/rspec/model
  2. lib/teamplates/rspec/model/model_spec.rb を以下のように記載する. model 名で変更になる部分は erb で記述されている. class_name はクラス名(先頭が大文字),singular_table_name はテーブル名の小文字,plural_table_name はテーブル名の小文字の複数形である.
    <%= class_name %>FactoryHash = {
      one: %w[],
      two: %w[],
      three: %w[]
    }
    
    # @param [Symbol, String] key オブジェクトを一意に決定するキー
    # @return [<%= class_name %>] <%= class_name %> FactoryGirl オブジェクト
    def <%= singular_table_name %>_factory(key)
      n, c = <%= class_name %>FactoryHash[key.to_sym]
      FactoryBot.find_or_create(
        :<%= singular_table_name %>,
        name: n,
        code: c,
        sort_order: <%= class_name %>.sort_orders[key.to_sym]
      ) if n
    end
    
    # @param [Array<Symbol, String>] keys オブジェクトを一意に決定するキーの配列
    # @return [Array<<%= class_name %>>] <%= class_name %> FactoryGirl オブジェクトの配列
    def <%= singular_table_name %>_factories(keys)
      keys.map { |k| <%= singular_table_name %>_factory(k) }
    end
    
    ##### ↑ write to spec/support/create_factories/<%= singular_table_name %>_factory.rb
    require 'rails_helper'
    
    RSpec.describe <%= class_name %>, type: :model do
      context 'common validation check' do
        subject { <%= singular_table_name %>_factory :<%= singular_table_name %> }
    
        #it_behaves_like :presence_validates, %i[]
        #it_behaves_like :unique_validates, %i[], -> { <%= singular_table_name %>_factory :other }
        #it_behaves_like :plural_unique_validates, %i[], -> { <%= singular_table_name %>_factory :other }
        #it_behaves_like :destroy_validates
        #it_behaves_like :reject_destroy_validates
        #it_behaves_like :belongs_to, :<%= singular_table_name %>, has_many: %i[], has_one: %i[], children: :optional, child: :optional
        #it_behaves_like :dependent_destroy, :<%= singular_table_name %>, %i[has_many has_one]
        #it_behaves_like :reject_destroy_for_relations, :<%= singular_table_name %>, %i[has_many has_one]
        #it_behaves_like :destroy_nullify_for_relations, :<%= singular_table_name %>, %i[has_many has_one]
      end
    
      context 'after some <%= plural_table_name %> are registrered' do
        model_keys = %i[]
        let!(:targets) { <%= singular_table_name %>_factories model_keys }
    
        describe '<%= class_name %> class' do
          subject { <%= class_name %> }
    
          #it_behaves_like :mst_block, -> t do
          #  {
          #    method1: [v1, a1, v2, a2, ...],
          #    method2: [v1, a1, v2, a2, ...],
          #  }
          #end
          #
          #it_behaves_like :msta_block, -> t do
          #  {
          #    method1: [v1, a1, v2, a2, ...],
          #    method2: [v1, a1, v2, a2, ...],
          #  }
          #end
          #it_behaves_like :mst, :METHOD1, -> { [v1, <%= singular_table_name %>_factories(model_keys.values_at()), v2, <%= singular_table_name %>_factories(model_keys.values_at())] }
          #it_behaves_like :msta, :METHOD1, -> { [v1, <%= singular_table_name %>_factories(model_keys.values_at()), v2, <%= singular_table_name %>_factories(model_keys.values_at())] }
        end
    
        context '<%= class_name %> instances' do
          subject { targets }
    
          #it_behaves_like :amst_block, -> t do
          #  {
          #    method1: [v1, a1, v2, a2, ...],
          #    method2: [v1, a1, v2, a2, ...],
          #  }
          #end
          #
          #it_behaves_like :amsta_block, -> t do
          #  {
          #    method1: [v1, a1, v2, a2, ...],
          #    method2: [v1, a1, v2, a2, ...],
          #  }
          #end
          #it_behaves_like :amst, :METHOD1, -> { [v1, a1, v2, a2] }
          #it_behaves_like :amsta, :METHOD1, -> { [v1, a1, v2, a2] }
        end
      end
    end
Kurasu モデルの作成
  1. generator で Kurasu モデルを作成する.Kurasu は teacher_id, name, oboslete を属性と持ち,Teacher と Kurasu は 1 対多の関係となる. 設計当初から obsolete 属性を追加した( アプリの設計 - 不定期刊 Rails App を作る(1) は修正した). 年度更新した際に運用上古いクラスを見えなくするためのフラグである. obsolete が true の時,そのクラスは未使用とする.
    $ bin/rails g model kurasu name:string obsolete:boolean teacher_id:integer
    Running via Spring preloader in process 51112
          invoke  active_record
          create    db/migrate/20181114124228_create_kurasus.rb
          create    app/models/kurasu.rb
          invoke    rspec
          create      spec/models/kurasu_spec.rb
          invoke      factory_bot
          create        spec/factories/kurasus.rb
  2. teacher_id および name については nil が入ることが許されない. Rails 側でも validate で制御できるが,データベース側でも null を許可しないように変更した. plural_uniqueness の説明もしたいので,teacher_id と name の属性はセットで unique である制約をつけてみた. すなわち,一人の教員は同じ名前のクラスを設定することができなくなる(他の教員が同じ名前のクラスを持つことはできる). これを実現するには,add_index に属性の配列を渡し,unique: true オプションをつければよい. この時,teacher_id だけで絞り込みをすることが多いことを考え,teacher_id, name の順番とした. これは,複数インデックスの場合には,左から順に少ない項目でもインデックスが利用可能となるためである(teacher_id だけの場合もこの index が利用可能). これらを踏まえ,db/migrate/20181114124228_create_kurasus.rb は以下のように変更した.
    class CreateKurasus < ActiveRecord::Migration[5.2]
      def change
        create_table :kurasus do |t|
          t.string :name, null: false # オプションを追加
          t.boolean :obsolete, null: false # オプションを追加
          t.integer :teacher_id, null: false # オプションを追加
    
          t.timestamps
        end
        add_index :kurasus, %i[teacher_id name], unique: true
      end
    end
  3. データベースの migrate を実行する(2行目以降は結果).
    $ bin/rails db:migrate
    == 20181114124228 CreateKurasus: migrating ====================================
    -- create_table(:kurasus)
       -> 0.1361s
    -- add_index(:kurasus, [:teacher_id, :name], {:unique=>true})
       -> 0.0090s
    == 20181114124228 CreateKurasus: migrated (0.1452s) ===========================
  4. 念のため migration が redo できることも確認しておく(2行目以降は結果).
    $ bin/rails db:migrate:redo
    == 20181114124228 CreateKurasus: reverting ====================================
    -- remove_index(:kurasus, {:column=>[:teacher_id, :name]})
       -> 0.0649s
    -- drop_table(:kurasus)
       -> 0.0143s
    == 20181114124228 CreateKurasus: reverted (0.1147s) ===========================
    
    == 20181114124228 CreateKurasus: migrating ====================================
    -- create_table(:kurasus)
       -> 0.0508s
    -- add_index(:kurasus, [:teacher_id, :name], {:unique=>true})
       -> 0.0043s
    == 20181114124228 CreateKurasus: migrated (0.0554s) ===========================

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