ruby-jp slack で ActionText の質問に答えた

ruby-jp.slack.com | ruby-jp#support チャンネルで ActionText に関する質問があったので調べて答えた。 ActionText はまだちゃんと触っていなかったので、調べるきっかけになってよかった。

質問内容

rails6の ActionTextrich_text_area について質問したいです!!! has_rich_text を使ったフォームで Create するとエラーが出てしまいます( Update では正常に更新できます)。

エラー文
SQLite3::ConstraintException: NOT NULL constraint failed: articles.body

models/article.rb

class Article < ApplicationRecord
  has_rich_text :body
  validates :title, presence: true
  validates :body, presence: true
end

そのほか views と controllers のコードも貼り付けてくれていた。

調べたこと考えたこと

ActionText か〜。まだ触ってないな〜。と思ってまずRailsガイドをみた。

railsguides.jp

読んでみると

リッチテキストコンテンツは独自のRichTextモデルに保存され、このモデルはアプリケーションの既存のあらゆるActive Recordモデルと関連付けられます。

と書いてあるから、今回のケースで言うと Article モデルの body フィールドの実体は RichText モデルに保存されるってことだな。

つまり articles テーブルには body カラムは必要ない、ということだ。

で、質問のエラー文をみてみると NOT NULL constraint failed: articles.body とある。

ん、これは articles モデルに body カラムがあるってことだなぁ。

てことは body カラムを作る migration をしたのだろう。

そして質問者さんは「この body カラムをリッチテキストにしたい!」と考えて has_rich_text :body をつけたのだろう、と想像した。

さらに質問を読み返してみると

フォームで Create するとエラーが出てしまいます( Update では正常に更新できます)。

とある。

Create ができないのに Update ができる、とはどういうことか。

というか Create ができないのに、その Update の対象になっているレコードはどうやって作ったのか。

おそらくそのレコードは has_rich_text :body をつける前に作成したのだろうなと思った。 has_rich_text :body をつける前に作成したレコード( body カラムが not null なもの)であれば、has_rich_text :body をつけた後に Update しても not null 制約に掛からないのでエラーにならない。

回答

以上を踏まえて次の3点を回答した。

  1. 「もともと Article モデルにあった body カラムを rich_text にしようとしているのかな?」と推測したよ
  2. body カラムの実体は RichText モデルに保存されるから articles テーブルには body カラムは不要だよ
    • articles テーブルの body カラムを消す migration をすれば Create のときもエラーじゃなくなると思うよ
  3. Update がうまくいくのは has_rich_text :body をつける前に作ったレコードだからだと思うよ

質問者さんの反応は

推測通りです!笑 DBをRollbackし、Articleモデルからbodyを削除してMIgrationしたらエラーが消えました!

あってた。わーい。

めでたし。

ではない。

重ねて質問させていただきたいのですが、 bodyをnull: falseにしたい場合は、どうすればよろしいのでしょうか?

ですよね。

has_rich_textに指定したフィールドのnullを禁止するには

ActionText::Attribute をみると has_rich_text の引数は name だけでオプションは指定できない。

null を禁止したければカスタムバリデーションを書くかなぁ、と思った。

質問者さんの Article モデルを書き換える形で書くと、こんな感じかな。

class Article < ApplicationRecord
  has_rich_text :body
  validates :title, presence: true
  validate :body_required

  private
  def body_required
    errors.add(:body, "is required") unless body.body.present?
  end
end

body.body.present? となるのがなんとなく見栄えが悪いけど仕方ない。 なぜなら Ariticle モデルのフィールドとしての body は RichText モデルへの relation であり、 RichText モデルは body フィールドを持つので body.body になってしまう。

Article モデルのフィールド名を変えて body 以外(例えば content など)にするとこの見栄えの悪さを解消できる。

class Article < ApplicationRecord
  has_rich_text :content
  validates :title, presence: true
  validate :content_required

  private
  def content_required
    errors.add(:content, "is required") unless content.body.present?
  end
end

Action Text の概要 - Railsガイド の例でも content というフィールド名を使っているのでなんとなく良さそう。

思ったこと

ruby-jp 基本的には眺めているだけだったけど質問したり答えたりできる場があるのは便利だなぁと思った。

あと ActionText について調べるきっかけになってよかった。

おしまい。

更新情報

2021-02-19

最後のコード例でのtypoを修正しました

class Article < ApplicationRecord
-  has_rich_text :body
+  has_rich_text :content
  validates :title, presence: true