オブザーバーでセッションにアクセスするのは良い考えですか?
-
02-07-2019 - |
質問
Ruby on Rails アプリケーションでユーザーのアクションをログに記録したいと考えています。
これまでのところ、更新と作成後にデータベースにログを挿入するモデル オブザーバーがあります。ログに記録されたアクションをどのユーザーが実行したかを保存するには、セッションへのアクセスが必要ですが、これには問題があります。
まず、MVC モデルが壊れます。第二に、技術はハッキング的なものから風変わりなものまで多岐にわたり、おそらく実装を Mongrel サーバーに結び付けることさえあります。
取るべき正しいアプローチは何でしょうか?
解決
これは非常に興味深い質問だと思います。ここでちょっと声を出して考えてみます…
最終的に私たちが直面しているのは、特定の機能セットを実現するために、デザイン パターンで許容される慣行に違反するという決断です。したがって、私たちは自分自身に問いかける必要があります
1) 考えられる解決策は何ですか? ない MVC パターンに違反する
2) 考えられる解決策は何ですか? するだろう MVC パターンに違反する
3) どのオプションが最適ですか?私はデザインパターンと標準プラクティスが非常に重要であると考えていますが、同時に、それらを保持することでコードがより複雑になる場合、正しい解決策はプラクティスに違反することである可能性が非常に高いです。これについては私に同意しない人もいるかもしれません。
まず、#1 について考えてみましょう。
思いつく限り、次のような解決策が考えられます。
A) これらのアクションを誰が実行しているかに本当に興味がある場合、このデータを何らかの方法でモデルに保存する必要がありますか?これにより、この情報がオブザーバーに提供されるようになります。また、これは、ActiveRecord クラスの他のフロントエンド呼び出し元が同じ機能を取得できることも意味します。
B) エントリの作成者を理解することにあまり興味がなく、Web アクション自体のログに興味がある場合は、コントローラーのアクションを「観察」することを検討するかもしれません。Rails ソースをいじってからしばらく経っているので、誰がその ActiveRecord::Observer でモデルを「監視」しているのかわかりませんが、それをコントローラー オブザーバーに適応させることはできるかもしれません。この意味で、あなたはもうモデルを観察していません。そのオブザーバーに対してセッションやその他のコントローラー タイプのデータ情報を提供することは理にかなっています。C) 「構造」を最小限に抑えた最も単純な解決策は、監視しているアクション メソッドの最後にロギング コードを単純にドロップすることです。
ここで、MVC の慣行を破るオプション #2 を検討してください。
A) あなたが提案したように、モデルのオブザーバーがセッションデータにアクセスできるようにする手段を見つけることができます。モデルをビジネス ロジックに結合しました。
B) ここでは他に考えられません :)
私の個人的な傾向は、あなたのプロジェクトについてこれ以上詳しく知りませんが、レコードに人々を結び付けたい場合は 1A、これに興味のある場所が数か所しかない場合は 1C です。すべてのコントローラーとアクションに対して堅牢なログ ソリューションが本当に必要な場合は、1B を検討してください。
モデルのオブザーバーにセッション データを見つけさせるのは少し「臭い」ので、他のプロジェクト/状況/コンテキストでモデルを使用しようとすると壊れる可能性があります。
他のヒント
うーん、これは厄介な状況です。MVC を適切に動作させるには、MVC に違反する必要があります。
私なら次のようなことをします:
class MyObserverClass < ActiveRecord::Observer
cattr_accessor :current_user # GLOBAL VARIABLE. RELIES ON RAILS BEING SINGLE THREADED
# other logging code goes here
end
class ApplicationController
before_filter :set_current_user_for_observer
def set_current_user_for_observer
MyObserverClass.current_user = session[:user]
end
end
これは少しハック的ですが、私がこれまで見てきた他の多くのコアレールのものと比べてハック的ではありません。
スレッドセーフにするために必要なのは、cattr_accessor を適切なメソッドに変更し、そのデータをスレッドローカル ストレージに保存することだけです (これはいずれにしても jruby で実行する場合にのみ重要です)。
MVC を壊すというのは正しいです。コントローラーでコールバックを使用することをお勧めします。これは主に、オブザーバーに何も記録させたくない状況 (save が呼び出されたが検証に失敗したモデルなど) があるためです。
私が選んだ回答で提案されていることを実行するきれいな方法を見つけました。
http://pjkh.com/articles/2009/02/02/creating-an-audit-log-in-rails
このソリューションは、AuditLog モデルと TrackChanges モジュールを使用して、任意のモデルに追跡機能を追加します。ただし、更新または作成するときにコントローラーに行を追加する必要があります。
以前は、このようなことを行うとき、User モデル クラスを拡張して「現在のユーザー」の概念を含める傾向がありました。
以前の回答を見ると、実際のアクティブ レコード ユーザーをセッションに保存するという提案が表示されます。これにはいくつかの欠点があります。
- セッション データベースに大きなオブジェクトを格納する可能性があります
- これは、ユーザーのコピーが常に (または強制的にログアウトされるまで) 'キャッシュ' されることを意味します。これは、このユーザーのステータスの変更は、ユーザーがログアウトして再度ログインするまで認識されないことを意味します。これは、たとえば、ユーザーを無効にしようとすると、ユーザーがログオフして再度ログオンするまで待機することを意味します。これはおそらくあなたが望んでいる動作ではありません。
そのため、リクエストの開始時に (フィルター内で) セッションから user_id を取得し、ユーザーを読み取り、User.current_user を設定します。
このようなもの...
class User cattr_accessor :current_user end class Application before_filter :retrieve_user def retrieve_user if session[:user_id].nil? User.current_user = nil else User.current_user = User.find(session[:user_id]) end end end
それ以降は、それは些細なことになるはずです。