LoginSignup
1
1

More than 1 year has passed since last update.

railsのカスタムバリデーション機能を使ってNGワード(不適切な内容)を防いでみる

Posted at

NGワード機能を追加しようと考えていたところ、
あまり参考にできる記事が見つからなかったので、記事を書いてみました。

Qiitaで記事を書くのは初めてですので、拙い面もありますがご了承ください。

  • やりたいこと

    • 不適切な言葉(下品な言葉など)を防ぎたい
    • htmlタグやurl,連続した文字("あああああ"など)を防ぎたい
  • やったこと

    • 1. modelにカスタムバリデーションを定義する(非推奨)
    • 2. カスタムバリデータクラスを作る(推奨)

テーブル構成

テーブル名 カラム名
User name
Micropost content
Comment content

1,2共通の操作

ブラックリストの作成(好きなように定義してください。)

config/blacklist.yml
---
- 不適切な単語1
- 不適切な単語2
- 不適切な単語3

不適切な単語は.gitignoreに入れてgitに載せないようにしておきましょう。

.gitignore
#以下を追加
/config/blacklist.yml

1. modelにカスタムバリデーションを定義する(非推奨)

まず、CommentのcontentとMicropostのcontentにNGワード機能を付けたいと思い、以下のようにカスタムバリデーションを定義してみました。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def content_cannot_contain_blacklist_words
#blacklist.ymlから不適切な言葉を読み取る
    blacklist = YAML.load_file('./config/blacklist.yml')
#contentが空でなく、contentの中にblacklistの用語が含まれているかを追加
    if content.present? && blacklist.any?{ |word| content.include?(word) }
      errors.add(:contain_blacklist_words, ": 不適切な言葉は使用できません")
# エラー時「contain_blacklist_words : 不適切な言葉は使用できません」と表示される。
    end
  end

  def content_cannot_contain_invalid_regex
# same_character_regex: 連続する五文字以上の語("あああああ")などを防ぐ
# url_regex: https(http)から始まるurlを防ぐ
# html_regex: htmlタグを防ぐ

# 正規表現のエスケープのため %r!正規表現!で囲む
    invalid_regex = { same_character_regex: %r!(.)\1{4,}!,
                      url_regex: %r!https?://[\w/:%#\$&\?\(\)~\.=\+\-]+!,
                      html_regex: %r!<(".*?"|'.*?'|[^'"])*?>!}
# invalid_regexをkey,valueとして取り出しvalue.match?(content)で正規表現と一致しているかを調べる。
    if content.present? && invalid_regex.any?{|key,value| value.match?(content)}
      errors.add(:contain_invalid_regex, ": 使うことのできない文字列が含まれています")
    end
  end

end

Micropost.rbにvalidateを追加(validationsと間違えないように)

app/models/micropost.rb
class Micropost < ApplicationRecord
# 先ほどapplicationで定義した関数を追加
  validate :content_cannot_contain_blacklist_words
  validate :content_cannot_contain_invalid_regex
end

Comment.rbにもvalidateを追加する

app/models/comment.rb
class Comment < ApplicationRecord
# 先ほどapplicationで定義した関数を追加
  validate :content_cannot_contain_blacklist_words
  validate :content_cannot_contain_invalid_regex
end
  • このやりかたでの問題点
    • contentにしか適用できていない
      (カラム名が変わると適用できない。例えばnameにvalidationを適応したい場合、content.present?の部分をname.present?などに変えたコードを用意しなければならないので、同じコードを繰り返してしまう。)
    • validate :関数名 を何度も書かなければならない。
    • そもそもどのカラムに適用してるのかがわかりにくい
      (validates :content, ~ のように書きたい)

2. カスタムバリデータクラスを作る(推奨)

1の問題点を解決しようとするとカスタムバリデータクラスを作ればいいことを発見する

この通りにやってみる

Validatorの定義

まずapp/validators/[validator名].rbを作る。
今回はinvalid_words_validator.rbとした。

app/validators/invalid_words_validator.rb
# InvalidWordsValidatorの部分は先ほど付けた名前をキャメルケースにしたものを使う。
class InvalidWordsValidator < ActiveModel::EachValidator

# record : テーブル名のこと(Micripostとか)
# attribute : カラム名(contentとか)
# value : フォームに入力された値("おはよう","foobar"とか)
  def validate_each(record, attribute, value)
# blacklist.ymlから不適切な言葉を読み取る
    blacklist = YAML.load_file('./config/blacklist.yml')
# valueが空でないか、blacklistに入力された値が含まれているかを判断
    if value.present? && blacklist.any?{ |word| value.include?(word) }
# contain_blacklist_words "内容~"と出力したくなかったので第二引数を''としている。
#(あまりいい方法ではないかもしれません。)
# :contain_blacklist_wordsはja.ymlなどに定義しておくとよい。
      record.errors.add(:contain_blacklist_words,'')
    end

# same_character_regex: 連続する五文字以上の語("あああああ")などを防ぐ
# url_regex: https(http)から始まるurlを防ぐ
# html_regex: htmlタグを防ぐ

# 正規表現のエスケープのため %r!正規表現!で囲む

    invalid_regex = { same_character_regex: %r!(.)\1{4,}!,
                      url_regex: %r!https?://[\w/:%#\$&\?\(\)~\.=\+\-]+!,
                      html_regex: %r!<(".*?"|'.*?'|[^'"])*?>!}
# invalid_regexをinvalid_key,invalid_valueとして取り出しinvalid_value.match?(value)で正規表現と一致しているかを調べる。
    if value.present? && invalid_regex.any?{|invalid_key,invalid_value| invalid_value.match?(value)}
      record.errors.add(:contain_invalid_regex, '')
    end

  end

end

適用したいモデルに以下を定義する。

今回は例としてuser.rbのnameに定義してみる。

app/models/user.rb
# validates :カラム名,Validator定義した名前: true
validates :name,invalid_words: true

以上です。お疲れさまでした。

参考にしたサイト

 - Railsガイド Active Record バリデーション
 - Rails tips: カスタムバリデータクラスを作る(翻訳)
 - Railsアプリでイタズラ投稿(NGワード)と格闘してみた話
 - コメント機能等で特定のワード(悪口など)を制限することは可能ですか?

1
1
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
1