Ruby on Rails app。で個々のユーザーのパーミッションはデータベーステーブルに保存されていると思いました。特定のユーザーに読み取りまたは書き込みを許可するかどうかをそれぞれのリソース(モデルのインスタンス)に決定させるのが最善です。この決定をコントローラーで毎回行うことは確かにそれほど乾燥しません。
問題は、これを行うには、モデルが現在のユーザーにアクセスして、may_read
?(current_user
、attribute_name
)</ code>。ただし、一般的にモデルはセッションデータにアクセスできません。
現在のスレッドに現在のユーザーへの参照を保存するためのいくつかの提案があります。 in このブログ投稿 。これは確かに問題を解決します。
近隣のGoogleの結果から、Userクラスに現在のユーザーへの参照を保存するように勧められました。これは、アプリケーションが一度に多くのユーザーに対応する必要のない人が考えたものと思われます。 ;)
簡単に言えば、モデル内から現在のユーザー(つまりセッションデータ)にアクセスしたいのは、私が 間違ったことをする から来るという感じがします。
どうして間違っているのか教えてもらえますか?
current_user
をモデルに入れないというあなたの本能は正しいと思います。
ダニエルのように、私はすべてスキニーコントローラーとファットモデルを担当していますが、責任の明確な区分もあります。コントローラーの目的は、着信要求とセッションを管理することです。モデルは「ユーザーxはこのオブジェクトに対してyを実行できますか?」という質問に答えられるはずですが、current_user
を参照するのは無意味です。コンソールにいる場合はどうですか? cronジョブが実行されている場合はどうなりますか?
多くの場合、モデルに適切な権限APIがあれば、これはいくつかのアクションに適用されるbefore_filters
の1行で処理できます。ただし、状況がさらに複雑になっている場合は、コントローラーが肥大化するのを防ぎ、モデルが過度に結合されないようにするために、より複雑な承認ロジックをカプセル化する別のレイヤー(おそらくlib/
)を実装することができますWeb要求/応答サイクル。
多くの人がこの質問に答えていますが、2セントをすぐに追加したかっただけです。
ユーザーモデルで#current_userアプローチを使用する場合は、スレッドセーフのため、注意して実装する必要があります。
方法としてThread.currentを使用したり、値を保存および取得したりすることを忘れない場合は、Userでクラス/シングルトンメソッドを使用しても構いません。ただし、Thread.currentをリセットする必要があるため、次のリクエストは許可すべきでないパーミッションを継承しないため、それほど簡単ではありません。
私がやろうとしているのは、クラスまたはシングルトン変数に状態を保存する場合、スレッドセーフをウィンドウの外に投げていることを思い出してください。
データベースの操作は、モデルの仕事です。現在のリクエストのユーザーを知ることを含む、Webリクエストの処理は、コントローラーの仕事です。
したがって、モデルインスタンスが現在のユーザーを知る必要がある場合、コントローラーはそれを伝える必要があります。
def create
@item = Item.new
@item.current_user = current_user # or whatever your controller method is
...
end
これは、Item
がattr_accessor
ために current_user
。
(注-最初にこの回答を 別の質問 に投稿しましたが、質問がこの質問の複製であることに気づきました。)
私はすべてスキニーコントローラーとファットモデルに専念しており、authはこの原則を破るべきではないと思います。
私はRailsで1年間コーディングしてきました。私はPHPコミュニティから来ています。私にとって、現在のユーザーを「request-long global」として設定するのは簡単な解決策です。これは、いくつかのフレームワークでデフォルトで行われます。
Yiiでは、Yii :: $ app-> user-> identityを呼び出すことで現在のユーザーにアクセスできます。 http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html を参照してください
Lavavelでは、Auth :: user()を呼び出して同じことを行うこともできます。 http://laravel.com/docs/4.2/security を参照してください
コントローラから現在のユーザーを渡すことができるのはなぜですか?
マルチユーザーをサポートするシンプルなブログアプリケーションを作成していると仮定しましょう。公開サイト(ユーザーはブログ投稿を読んだりコメントしたりできます)と管理サイト(ユーザーはログインしており、データベースのコンテンツにCRUDアクセスできます)の両方を作成しています。
「標準のAR」は次のとおりです。
class Post < ActiveRecord::Base
has_many :comments
belongs_to :author, class_name: 'User', primary_key: author_id
end
class User < ActiveRecord::Base
has_many: :posts
end
class Comment < ActiveRecord::Base
belongs_to :post
end
次に、公開サイトで:
class PostsController < ActionController::Base
def index
# Nothing special here, show latest posts on index page.
@posts = Post.includes(:comments).latest(10)
end
end
それはきれいでシンプルでした。ただし、管理サイトでは、さらに何かが必要です。これは、すべての管理コントローラーの基本実装です。
class Admin::BaseController < ActionController::Base
before_action: :auth, :set_current_user
after_action: :unset_current_user
private
def auth
# The actual auth is missing for brievery
@user = login_or_redirect
end
def set_current_user
# User.current needs to use Thread.current!
User.current = @user
end
def unset_current_user
# User.current needs to use Thread.current!
User.current = nil
end
end
そのため、ログイン機能が追加され、現在のユーザーはグローバルに保存されます。これで、ユーザーモデルは次のようになります。
# Let's extend the common User model to include current user method.
class Admin::User < User
def self.current=(user)
Thread.current[:current_user] = user
end
def self.current
Thread.current[:current_user]
end
end
User.currentはスレッドセーフになりました
他のモデルを拡張して、これを活用しましょう。
class Admin::Post < Post
before_save: :assign_author
def default_scope
where(author: User.current)
end
def assign_author
self.author = User.current
end
end
投稿モデルが拡張され、現在ログインしているユーザーの投稿のみがあるように感じられます。 それはなんてクールなの!
管理者の投稿コントローラーは次のようになります。
class Admin::PostsController < Admin::BaseController
def index
# Shows all posts (for the current user, of course!)
@posts = Post.all
end
def new
# Finds the post by id (if it belongs to the current user, of course!)
@post = Post.find_by_id(params[:id])
# Updates & saves the new post (for the current user, of course!)
@post.attributes = params.require(:post).permit()
if @post.save
# ...
else
# ...
end
end
end
コメントモデルの場合、管理者バージョンは次のようになります。
class Admin::Comment < Comment
validate: :check_posts_author
private
def check_posts_author
unless post.author == User.current
errors.add(:blog, 'Blog must be yours!')
end
end
end
私見:これは、ユーザーが自分のデータのみにアクセス/変更できるようにするための強力で安全な方法です。すべてのクエリを「current_user.posts.whatever_method(...)」で開始する必要がある場合に、開発者がテストコードを記述する必要がある量を考えますか?たくさん。
私が間違っているが、私が思う場合は私を修正してください:
問題の分離がすべてです。コントローラーのみが認証チェックを処理する必要があることが明らかな場合でも、現在ログインしているユーザーがコントローラーレイヤーに留まることは決してありません。
覚えておく必要があるのは、使いすぎないことです! User.currentを使用していないメールワーカーがいるか、コンソールなどからアプリケーションにアクセスしている可能性があることに注意してください。
古代のスレッドですが、Rails 5.2で始まることに注意する価値があります。これには、解決策があります:現在のモデルシングルトン、ここで説明します: https://evilmartians.com/ chronicles/Rails-5-2-active-storage-and-beyond#current-everything
私の推測では、current_user
は最終的にUserインスタンスなので、User
モデルまたはデータモデルにこれらのアクセス許可を追加しないでください。または質問?
私の推測では、何とかしてモデルを再構築し、現在のユーザーをパラメーターとして渡す必要があります。
class Node < ActiveRecord
belongs_to :user
def authorized?(user)
user && ( user.admin? or self.user_id == user.id )
end
end
# inside controllers or helpers
node.authorized? current_user
私は、質問者の根底にあるビジネスニーズを何も知らない人々による「それをしないでください」という応答にいつも驚いています。はい、一般的にこれは避けるべきです。しかし、それが適切で非常に有用な状況があります。私は自分で持っていました。
ここに私の解決策がありました:
def find_current_user
(1..Kernel.caller.length).each do |n|
RubyVM::DebugInspector.open do |i|
current_user = eval "current_user rescue nil", i.frame_binding(n)
return current_user unless current_user.nil?
end
end
return nil
end
これは、current_user
に応答するフレームを探してスタックを逆方向にたどります。見つからない場合は、nilを返します。期待される戻り値の型を確認することにより、そしておそらくフレームの所有者がコントローラの型であることを確認することにより、より堅牢にすることができますが、通常はうまく動作します。
私は私のアプリケーションでこれを持っています。単に現在のコントローラーのsession [:user]を探し、それをUser.current_userクラス変数に設定します。このコードは実稼働環境で機能し、非常に単純です。私はそれを思いついたと言うことができればいいのですが、私は他のインターネットの天才からそれを借りたと信じています。
class ApplicationController < ActionController::Base
before_filter do |c|
User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?
end
end
class User < ActiveRecord::Base
cattr_accessor :current_user
end
Declarative Authorizationプラグインを使用していますが、これはcurrent_user
で言及していることと似たようなことをします。before_filter
を使用してcurrent_user
を引き出し、モデル層が到達できる場所に保存します。次のようになります。
# set_current_user sets the global current user for this request. This
# is used by model security that does not have access to the
# controller#current_user method. It is called as a before_filter.
def set_current_user
Authorization.current_user = current_user
end
ただし、宣言的承認のモデル機能は使用していません。私はすべて「Skinny Controller-Fat Model」アプローチに賛成ですが、私の考えでは、認可(および認証)はコントローラー層に属するものだと感じています。
私の感じでは、現在のユーザーはMVCモデルの「コンテキスト」の一部であり、現在の時間、現在のロギングストリーム、現在のデバッグレベル、現在のトランザクションなど、現在のユーザーを考えます。関数への引数としての「モダリティ」。または、現在の関数本体の外部のコンテキストの変数で使用できるようにします。スレッドのローカルコンテキストは、最も簡単なスレッドセーフのため、グローバル変数またはその他のスコープ変数よりも優れた選択肢です。 Josh Kが言ったように、スレッドローカルの危険性は、タスクの後にクリアする必要があるということです。 MVCはアプリケーションの現実をいくぶん簡略化した図であり、すべてがそれでカバーされるわけではありません。