作業ログ 必要なモデルを作る 2019/10/12

この記事の続きです

kenta-s.hatenadiary.jp

まずは作ろうとしているものをざっくり説明

「未経験のエンジニアが実践経験を積む機会がない」という課題が世の中にあるんじゃないかと思いました。

プログラミング言語フレームワークを学ぶ方法はいくらでもあると思いますが、エンジニアが実際に仕事をするときって、プログラミング言語だけじゃなくていろいろとありますよね。

まずアプリケーション動くところまで環境構築しないといけないですし、コミュニケーションにはSlack使ったり、タスクは普通JiraなりRedmineなりで管理されてますし、コードを書くにしても動くコードだけじゃなくてテストコードも必要ですし、githubでPR出したらそれで終わりじゃなくて、マージされるまでレビューに対応したり必要ですし。

未経験のエンジニアがこのあたりを経験できていれば、非エンジニアからエンジニアへの転職がもっとスムーズになるんじゃないかと思いました。

あと、重要なポイントとして、彼ら(未経験エンジニア)の不安も払しょくできると思います。

どうやったらこういうリアルな体験ができるかというと、現役のエンジニアと一緒に仕事(?)をしてもらうのが一番だと考えたので、

現役のエンジニアが出した「お題」を、未経験のエンジニアに解決してもらう(PRがマージされるところまでやってもらう)という流れを生み出すきっかけになるマッチングシステムを作ってみよう、というのがざっくりした構想です。

あまり細かいところは考えずミニマムでパッと作って反応を見ます。

ER図を考える

なにはともあれDBから考えます。

手書きですが、とりあえずこんな感じになりそうです。fieldsはちょっと適当に書いてしまいました。

f:id:Kenta-s:20191012145214j:plain

現役エンジニアがtask(お題)を投稿して、未経験のエンジニアが気になったお題に応募(application)するということでとりあえず必要なテーブルは三つです。

最初は現役エンジニア役は僕ひとりでやるつもりなので、tasksは管理画面から登録します。

ユーザーがお題に応募すると、status: :pending なapplicationレコードが作られて、お題提出側が、承認するとstatus: :acceptedに、却下するとstatus: :rejectedになります。

tasksのend_atを過ぎると自動的にrejectedになります。

モデルを作る

まずはdeviseを入れます。

devise、嫌われてる印象がありますが僕は好きです。あのソースコードを読むのは確かにナイトメアですが、ドキュメントが充実してる(かつ大抵のやりたいことはできるように作られている)ので大抵の場合は避けられるナイトメアだと思います。

$ bundle
$ bundle exec rails generate devise:install
$ vim config/environments/development.rb # default_url_optionsのやつ
$ bundle exec rails g devise User
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment
      invoke  active_record
      create    db/migrate/20191012061040_devise_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb
      invoke      factory_bot
      create        spec/factories/users.rb
      insert    app/models/user.rb
       route  devise_for :users

migrationファイルを適当に修正してmigrate

$ bundle exec rails db:migrate
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment
== 20191012061040 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0346s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0228s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0246s
-- add_index(:users, :confirmation_token, {:unique=>true})
   -> 0.0226s
-- add_index(:users, :unlock_token, {:unique=>true})
   -> 0.0242s
-- add_index(:users, :display_name, {:unique=>true})
   -> 0.0236s
== 20191012061040 DeviseCreateUsers: migrated (0.1531s) =======================

webpackerがどうとか警告出るのでGemfileからwebpackerを消しておきます。

余談ですが、一個前のプロジェクト(個人プロジェクト)では途中からwebpackerを消してwebpackに移行しました。 そのときめちゃくちゃ苦労したのでwebpackerという文字列を見るとネガティブな感情が沸き起こってしまいます。

今回は厄介にならないうちに消しさります。

$ vim Gemfile
$ bundle

shoulda-matchersも好きなのでいれておきます。

github.com

user

# $ cat spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'associations' do
    it { should have_many(:applications).dependent(:destroy) }
  end

  describe 'validations' do
    subject { FactoryBot.build(:user, display_name: 'a') }
    it { should validate_presence_of(:display_name) }
    it { should validate_presence_of(:email) }
    it { should validate_uniqueness_of(:display_name).case_insensitive }
    it { should validate_uniqueness_of(:email).case_insensitive }
  end
end

一個だけテスト落ちるので、次はApplicationモデルを作ります

Failures:

  1) User associations is expected to have many applications dependent => destroy
     Failure/Error: it { should have_many(:applications).dependent(:destroy) }
       Expected User to have a has_many association called applications (no association called applications)
     # ./spec/models/user_spec.rb:5:in `block (3 levels) in <top (required)>'

ところでApplicationモデルって名前なにかと衝突しそうで怖いですね、大丈夫ですかね

$ bundle exec rails g model Application user:references status:integer
      invoke  active_record
      create    db/migrate/20191012070112_create_applications.rb
      create    app/models/application.rb
      invoke    rspec
      create      spec/models/application_spec.rb
      invoke      factory_bot
      create        spec/factories/applications.rb
# $ cat app/models/application.rb
class Application < ApplicationRecord
  belongs_to :user
end

「Application」はやっぱり強烈に嫌な感じがしますね、、変更します。「応募」というとapplicationだと思うんですけどね、、

Application < ApplicationRecord

ですからね

TaskApplicationにします

$ bundle exec rails g model TaskApplication user:references task:references status:integer
      invoke  active_record
      create    db/migrate/20191012073325_create_task_applications.rb
      create    app/models/task_application.rb
      invoke    rspec
      create      spec/models/task_application_spec.rb
      invoke      factory_bot
      create        spec/factories/task_applications.rb

しかもここでTaskApplicationがtaskにbelongs_toなので先にTaskを作らないといけないことに気付きました。はぁ、自分は何をやっているんだ、、、

migrationが外部キーのほげほげで落ちるので一旦TaskApplicationはrails d modelします

$ bundle exec rails d model TaskApplication

というわけでTask作成

$ bundle exec rails g model Task title:string description:text end_at:datetime
      invoke  active_record
      create    db/migrate/20191012074205_create_tasks.rb
      create    app/models/task.rb
      invoke    rspec
      create      spec/models/task_spec.rb
      invoke      factory_bot
      create        spec/factories/tasks.rb

テストはUserモデルと似たような感じです。例によってhas_manyのテストは落ちます

$ cat spec/models/task_spec.rb
require 'rails_helper'

RSpec.describe Task, type: :model do
  describe 'associations' do
    it { should have_many(:task_applications).dependent(:destroy) }
  end

  describe 'validations' do
    it { should validate_presence_of(:title) }
    it { should validate_presence_of(:description) }
    it { should validate_presence_of(:end_at) }
  end
end

こんどこそTaskApplicationを作成

$ bundle exec rails g model TaskApplication user:references task:references status:integer

テストはこれも同じような感じです。

# $ cat spec/models/task_application_spec.rb
require 'rails_helper'

RSpec.describe TaskApplication, type: :model do
  describe 'associations' do
    it { should belong_to(:task) }
    it { should belong_to(:user) }
  end

  describe 'validations' do
    it { should validate_presence_of(:status) }
  end
end

statusはenumです

class TaskApplication < ApplicationRecord
  belongs_to :user
  belongs_to :task

  enum status: {
    pending: 0,
    accepted: 1,
    rejected: 2,
  }

  validates :status, presence: true
end
$ bundle exec rspec
............

Finished in 0.16116 seconds (files took 1.77 seconds to load)
12 examples, 0 failures

これでモデルがそろいました。

つぎはControllerをやっていきます