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なりで頂ければ対応させて頂きますので、遠慮なく利用くださいね。
ディスカッション
コメント一覧
まだ、コメントがありません