NGワード機能を追加しようと考えていたところ、
あまり参考にできる記事が見つからなかったので、記事を書いてみました。
Qiitaで記事を書くのは初めてですので、拙い面もありますがご了承ください。
-
やりたいこと
- 不適切な言葉(下品な言葉など)を防ぎたい
- htmlタグやurl,連続した文字("あああああ"など)を防ぎたい
-
やったこと
-
- modelにカスタムバリデーションを定義する(非推奨)
-
- カスタムバリデータクラスを作る(推奨)
-
テーブル構成
テーブル名 | カラム名 |
---|---|
User | name |
Micropost | content |
Comment | content |
1,2共通の操作
ブラックリストの作成(好きなように定義してください。)
---
- 不適切な単語1
- 不適切な単語2
- 不適切な単語3
不適切な単語は.gitignoreに入れてgitに載せないようにしておきましょう。
#以下を追加
/config/blacklist.yml
1. modelにカスタムバリデーションを定義する(非推奨)
まず、CommentのcontentとMicropostのcontentにNGワード機能を付けたいと思い、以下のようにカスタムバリデーションを定義してみました。
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と間違えないように)
class Micropost < ApplicationRecord
# 先ほどapplicationで定義した関数を追加
validate :content_cannot_contain_blacklist_words
validate :content_cannot_contain_invalid_regex
end
###Comment.rbにもvalidateを追加する
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, ~ のように書きたい)
- contentにしか適用できていない
2. カスタムバリデータクラスを作る(推奨)
1の問題点を解決しようとするとカスタムバリデータクラスを作ればいいことを発見する
この通りにやってみる
Validatorの定義
まずapp/validators/[validator名].rbを作る。
今回は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に定義してみる。
# validates :カラム名,Validator定義した名前: true
validates :name,invalid_words: true
以上です。お疲れさまでした。
参考にしたサイト
- Railsガイド Active Record バリデーション
- Rails tips: カスタムバリデータクラスを作る(翻訳)
- Railsアプリでイタズラ投稿(NGワード)と格闘してみた話
- コメント機能等で特定のワード(悪口など)を制限することは可能ですか?