7
9

More than 3 years have passed since last update.

Railsアプリでイタズラ投稿(NGワード)と格闘してみた話

Last updated at Posted at 2021-02-12

前提

そうだ、ポートフォリオアプリの開発記録忘れる前に記事にしときたいな...
➡︎ Qiitaに投稿
30代元食品工場長がフードシェアサービスのポートフォリオを約2週間で開発した話
➡︎ 何故かLGTMたくさん頂けた
(技術参考になるかも怪しい記事にLGTMありがとうございます!)
➡︎ ポートフォリオアプリにイタズラ投稿発生

  • 「ああああああ」
  • 「(NGワードなどなど)」

➡︎ なるほど...これは考えてなかった。勉強になるな...(現在)

環境

image.png
image.png

課題の確認と対策想定

  1. 「あああああ...」など同じ文字連続への対応 ➡︎ モデルへのバリデーション追加でいけそう⭕️
  2. 下ネタやNGワードへの対応 ➡︎ モデルへのバリデーション追加かgemで対応できるかも⭕️
  3. 問題ある画像への対応 ➡︎ 技術的に無理✖️
  4. 悪質ユーザーが登録することへの対応 ➡︎ Email対策してもいたちごっこだと予想して諦め✖️

対策実施

変更前

models/item.rb

#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を使い、固有メッセージも加えます。

models/item.rb

   + 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: 確認する

Image from Gyazo

うまくいきました。

 NGWORD_REGEX = /(.)\1{4,}/.freeze

引き渡し場所を見る通り、正規表現通り、どの場所からでも5文字連続だと制限されるようになりました。

2. 下ネタやNGワードへの対応

最初はNGWORD_REGEXのように、NGワードを定義してとも思ったのですが、

  1. コードが長くなりすぎる
  2. 可読性の悪さ
  3. 追加しずらい

ということで却下。

カスタムバリデーションも考慮したのですが、良いgemないかと調査したところ、
少々古いのですが、Obscenityというgemを見つけたので実装してみることに。
こちらの記事ブラックリストワードをフィルタリングする Obscenityを参考にさせていただきました。

1: gemの導入

gemfile
+ gem 'obscenity'
通常コンソール
bundle install
Dockerの場合
docker-compose up --build  

2: 設定ファイルの作成

config/initializers に obscenity.rb を作成

config/initializers/obscenity.rb
Obscenity.configure do |config|
  config.blacklist   = "config/blacklist.yml"  # ブラックリストへのpath
  config.replacement = :stars       # NGワード置き換え設定
end

詳細はObscenityを確認してください。

3: 単語ファイルの作成

config に blacklist.yml を作成
規制されるのがこわいので、単語は載せません!
追加もしやすい設計です。

config/blacklist.yml
---
- NGワード
- NGワード
- NGワード
- NGワード
- NGワード
#以下略

4: 読み込みの設定

このままだと、modelで使用できないので、
config/application.rbobscenity/active_modelを呼ぶ設定を記述します。
私はこちらで上手くいったのですが、他の場所がいいよ!という方は教えていただきたいです...

config/application.rb
# 省略
require "action_cable/engine"
require "sprockets/railtie"
+ require 'obscenity/active_model'
# require "rails/test_unit/railtie"

# 省略

5: バリデーションの記述

では、itemモデルに戻ってバリデーションをかけていきます。
方針としては、
1.商品名にNGワードは✖️
2.説明や引き渡し場所も✖️ にしたいところですが、商品名と違い最初がたまたまNGワードになっていたという場合も考慮して、*に置き換えたいです。

というわけで、以下のように記述。

models/item.rb

   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ワードにしてみました。

商品名を確認してみます。

Image from Gyazo

成功しています。
空白が前に合っても大丈夫です。
しかし、前後に単語が入る、つまり文章の一部と認識されると機能しません。
巻きスカートとかだと通っちゃいますが、
巻き スカートとか、巻き、スカートのように、
1単語と認識される記述であれば制限してくれます。

伏字も確認してみます。

このように入力してみましょう。
Image from Gyazo

こうなりました。
Image from Gyazo

1単語として認識された部分が、伏字(***)になっていますが、文章中は認識されてないので、表示されています。
ちなみに編集する際にもフォーム内の文字は伏字化されています。

これで一応は、あからさまなNGワードがそうとわかるように表示されることはなさそうです。

結論

勉強になりました。
私が知らないだけかもしれませんが、railsアプリのNGワード対策って思ったほど、資料がありません。
正規表現もそうですが、意外とこうゆう部分を見逃している方は多そうです。(自分も)
今回、調べながらで時間かかりましたが、数時間程度で実装できたので、ポートフォリオアプリに実装してみても面白いのではないでしょうか。
NG具合を見てみたい方は、私のポートフォリオアプリを使ってみてください。
ただし、終わったら消してくださるよう伏してお願い致しますm(_ _)m

参考サイト様

基本的な正規表現
Railsガイド バリデーション
Railsバリデーションまとめ
https://teratail.com/questions/283606
同じパターンの繰り返しを探す正規表現
Obscenity
ブラックリストワードをフィルタリングする Obscenity

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