1
0

More than 3 years have passed since last update.

Railsチュートリアル 第13章 ユーザーのマイクロポスト - Micropostモデル

Posted at

基本的なモデル

Micropostモデルが持つ属性

Micropostモデルの基本となる属性は以下の2つです。

  • content属性
    • マイクロポストの内容
  • user_id属性
    • 特定のユーザーと、当該マイクロポストを関連付ける

実際には、Railsにより自動生成される属性もあるため、Micropostモデルの全体像は以下のようになります。

Micropost.png

Text型とは

RDBにおけるText型は、(存在する場合)String型とは異なる、文字列を内容として格納する型です。Railsチュートリアルでは、String型と対比した場合のText型の特徴として、以下の事柄が挙げられています。

  • String型に対応するのはテキストフィールドであり、Test型に対応するのはテキストエリアである
    • マイクロポストの投稿フィールドとして用いる場合、1行のテキストフィールドより複数行のテキストエリアのほうが自然である
  • 将来における柔軟性に富む
    • 言語に応じて投稿の長さを調整できる、など

以上のような理由から、今回のサンプルアプリケーションにおいて、マイクロポストの投稿・保存は、Text型の属性を用いて行うこととします。

Micropostモデルの生成

モデルを生成するためのコマンドは、rails generate modelコマンドですね。今回は以下のコマンドを用いてMicropostモデルを生成します。

# rails generate model Micropost content:text user:references
Running via Spring preloader in process 1289
      invoke  active_record
      create    db/migrate/20191218224953_create_microposts.rb
      create    app/models/micropost.rb
      invoke    test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml

結果、以下のようなモデルが生成されます。例によってApplicationRecordを継承しています。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
end

user:referencesbelongs_to :userという、見慣れないコードが存在しますね。大まかには、「RDBのインデックスと外部キー参照が定義されたuser_idカラムを自動追加し、UserとMicropostを紐付けする準備を行う」という意味合いのコードです。これらのコードが意味する事柄については、後の「User/Micropostの関連付け」の項で、より詳しく解説していきます。

Micropostモデルのマイグレーション

上述rails generate modelコマンドで生成されたMicropostモデルのマイグレーションは、初期状態では以下のようになっています。

db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[5.1]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

t.references :user, foreign_key: trueというのも、user:referencesbelongs_to :userと同様、UserとMicropostの紐付けに関するコードです。

また、t.timestampsというコードが自動で追加されています。「created_at属性およびupdated_at属性の定義と、その値の自動保存」を実装するためのコードです。Userモデルのマイグレーションでも、同様のコードが登場していましたね。

Micropostモデルのマイグレーションに、新たなインデックスを追加する

Micropostモデルにおいては、「user_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出す」という主要なユースケースが存在します。当該ユースケースを効率よく実行できるようにするために、必要なインデックスを追加していきましょう。すなわち、「user_id属性とcreated_at属性の組に対応するインデックス」ですね。

db/migrate/[timestamp]_create_microposts.rb
  class CreateMicroposts < ActiveRecord::Migration[5.1]
    def change
      create_table :microposts do |t|
        t.text :content
        t.references :user, foreign_key: true

        t.timestamps
      end
+     add_index :microposts, [:user_id, :created_at]
    end
  end

複合キーインデックスとは

RDBにおいて、「複数の属性の組み合わせ」に対して設定されるインデックスのことを指します。「単一の属性では重複が発生する」という場合などに用いられます。なお、日本語では「複合インデックス」や「複数列インデックス」という言い回しのほうがより一般的なようです。

RailsのActive Recordに複合キーインデックスを作成させるには、対応するマイグレーションにおいて、add_indexの2番目の引数に、複合キーインデックスを構成するすべての属性を指すシンボルを配列で与える必要があります。

例えば、以下のような感じですね。

add_index :microposts, [:user_id, :created_at]

Micropostモデルの生成をRDBに反映する

例によってrails db:migrateコマンドです。

# rails db:migrate
== [timestamp] CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0213s
-- add_index(:microposts, [:user_id, :created_at])
   -> 0.0058s
== [timestamp] CreateMicroposts: migrated (0.0349s) ========================

なお、現在RailsサーバーやRailsコンソールが起動されている場合は、先にexitしておく必要があります。そうでないとrails db:migrateが失敗します。

演習 - 基本的なモデル

1.1. RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。

# rails console

>> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>

1.2. その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atupdated_at) には何が入っているでしょうか?

>> micropost[:user_id] = 1
=> 1
>> micropost[:content] = "Lorem ipsum"
=> "Lorem ipsum"

>> pp micropost[:created_at]
nil
>> pp micropost[:updated_at]
nil

created_atupdated_atの内容は、いずれもnilになっていますね。

2.1. 先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか?

>> micropost.user
  User Load (2.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-12-02 23:07:26", updated_at: "2019-12-02 23:07:26", password_digest: "$2a$10$AXWNXZNDu9NkLx3tvgr80umb9xpbx1KwPz9ndp/7Pwr...", remember_digest: nil, admin: true, activation_digest: "$2a$10$Q.bVywQrrgEJC6Mg0IhdXONY5M/0jQYm4/ZEBwhJfxc...", activated: true, activated_at: "2019-12-02 23:07:26", reset_digest: nil, reset_sent_at: nil>

usersテーブルに対し、SQLのSELECT文を、抽出対象カラムをid・抽出条件をmicropost[:user_id]として実行しています。

結果、id=1のUserオブジェクトが返ってきます。

2.2. また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?

>> micropost.user.name
=> "Example User"

>> User.find(1).name
  User Load (5.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> "Example User"

id=1であるUserオブジェクトのname属性の値が返ってきます。User.find(1).nameと同じ結果ですね。

3.1. 先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。

>> micropost.save
   (0.5ms)  begin transaction
  SQL (19.6ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2019-12-19 05:11:44.579268"], ["updated_at", "2019-12-19 05:11:44.579268"]]
   (9.8ms)  commit transaction

特にバリデーションが設定されていないため、何も考えずにmicropost.saveメソッドを実行しても、micropostオブジェクトの内容が正しくRDBに保存されます。

3.2. この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?

>> pp micropost[:created_at]
Thu, 19 Dec 2019 05:11:44 UTC +00:00

>> pp micropost[:updated_at]
Thu, 19 Dec 2019 05:11:44 UTC +00:00

created_atおよびupdated_atの内容には、先ほどのmicropost.saveのログに表示された時刻と同じ時刻が保存されています。

Micropostのバリデーション

Micropostモデルに対する最初のテスト

  • setupメソッドで与えたMicropostオブジェクトが有効であることを確認する
  • user_idが存在しないMicropostオブジェクトが有効でないことを確認する
test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase
  def setup
    @user = users(:rhakurei)
    #HACK: このコードは慣習的に正しくないため、要修正
    @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
  end

  test "should be valid" do
    assert @micropost.valid?
  end

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
end

Micropostモデルに対する最初のテストの実行

# rails test test/models/micropost_test.rb
Running via Spring preloader in process 1340
Started with run options --seed 11643

  2/2: [===================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.74715s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

現時点においては、当該テストは問題なく成功します。

Micropostモデルに、テスト駆動で新たな機能を追加していく

長くなりましたので、別記事で解説します。

演習 - Micropostのバリデーション

1.1. Railsコンソールを開き、user_idcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。

>> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> micropost.valid?
=> false

micropost.valid?を実行すると、少々の待ち時間の後にfalseという結果が返ってきます。

1.2. また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

1.1. の続きとなります。

>> micropost.errors.full_messages
=> ["User must exist", "Content can't be blank"]
  • User must exist
  • Content can't be blank

以上のエラーメッセージが返ってきていますね。

2.1. コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。

>> micropost = Micropost.new(user_id: nil, content: "a" * 141)
=> #<Micropost id: nil, content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", user_id: nil, created_at: nil, updated_at: nil>
>> micropost.valid?
=> false

文字列の乗算により、141文字の文字列を生成し、その内容をcontentの内容としています。

"a" * 141

2.2. また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

>> micropost.errors.full_messages
=> ["User must exist", "Content is too long (maximum is 140 characters)"]
  • User must exist
  • Content is too long (maximum is 140 characters)

以上のエラーメッセージが返ってきていますね。

User/Micropostの関連付け

# rails generate model Micropost content:text user:references
(Micropostモデルより)
belongs_to :user
(Micropostのマイグレーションより)
t.references :user, foreign_key: true

Micropostモデルの生成に際して、以上のような見慣れないコードが登場しました。これらはどのような意味を持つのでしょうか。その解説です。

Micropost belongs to User

micropost belongs to user.png

MicropostとそのUserは、belongs_to(1対1)という関係性があります。

User has many Microposts

user has many microposts.png

UserとそのMicropostには、has_many(1対多)という関係性があります。

モデル定義に記述する、belongs_tohas_many

まず、Micropostモデルにbelongs_to :userというコードが必要になります。ただ、このコードは、Micropostモデルの生成時点で自動で生成されています。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  # ...略
end

さらに、Userモデルにhas_many :micropostsというコードが必要になります。こちらは手動で追加する必要があります。

app/models/user.rb
  class User < ApplicationRecord
+   has_many :microposts
    ...略
  end

User/Micropost関連メソッド

上述のbelongs_tohas_manyというモデル同士の関係性をモデルに記述することにより、以下のようなメソッドを利用することが可能になります。

メソッド名 用途
micropost.user Micropostに紐付いたUserオブジェクトを返す
user.microposts Userのマイクロポストの集合を返す
user.microposts.create(arg) userに紐付いたマイクロポストを生成する。失敗時にはnilを返す
user.microposts.create!(arg) userに紐付いたマイクロポストを生成する。失敗時には例外を発生させる
user.microposts.build(arg) userに紐付いた新しいMicropostオブジェクトを返す
user.microposts.find_by(id: 1) userに紐付いていて、id1であるマイクロポストを検索する

Micropostモデルに対するテストのsetupメソッドを、慣習的に正しいマイクロポストの生成方法に書き換えていく

関連するモデルにbelongs_tohas_manyを実装すれば、慣習的に正しいマイクロポストの生成を実装できるようになります。test/models/micropost_test.rbsetupメソッドを、慣習的に正しいマイクロポストの生成方法に書き換えていきましょう。

test/models/micropost_test.rb
  require 'test_helper'

  class MicropostTest < ActiveSupport::TestCase
    def setup
      @user = users(:rhakurei)
-     #HACK: このコードは慣習的に正しくないため、要修正
-     @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
+     @micropost = @user.microposts.build(content: "Lorem ipsum")
    end

    test "should be valid" do
      assert @micropost.valid?
    end

    test "user id should be present" do
      @micropost.user_id = nil
      assert_not @micropost.valid?
    end

    test "content should be present" do
      @micropost.content = "  "
      assert_not @micropost.valid?
    end

    test "content should be at most 140 characters" do
      @micropost.content = "a" * 141
      assert_not @micropost.valid?
    end
  end

Micropostモデルに対するテストのsetupメソッドを、慣習的に正しいマイクロポストの生成方法をテストで実装すると、テストが成功しなくなる

この時点でtest/models/micropost_test.rbに対するテストを実行すると、テストが成功しなくなります。

# rails test test/models/micropost_test.rb
Running via Spring preloader in process 1476
Started with run options --seed 20502

 FAIL["test_user_id_should_be_present", MicropostTest, 0.7893391999969026]
 test_user_id_should_be_present#MicropostTest (0.79s)
        Expected true to be nil or false
        test/models/micropost_test.rb:15:in `block in <class:MicropostTest>'

  4/4: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.81867s
4 tests, 4 assertions, 1 failures, 0 errors, 0 skips

私の環境では、test/models/micropost_test.rbの14行目〜15行目の内容は以下のようになっています。

test/models/micropost_test.rb(14〜15行目)
@micropost.user_id = nil
assert_not @micropost.valid?

@micropost.user_idnilであるのに、@micropost.valid?trueを返してしまっている」という失敗ですね。

Micropostモデルに、ユーザーの存在性を検証するバリデーションを追加する

先ほどの問題を解消するためには、Micropostモデルに、ユーザーの存在性を検証するバリデーションを追加する必要があります。app/models/micropost.rbのコードの変更点は以下のとおりです。

app/models/micropost.rb
  class Micropost < ApplicationRecord
    belongs_to :user
+   validates :user_id, presence: true
    validates :content, presence: true, length: { maximum: 140 }
  end

上記変更を反映した後に、再びテストを実行してみます。

# rails test test/models/micropost_test.rb
Running via Spring preloader in process 1489
Started with run options --seed 47152

  4/4: [===================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.87582s
4 tests, 4 assertions, 0 failures, 0 errors, 0 skips

今度はテストが成功するようになります。

演習 - User/Micropostの関連付け

1. データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?

>> user = User.first

>> micropost = user.microposts.create(content: "Lorem ipsum")
   (0.1ms)  begin transaction
  SQL (15.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2019-12-20 13:10:45.903917"], ["updated_at", "2019-12-20 13:10:45.903917"]]
   (10.2ms)  commit transaction
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2019-12-20 13:10:45", updated_at: "2019-12-20 13:10:45">

実際に生成されたマイクロポストが返ってきています。

2.1. 先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。

>> user.microposts.find(micropost.id)
  Micropost Load (6.3ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2019-12-20 13:10:45", updated_at: "2019-12-20 13:10:45">

確かに 1. で追加されたマイクロポストを返してきています。

2.2. また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?

>> user.microposts.find(micropost)
Traceback (most recent call last):
        1: from (irb):5
ArgumentError (You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.)

ArgumentErrorを投げてきていますね。

.findActiveRecord::Baseを継承したオブジェクトそのものを渡すとArgumentErrorを投げる」というのは、Rails 5.1 以降の仕様となります。 - rails/CHANGELOG.md at v5.1.0 · rails/rails · GitHub

Raise ArgumentError when passing an ActiveRecord::Base instance to .find, > .exists? and .update.

Rafael Mendonça França

3.1. user == micropost.userを実行した結果はどうなるでしょうか?

>> user == micropost.user
=> true

3.2. また、user.microposts.first == micropostを実行した結果はどうなるでしょうか? それぞれ確認してみてください。

>> user.microposts.first == micropost
  Micropost Load (27.9ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> false

当該ユーザーにより、今回の演習 1. 以前に一度マイクロポストを追加したことがありました。そのため、今回追加したマイクロポストとuser.microposts.firstは一致せず、user.microposts.first == micropostはfalse`を返します。

>> user.microposts.first
  Micropost Load (7.8ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2019-12-19 05:11:44", updated_at: "2019-12-19 05:11:44">

マイクロポストを改良する

マイクロポストが投稿日時の降順(=新しい順)に表示されるようにする

「マイクロポストが投稿日時の降順(=新しい順)に表示されるようにする」という実装を、Micropostモデルに追加します。実装およびテストについては、別記事で解説します。

ユーザーが削除されると、当該ユーザーのマイクロポストも同時に破棄されるようにする

「ユーザーが削除されると、当該ユーザーのマイクロポストも同時に破棄されるようにする」という実装を、Userモデルに追加します。実装およびテストについては、別記事で解説します。

Railsチュートリアル 第13章 ユーザーのマイクロポスト - 「ユーザーが削除されると、当該ユーザーのマイクロポストも同時に破棄されるようにする」という実装を、Userモデルに追加する」

演習 - マイクロポストを改良する

1. Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。

>> Micropost.first.created_at
  Micropost Load (14.9ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> Fri, 20 Dec 2019 13:10:45 UTC +00:00

>> Micropost.last.created_at
  Micropost Load (0.5ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> Thu, 19 Dec 2019 05:11:44 UTC +00:00

確かにMicropost.first.created_atの結果の日時よりも、Micropost.last.created_atの日時のほうが過去の日時となっています。

2. Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか?

ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。

>> Micropost.first
  Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]

>> Micropost.last 
  Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]

firstのほうのSQL文にはDESCという句が含まれており、一方lastのほうのSQL文にはASCという句が含まれています。

3.1. データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか?

>> user = User.first
  User Load (1.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, ...>

>> user.microposts.last.id
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> 1

現状の実装において、「ユーザーが最初に投稿したマイクロポスト」というのは、user.microposts.lastというメソッドで取得することができます。そのidのみを取得するのであれば、user.microposts.last.idというメソッドですね。

user.microposts.last.idの戻り値は1ですね。

3.2. 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。

>> user.destroy
   (0.2ms)  SAVEPOINT active_record_1
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
  SQL (18.6ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 2]]
  SQL (0.2ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 1]]
  SQL (5.6ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1

>> Micropost.find(1)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):11
ActiveRecord::RecordNotFound (Couldn't find Micropost with 'id'=1)

ポイントは以下です。

  • userオブジェクトのdestroyメソッドを実行した時点で、micropostsテーブルのレコードに対してもSQL文DELETEがいくつか発行されている
    • 削除対象のレコードを確定するために、当該ユーザーのidWHERE句に持つSQL文SELECTが発行されている
  • 削除されたマイクロポストのIDを引数に取ってMicropost.findを実行すると、ActiveRecord::RecordNotFoundというエラーが投げられてくる

余談 - Userモデルのhas_manyメソッドにdependent: :destroyオプションがない場合

>> user.destroy
   (0.6ms)  SAVEPOINT active_record_1
  SQL (25.4ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (0.2ms)  ROLLBACK TO SAVEPOINT active_record_1
Traceback (most recent call last):
        1: from (irb):20
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "users" WHERE "users"."id" = ?)

user.destroyActiveRecord::InvalidForeignKeyというエラーで失敗します。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0