RailsでToDoサービスを作ろう(第3回)〜ユーザを紐付ける 〜

にゅ~ぶる
にゅ~ぶる

こんにちは、にゅ〜ぶるです。

こんにちは〜、ぶるこだよ〜💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

さて、第3回目ですね!前回は、こちら
今日は、「ユーザを紐付ける」について、進めていきたいと思います!

にゅ~ぶる
にゅ~ぶる

自分だけのToDoリストであれば、不要かもしれませんが、多くの方に利用して貰うには、「ユーザ」は必須になりますよね。
それでは、やっていきましょ〜🎶

にゅ~ぶる
にゅ~ぶる

コマンドは、こんな感じ

rails generate scaffold User name:string

名前 … nameというカラム名の文字列型(string)

にゅ~ぶる
にゅ~ぶる

その他、追加したいカラムは自由に追加してみてください。

年齢 … ageというカラム名の整数型(integer) など

にゅ~ぶる
にゅ~ぶる

テーブルを作成するmigrateファイルも作成されますので、
migrateコマンドも忘れずにね!

rails db:migrate

わ〜お、忘れるところだった💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

Userの画面を作ったので、「rails s」で起動した後、
「http://127.0.0.1:3000/users」にアクセスしてみてね!

にゅ~ぶる
にゅ~ぶる

そして、自分のユーザを登録してみましょう。
作成されたCRUDの画面を使って登録しても良いのですが、
今回は、少し脱線しますが、Seedを使ってみましょう。

にゅ~ぶる
にゅ~ぶる

でも今回使うSeedは、標準で使えるSeedではなく、「Seed Fu」をオススメします。
実行するたびにデータが新規登録されるSeedに比べ、更新が楽になりますので、「Seed Fu」を使っています。

にゅ~ぶる
にゅ~ぶる

では早速、使ってみましょう。
まずは、Gemfileに以下を追加します。

gem 'seed-fu', '~> 2.3'

Rails 3.1, 3.2, 4.0, 4.1, 4.2, 5.0では、2.3以上である必要があるため、この指定をしています。

参考 https://github.com/mbleigh/seed-fu

にゅ~ぶる
にゅ~ぶる

そして、Gemfileを修正したら「bundle install」ですね。

bundle install
にゅ~ぶる
にゅ~ぶる

そして、Seedファイルを置く場所を作成します。

mkdir db/fixtures
mkdir db/fixtures/development
mkdir db/fixtures/production

このディレクトリは固定ですか?

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

そうだね、変更する事も可能だけど、
基本的には、db/fixtures配下にseedファイルを作成したら良いと思います。
そして、development/productionディレクトリ配下にseedファイルを作成すると、その環境下でのみ実行されるseedファイルになります。

にゅ~ぶる
にゅ~ぶる

今回は、ユーザ情報を作成しますので、開発環境(development)だけに作成してみましょう。
※seedファイルのファイル名は自由につけても構いませんが、アルファベット順に実行される事に注意してください。

User.seed do |s|
  s.id = 1 s.name = "にゅ〜ぶる"
end
User.seed do |s|
  s.id = 2 s.name = "ぶるこ"
end
にゅ~ぶる
にゅ~ぶる

では、実行してみましょう!

rake db:seed_fu
にゅ~ぶる
にゅ~ぶる

こんな感じのログが出力されて、usersテーブルに登録されているのが確認出来るかと思います。

== Seed from /Users/newburu/project/rails/todo/db/fixtures/development/user.rb
 - User {:id=>1, :name=>"にゅ〜ぶる"}
 - User {:id=>2, :name=>"ぶるこ"}
にゅ~ぶる
にゅ~ぶる

データに変更が発生した際は、このファイルを修正して、再度コマンドを実行するだけでOKです。是非、試してみて下さい。

おぉぉお!!すごぉ〜い💕
データを消したり、DBを作り直したりしなくても変更できた!!

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

その他、Seed-Fuには色々な書き方がありますが、必要あれば調べてみて下さいね。
では、これで初期データも出来たので、次に進みましょうか!

は〜い💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

次は、作成したTaskとUserを紐づけることをしてみましょう。
紐づける事によって、「にゅ〜ぶるのTask」と「ぶるこのTask」といったように分けることが出来るようになります。

にゅ~ぶる
にゅ~ぶる

まずは、紐づいた情報を管理するために、テーブルを修正します。
修正する際は、generate機能のmigrationを使います。

rails generate migration AddUserToTask user:references
にゅ~ぶる
にゅ~ぶる

中身はこんなファイルが作成されたと思います。

class AddUserToTask < ActiveRecord::Migration[6.0]
  def change
    add_reference :tasks, :user, null: false, foreign_key: true
  end
end
にゅ~ぶる
にゅ~ぶる

rails generate migration の後に、「AddUserToTask」としたことで、自動でTaskに追加するのだと判断されて、「user:references」を指定したことで、Userへの参照用のカラムだと判断され、Migrationファイルが自動生成されました。

にゅ~ぶる
にゅ~ぶる

では、rails db:migrateを実行して、テーブルがどう変わったか確認してみましょう。

rails db:migrate

うわ〜ん😭
エラーが出ちゃったよおぉぉ😭

ぶるこ
ぶるこ
Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails (`todo_development`.`#sql-16e75_a`, CONSTRAINT `fk_rails_4d2a9e4d7e` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))
にゅ~ぶる
にゅ~ぶる

あっ!
前回、Taskを作成した際に、何かデータを登録してますね?
そのデータに対して、ユーザが紐づいていない状態なのに、紐付けキー(必須になる)を作成してしまった為、MySQLのエラーが出ちゃいましたね…💦
※エラーが出なかった方は、少しお待ちください。

にゅ~ぶる
にゅ~ぶる

では、DBをリセットしましょう。(登録した中身は消えちゃいますが…)

rails db:migrate:reset
Dropped database 'todo_development'
Dropped database 'todo_test'
Created database 'todo_development'
Created database 'todo_test'
== 20200429055957 CreateTasks: migrating ======================================
-- create_table(:tasks)
   -> 0.0302s
== 20200429055957 CreateTasks: migrated (0.0303s) =============================
== 20200509005333 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0268s
== 20200509005333 CreateUsers: migrated (0.0268s) =============================
== 20200509043009 AddUserToTask: migrating ====================================
-- add_reference(:tasks, :user, {:null=>false, :foreign_key=>true})
   -> 0.1002s
== 20200509043009 AddUserToTask: migrated (0.1003s) ===========================
にゅ~ぶる
にゅ~ぶる

はい。すっきり笑

よかったぁ〜💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

初期データも消えてしまったので、「rake db:seed_fu」も再度実行しておいて下さいね。

にゅ~ぶる
にゅ~ぶる

これでDB上ではTaskとUserが紐づいたのですが、Railsにも紐づいている事を教えてあげなければいけません。その時に使うのが、Modelに「has_one/many」「belongs_to」です。

にゅ~ぶる
にゅ~ぶる

まずは、「has_one/many」を設定しましょう。
has_XXXとなっている事から分かる通り、親と子の関係で、親の方に設定します。

class User < ApplicationRecord
  has_many :tasks
end
にゅ~ぶる
にゅ~ぶる

そして、子の方に、「belongs_to」を設定します。

class Task < ApplicationRecord
  belongs_to :user
end
にゅ~ぶる
にゅ~ぶる

こう設定する事で、
User.tasksで、そのUserのTask一覧が取得でき、
Task.userで、そのTaskのUserが取得出来ます。

では、実際に試してみましょう。

にゅ~ぶる
にゅ~ぶる

ちょっと試したいなという時に便利なのが、「rails console」です。
コンソール上からRailsのサービスにアクセスする事が出来ます。

rails console
※略して、「rails c」でも可
にゅ~ぶる
にゅ~ぶる

Rubyをやった事のある方ならご存知、irbのコンソールが起動します。
このコンソール上で、自由にプログラムを実行させる事が出来るのです。

にゅ~ぶる
にゅ~ぶる

こんな感じにテストデータを作成してみましょう。

user = User.first
user.tasks.create(title: "テスト", content: "テストデータ")
にゅ~ぶる
にゅ~ぶる

この状態で、次のように実行してみて下さい。なんと返ってくるでしょう?

task = Task.first
task.user
にゅ~ぶる
にゅ~ぶる

こんな結果が返ってきますね。

=> #<User id: 1, name: "にゅ〜ぶる", created_at: "2020-05-09 04:53:02", updated_at: "2020-05-09 04:53:02">
にゅ~ぶる
にゅ~ぶる

ほら、TaskとUserが紐づいた!

やったぁ〜💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

これで完成!!!!
と言いたいところですが、画面も直さないといけないですね。

にゅ~ぶる
にゅ~ぶる

では、画面のどこを直す必要があるでしょうか?

えっと〜
新規登録(C)はいるでしょ〜💕
あとはぁ〜変更(U)に参照(R)に
削除(D)も?

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

削除は画面がないので、修正する必要はないんだけど、
親を削除したら子も一緒に削除はしておきたいよね。それを最後にやろっか。

にゅ~ぶる
にゅ~ぶる

では、新規登録の画面は、
「app/views/tasks/new.html.erb」になるんだけど、中身はこんな感じ

<h1>New Task</h1>

<%= render 'form', task: @task %>

<%= link_to 'Back', tasks_path %>
にゅ~ぶる
にゅ~ぶる

renderを使って、別ファイルになっているんだ。
そのファイルが「app/views/tasks/_form.html.erb」

<%= form_with(model: task, local: true) do |form| %>
  <% if task.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>

      <ul>
        <% task.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :content %>
    <%= form.text_area :content %>
  </div>

  <div class="field">
    <%= form.label :deadline %>
    <%= form.date_select :deadline %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
にゅ~ぶる
にゅ~ぶる

これに、ユーザを追加してみましょう。
ユーザはリスト表示したいので、「form_withのselect」を使います。

好きなところに、以下を追加してみて下さい。

<div class="field">
  <%= form.label :user_id %>
  <%= form.select :user_id, User.all.map { |user| [user.name, user.id] } %>
</div>

おぉ〜💕
出た出た〜登録しよ〜💕

ぶるこ
ぶるこ

が〜ん❗️

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

Userに値が入ってない!!って怒られたね。
原因は、ここ。

app/controllers/tasks_controller.rb
def task_params
  params.require(:task).permit(:title, :content, :deadline)
end
にゅ~ぶる
にゅ~ぶる

Strong Parametersっていうんだけど、
要は、受け取る可能なパラメータリストを指定して、それ以外の不正なパラメータを受け取らないようにしている感じだね。

にゅ~ぶる
にゅ~ぶる

ここに、userを許可してあげないといけないんだ。
書き方はこう。

def task_params
  params.require(:task).permit(:title, :content, :deadline, :user_id)
end
にゅ~ぶる
にゅ~ぶる

これで登録できるようになるよ。

わぁ〜💕
登録できたぁぁあ💕

ぶるこ
ぶるこ

次は、変更画面だね💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

そうだね。
でも実は、「app/views/tasks/_form.html.erb」が変更画面でも使われているので既に修正出来てるんだ。

なんやて工藤!!!

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

だから、次は、参照画面を修正するよ。
参照画面のViewは、「app/views/tasks/show.html.erb」だね。

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @task.title %>
</p>

<p>
  <strong>Content:</strong>
  <%= @task.content %>
</p>

<p>
  <strong>Deadline:</strong>
  <%= @task.deadline %>
</p>

<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
にゅ~ぶる
にゅ~ぶる

これは単純に、@taskというインスタンス変数にTaskの情報が入っているから、
そのまま表示するだけだね。
次のコードを追加してみてね。

<p>
  <strong>User:</strong>
  <%= @task.user %>
</p>

変な数字みたいなのが…

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

おっと、それは、クラスのオブジェクトIDなんだけど、まぁ気にしなくても大丈夫です。
こう置き換えてみてね。

<p>
  <strong>User:</strong>
  <%= @task.user.name %>
</p>

おぉ〜ユーザの名前が出たぁ💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

うん。これで完成だね!
じゃあ、応用の課題として、一覧画面の「app/views/tasks/index.html.erb」にも修正してみて下さいね。
※回答はあえて載せません。合ってるかどうかの確認が必要であれば、コメントなりでご連絡くださいね。

わぁ〜い、かんせー💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

じゃ、ここからはおまけ?
Userを削除した際に、そのUserに関するTaskも一緒に削除される機能を追加するよ。

(*´∀`*)ワクワク
でも、難しそう…

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

そう思うかもだけど、実は簡単なんだ。
Modelの紐付け設定に、このオプション(dependent: :destroy)を追加するだけなんだ。

class User < ApplicationRecord
  has_many :tasks, dependent: :destroy
end

なんやて工藤!!!!

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

では、動作確認してみようか。
画面で動かしてみるのでも良いし、「rails console」を使うのでも良いよ。
「rails console」の場合は、こんな感じかな。

# テスト用Taskを登録
user = User.first
user.tasks.create(title: "test1", content: "テスト1です")
user.tasks.create(title: "test2", content: "テスト2です")

# テスト用Taskが登録されたか確認
Task.all

# テスト用Userを削除 ※ここで同時にTaskが削除される
user.destroy

# 削除されたか確認
Task.all

ほんとだぁ〜すご〜い💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

今回はここまでだよ。お疲れ様でした!
次回は、「Punditで認可の権限管理する」お楽しみにっ!!

最後まで読んでくれてありがとうございました!

現在、Railsのチュートリアル的な感じで、
「Todoサービスを作る!」をテーマにお送りしております。
アジェンダは、こちら

質問等ありましたら、コメントなりTwitterなりで頂ければ対応させて頂きますので、遠慮なく利用くださいね。