前提
そうだ、ポートフォリオアプリの開発記録忘れる前に記事にしときたいな...
➡︎ Qiitaに投稿
30代元食品工場長がフードシェアサービスのポートフォリオを約2週間で開発した話
➡︎ 何故かLGTMたくさん頂けた
(技術参考になるかも怪しい記事にLGTMありがとうございます!)
➡︎ ポートフォリオアプリにイタズラ投稿発生
- 「ああああああ」
- 「(NGワードなどなど)」
➡︎ なるほど...これは考えてなかった。勉強になるな...(現在)
環境
課題の確認と対策想定
- 「あああああ...」など同じ文字連続への対応 ➡︎ モデルへのバリデーション追加でいけそう⭕️
- 下ネタやNGワードへの対応 ➡︎ モデルへのバリデーション追加かgemで対応できるかも⭕️
- 問題ある画像への対応 ➡︎ 技術的に無理✖️
- 悪質ユーザーが登録することへの対応 ➡︎ Email対策してもいたちごっこだと予想して諦め✖️
対策実施
変更前
#item(商品管理)モデル
#アソシエーション関連と追加メソッドは省略。
#変更がわかりやすいよう、あえて元々のコードとは異なるカラムの順番にしました。
with_options presence: true do
validates :name, length: { maximum: 40 }
validates :description, length: { maximum: 200 }
validates :contact_location
validates :quantity, numericality: { greater_than: -1, less_than: 100 }
validates :deadline
validates :price, format: { with: /\A[0-9]+\z/ }, numericality: { less_than: 1_000_000 }
validates :image
end
with_options numericality: { other_than: 1 } do
validates :prefecture_id
validates :category_id
validates :condition_id
end
バリデーション知らない方に説明すると、データ入力の際に、下記のような制限をかけてます。
with_options presence: true do ~ end #〜範囲のデータは「 空入力禁止 」
with_options numericality: { other_than: 1 } do ~ end #〜範囲のデータは「 1(未選択)禁止 」
length: { maximum: 数値 } #最大文字数
numericality: { greater_than: -1, less_than: 100 } #数値限定で最大値と最小値設定
format: { with: /\A[0-9]+\z/ } #指定した正規表現含ませる設定
他、日付やユーザー関連の制限は追加メソッドで指定していたりします。
バリデーションに関しては、他ユーザー様記事やRailsガイドをご参考にされると良いと思います。
1. 「あああああ...」など同じ文字連続への対応
文字指定なら正規表現!
以前から感じてますが、初心者はSQLと正規表現の習得は必修だと思います。
自分もまだスラスラとは書けないので身につけたいです。
1: NGWORD_REGEXを設定します。
語句はなんでもいいですが、エラーが出るので必ずバリデーションの前に設定してください。
今回は5文字以上の同じ連続した文字があった場合は制限する
ことにしました。
NGWORD_REGEX = /(.)\1{4,}/.freeze
#1 (.) で、全文字中1個指定(後ろで\1などするために()で囲む)
#2 \1 で、直前文字と同じ1文字
#3 {4,} で、\1が4桁以上のとき
#結果 1で文字1個指定して、2で同じ文字を選んで、3で2が4桁以上だったら = 同じ文字が5桁連続
#応用で同じフレーズとかの指定も可
正規表現は奥が深いですね。
2: NGWORD_REGEXを利用して、バリデーションをかける
指定した正規表現含ませるformat:
に、除くを意味するwithout
を合わせると良さそうです。
format: { without: NGWORD_REGEX }
3: 記述する
商品名:name
、商品説明:description
、引き渡し場所:contact_location
にかけようと思いますが、
3箇所にかけるので、またwith_options
を使い、固有メッセージも加えます。
+ NGWORD_REGEX = /(.)\1{4,}/.freeze
with_options presence: true do
+ with_options format: { without: NGWORD_REGEX, message: 'は5文字以上の繰り返しは禁止です' } do
validates :name, length: { maximum: 40 }
validates :description, length: { maximum: 200 }
validates :contact_location
+ end
validates :quantity, numericality: { greater_than: -1, less_than: 100 }
validates :deadline
validates :price, format: { with: /\A[0-9]+\z/ }, numericality: { less_than: 1_000_000 }
validates :image
end
with_options numericality: { other_than: 1 } do
validates :prefecture_id
validates :category_id
validates :condition_id
end
4: 確認する
うまくいきました。
NGWORD_REGEX = /(.)\1{4,}/.freeze
引き渡し場所を見る通り、正規表現通り、どの場所からでも5文字連続だと制限されるようになりました。
2. 下ネタやNGワードへの対応
最初はNGWORD_REGEX
のように、NGワードを定義してとも思ったのですが、
- コードが長くなりすぎる
- 可読性の悪さ
- 追加しずらい
ということで却下。
カスタムバリデーションも考慮したのですが、良いgemないかと調査したところ、
少々古いのですが、Obscenityというgemを見つけたので実装してみることに。
こちらの記事ブラックリストワードをフィルタリングする Obscenityを参考にさせていただきました。
1: gemの導入
+ gem 'obscenity'
bundle install
docker-compose up --build
2: 設定ファイルの作成
config/initializers に obscenity.rb を作成
Obscenity.configure do |config|
config.blacklist = "config/blacklist.yml" # ブラックリストへのpath
config.replacement = :stars # NGワード置き換え設定
end
詳細はObscenityを確認してください。
3: 単語ファイルの作成
config に blacklist.yml を作成
規制されるのがこわいので、単語は載せません!
追加もしやすい設計です。
---
- NGワード
- NGワード
- NGワード
- NGワード
- NGワード
#以下略
4: 読み込みの設定
このままだと、modelで使用できないので、
config/application.rb
にobscenity/active_model
を呼ぶ設定を記述します。
私はこちらで上手くいったのですが、他の場所がいいよ!という方は教えていただきたいです...
# 省略
require "action_cable/engine"
require "sprockets/railtie"
+ require 'obscenity/active_model'
# require "rails/test_unit/railtie"
# 省略
5: バリデーションの記述
では、itemモデルに戻ってバリデーションをかけていきます。
方針としては、
1.商品名にNGワードは✖️
2.説明や引き渡し場所も✖️ にしたいところですが、商品名と違い最初がたまたまNGワードになっていたという場合も考慮して、*に置き換えたいです。
というわけで、以下のように記述。
NGWORD_REGEX = /(.)\1{4,}/.freeze
with_options presence: true do
with_options format: { without: NGWORD_REGEX, message: 'は5文字以上の繰り返しは禁止です' } do
+ validates :name, length: { maximum: 40 },obscenity: { message: 'はNGワードになっています' }
+ validates :description, length: { maximum: 200 },obscenity: { sanitize: true }
+ validates :contact_location,obscenity: { sanitize: true }
end
validates :quantity, numericality: { greater_than: -1, less_than: 100 }
validates :deadline
validates :price, format: { with: /\A[0-9]+\z/ }, numericality: { less_than: 1_000_000 }
validates :image
end
with_options numericality: { other_than: 1 } do
validates :prefecture_id
validates :category_id
validates :condition_id
end
obscenity: true
# 上記で、バリーデーションがかけられるが、
obscenity: { message: 'はNGワードになっています' }
# のように、固有メッセージ付きで記述
obscenity: { sanitize: true }
# NGワードを置き換え設定、obscenity.rb の config.replacement = :stars 通りに*に置き換え
6: 確認する
今回はわかりやすく「スカート」をNGワードにしてみました。
商品名を確認してみます。
成功しています。
空白が前に合っても大丈夫です。
しかし、前後に単語が入る、つまり文章の一部と認識されると機能しません。
巻きスカート
とかだと通っちゃいますが、
巻き スカート
とか、巻き、スカート
のように、
1単語と認識される記述であれば制限してくれます。
伏字も確認してみます。
1単語として認識された部分が、伏字(***)になっていますが、文章中は認識されてないので、表示されています。
ちなみに編集する際にもフォーム内の文字は伏字化されています。
これで一応は、あからさまなNGワードがそうとわかるように表示されることはなさそうです。
結論
勉強になりました。
私が知らないだけかもしれませんが、railsアプリのNGワード対策って思ったほど、資料がありません。
正規表現もそうですが、意外とこうゆう部分を見逃している方は多そうです。(自分も)
今回、調べながらで時間かかりましたが、数時間程度で実装できたので、ポートフォリオアプリに実装してみても面白いのではないでしょうか。
NG具合を見てみたい方は、私のポートフォリオアプリを使ってみてください。
ただし、終わったら消してくださるよう伏してお願い致しますm(_ _)m
参考サイト様
基本的な正規表現
Railsガイド バリデーション
Railsバリデーションまとめ
https://teratail.com/questions/283606
同じパターンの繰り返しを探す正規表現
Obscenity
ブラックリストワードをフィルタリングする Obscenity