Rails:从 Ruby on Rails 中的模型内访问 current_user

发布于 2024-08-07 06:35:15 字数 698 浏览 4 评论 0原文

我需要在 Ruby on Rails 应用程序中实现细粒度的访问控制。各个用户的权限保存在数据库表中,我认为最好让相应的资源(即模型的实例)决定是否允许某个用户读取或写入它。每次在控制器中做出这个决定肯定不会很干燥。
问题是,为了做到这一点,模型需要访问当前用户,调用类似 may_read?(current_user, attribute_name< /代码>)。不过,模型通常无法访问会话数据。

有很多建议在当前线程中保存对当前用户的引用,例如在 这篇博文。这肯定能解决问题。

附近的 Google 结果建议我在 User 类中保存对当前用户的引用,我猜这是由应用程序不必同时容纳大量用户的人想到的。 ;)

长话短说,我感觉我希望从模型中访问当前用户(即会话数据)来自于我 做错了

你能告诉我我哪里错了吗?

I need to implement fine-grained access control in a Ruby on Rails app. The permissions for individual users are saved in a database table and I thought that it would be best to let the respective resource (i.e. the instance of a model) decide whether a certain user is allowed to read from or write to it. Making this decision in the controller each time certainly wouldn’t be very DRY.
The problem is that in order to do this, the model needs access to the current user, to call something like may_read?(current_user, attribute_name). Models in general do not have access to session data, though.

There are quite some suggestions to save a reference to the current user in the current thread, e.g. in this blog post. This would certainly solve the problem.

Neighboring Google results advised me to save a reference to the current user in the User class though, which I guess was thought up by someone whose application does not have to accommodate a lot of users at once. ;)

Long story short, I get the feeling that my wish to access the current user (i.e. session data) from within a model comes from me doing it wrong.

Can you tell me how I’m wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

春庭雪 2024-08-14 06:35:15

我想说,您将 current_user 排除在模型之外的直觉是正确的。

和丹尼尔一样,我也赞成瘦控制者和胖模型,但也有明确的职责分工。控制器的目的是管理传入的请求和会话。该模型应该能够回答“用户 x 可以对此对象执行 y 操作吗?”的问题,但引用 current_user 是没有意义的。如果你在控制台怎么办?如果它正在运行一个 cron 作业怎么办?

在许多情况下,在模型中使用正确的权限 API 时,可以使用适用于多个操作的一行 before_filters 来处理此问题。但是,如果事情变得更加复杂,您可能需要实现一个单独的层(可能在 lib/ 中)来封装更复杂的授权逻辑,以防止您的控制器变得臃肿,并防止您的模型变得过于臃肿。与网络请求/响应周期紧密耦合。

I'd say your instincts to keep current_user out of the model are correct.

Like Daniel I'm all for skinny controllers and fat models, but there is also a clear division of responsibilities. The purpose of the controller is to manage the incoming request and session. The model should be able to answer the question "Can user x do y to this object?", but it's nonsensical for it to reference the current_user. What if you are in the console? What if it's a cron job running?

In many cases with the right permissions API in the model, this can be handled with one-line before_filters that apply to several actions. However if things are getting more complex you may want to implement a separate layer (possibly in lib/) that encapsulates the more complex authorization logic to prevent your controller from becoming bloated, and prevent your model from becoming too tightly coupled to the web request/response cycle.

冷心人i 2024-08-14 06:35:15

控制器应该告诉模型实例

使用数据库是模型的工作。处理 Web 请求,包括了解当前请求的用户,是控制器的工作。

因此,如果模型实例需要知道当前用户,控制器应该告诉它。

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

这假设 Item 具有 current_userattr_accessor

(注意 - 我首先在另一个问题上发布了这个答案,但我刚刚注意到该问题与此问题重复。)

The Controller should tell the model instance

Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.

Therefore, if a model instance needs to know the current user, a controller should tell it.

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

This assumes that Item has an attr_accessor for current_user.

(Note - I first posted this answer on another question, but I've just noticed that question is a duplicate of this one.)

和我恋爱吧 2024-08-14 06:35:15

虽然这个问题已经有很多人回答了,但我只是想快速添加我的两分钱。

由于线程安全性,在用户模型上使用 #current_user 方法应谨慎实施。

如果您记得使用 Thread.current 作为存储和检索值的方式,那么可以在 User 上使用类/单例方法。但这并不那么容易,因为您还必须重置 Thread.current,以便下一个请求不会继承它不应该继承的权限。

我想说的一点是,如果您将状态存储在类或单例变量中,请记住您正在将线程安全性抛到九霄云外。

Although this question has been answered by many I just wanted to add my two cents in quickly.

Using the #current_user approach on the User model should be implemented with caution due to Thread Safety.

It is fine to use a class/singleton method on User if you remember to use Thread.current as a way or storing and retrieving your values. But it is not as easy as that because you also have to reset Thread.current so the next request does not inherit permissions it shouldn't.

The point I am trying to make is, if you store state in class or singleton variables, remember that you are throwing thread safety out the window.

◇流星雨 2024-08-14 06:35:15

为了更清楚地了解 armchairdj 的答案

我在开发 Rails 6 应用程序时遇到了这一挑战。

我是这样解决的

从 Rails 5.2 开始,我们现在可以添加一个神奇的 Current 单例,它的作用就像一个可以从应用程序内的任何位置访问的全局存储。

首先,在您的模型中定义它:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

接下来,在控制器中的某个位置设置用户,使其可以在模型、作业、邮件或任何您想要的地方访问:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_user

  private

  def set_current_user
    Current.user = current_user
  end
end

现在您可以在您的模型中调用Current.user

# app/models/post.rb
class Post < ApplicationRecord
  # You don't have to specify the user when creating a post,
  # the current one would be used by default
  belongs_to :user, default: -> { Current.user }
end

或者您可以在表单中调用Current.user

# app/forms/application_registration.rb
class ApplicationRegistration
  include ActiveModel::Model

  attr_accessor :email, :user_id, :first_name, :last_name, :phone,
                    
  def save
    ActiveRecord::Base.transaction do
      return false unless valid?

      # User.create!(email: email)
      PersonalInfo.create!(user_id: Current.user.id, first_name: first_name,
                          last_name: last_name, phone: phone)

      true
    end
  end
end

或者您可以在视图中调用Current.user

# app/views/application_registrations/_form.html.erb
<%= form_for @application_registration do |form| %>
  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, value: Current.user.email %>
  </div>

  <div class="field">
    <%= form.label :first_name %>
    <%= form.text_field :first_name, value: Current.user.personal_info.first_name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

注意:您可能会说:“此功能违反了关注点分离原则! ”是的,确实如此。如果感觉不对,请勿使用它。

您可以在这里阅读有关此答案的更多信息: 当前所有内容

就这些了。

我希望这有帮助

To shed more light on armchairdj's answer

I faced this challenge when working on a Rails 6 application.

Here's how I solved it:

From Rails 5.2 you now we can add a magic Current singleton which acts like a global store accessible from anywhere inside your app.

First, define it in your models:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

Next, set the user somewhere in your controller to make it accessible in models, jobs, mailers, or wherever you want:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_user

  private

  def set_current_user
    Current.user = current_user
  end
end

Now you can call the Current.user in your models:

# app/models/post.rb
class Post < ApplicationRecord
  # You don't have to specify the user when creating a post,
  # the current one would be used by default
  belongs_to :user, default: -> { Current.user }
end

OR you can call the Current.user in your forms:

# app/forms/application_registration.rb
class ApplicationRegistration
  include ActiveModel::Model

  attr_accessor :email, :user_id, :first_name, :last_name, :phone,
                    
  def save
    ActiveRecord::Base.transaction do
      return false unless valid?

      # User.create!(email: email)
      PersonalInfo.create!(user_id: Current.user.id, first_name: first_name,
                          last_name: last_name, phone: phone)

      true
    end
  end
end

OR you can call the Current.user in your views:

# app/views/application_registrations/_form.html.erb
<%= form_for @application_registration do |form| %>
  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, value: Current.user.email %>
  </div>

  <div class="field">
    <%= form.label :first_name %>
    <%= form.text_field :first_name, value: Current.user.personal_info.first_name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Note: You may say: “This feature breaks the Separation of Concerns principle!” Yes, it does. Don’t use it if it feels wrong.

You can read more about this answer here: Current everything

That's all.

I hope this helps

一抹苦笑 2024-08-14 06:35:15

古老的线程,但值得注意的是,从 Rails 5.2 开始,有一个内置的解决方案:当前模型单例,此处介绍:https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond#current-everything

Ancient thread, but worth noting that starting in Rails 5.2, there's a baked-in solution to this: the Current model singleton, covered here: https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond#current-everything

空‖城人不在 2024-08-14 06:35:15

我完全喜欢瘦控制器和胖模型,我认为 auth 不应该打破这个原则。

我已经使用 Rails 编码一年了,我来自 PHP 社区。对我来说,将当前用户设置为“请求长全局”是一个简单的解决方案。在某些框架中这是默认完成的,例如:

在 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

因此添加了登录功能,并将当前用户保存到全局中。现在 User 模型看起来像这样:

# 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

Post 模型已扩展,因此感觉好像只有当前登录的用户的帖子。 这太酷了!

管理帖子控制器可能看起来像这样:

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 或者您可能从控制台等访问该应用程序......

I'm all in for skinny controller & fat models, and I think auth shouldn't break this principle.

I've been coding with Rails for an year now and I'm coming from PHP community. For me, It's trivial solution to set the current user as "request-long global". This is done by default in some frameworks, for example:

In Yii, you may access the current user by calling Yii::$app->user->identity. See http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html

In Lavavel, you may also do the same thing by calling Auth::user(). See http://laravel.com/docs/4.2/security

Why if I can just pass the current user from controller??

Let's assume that we are creating a simple blog application with multi-user support. We are creating both public site (anon users can read and comment on blog posts) and admin site (users are logged in and they have CRUD access to their content on the database.)

Here's "the standard ARs":

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

Now, on the public site:

class PostsController < ActionController::Base
  def index
    # Nothing special here, show latest posts on index page.
    @posts = Post.includes(:comments).latest(10)
  end
end

That was clean & simple. On the admin site however, something more is needed. This is base implementation for all admin controllers:

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

So login functionality was added and the current user gets saved to a global. Now User model looks like this:

# 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 is now thread-safe

Let's extend other models to take advantage of this:

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

Post model was extended so that it feels like there's only currently logged in user's posts. How cool is that!

Admin post controller could look something like this:

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

For Comment model, the admin version could look like this:

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

IMHO: This is powerful & secure way to make sure that users can access / modify only their data, all in one go. Think about how much developer needs to write test code if every query needs to start with "current_user.posts.whatever_method(...)"? A lot.

Correct me if I'm wrong but I think:

It's all about separation of concerns. Even when it's clear that only controller should handle the auth checks, by no means the currently logged in user should stay in the controller layer.

Only thing to remember: DO NOT overuse it! Remember that there may be email workers that are not using User.current or you maybe accessing the application from a console etc...

心房敞 2024-08-14 06:35:15

这是 2021 年的召唤。从 Rails 5.2 开始,有一个新的全局 API 可以使用,但请谨慎使用,如 API 文档中所述:

https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html

抽象超类,提供线程隔离的属性单例,在每次请求之前和之后自动重置。这使您可以让整个系统轻松使用所有每个请求的属性。

警告:很容易过度使用像 Current 这样的全局单例,从而导致模型混乱。 Current 只能用于少数顶级全局变量,例如帐户、用户和请求详细信息。卡在 Current 中的属性或多或少应该被所有请求的所有操作使用。如果您开始将特定于控制器的属性放在那里,就会造成混乱。

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_user

  private

  def set_current_user
    Current.user = current_user
  end
end

# and now in your model
# app/models/post.rb
class Post < ApplicationRecord
  # You don't have to specify the user when creating a post,
  # the current one would be used by default
  belongs_to :user, default: -> { Current.user }
end

This is 2021 calling. Since rails 5.2 there's a new global API that can be used, but use it with caution as stated in the API docs:

https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html

Abstract super class that provides a thread-isolated attributes singleton, which resets automatically before and after each request. This allows you to keep all the per-request attributes easily available to the whole system.

A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. Current should only be used for a few, top-level globals, like account, user, and request details. The attributes stuck in Current should be used by more or less all actions on all requests. If you start sticking controller-specific attributes in there, you're going to create a mess.

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_user

  private

  def set_current_user
    Current.user = current_user
  end
end

# and now in your model
# app/models/post.rb
class Post < ApplicationRecord
  # You don't have to specify the user when creating a post,
  # the current one would be used by default
  belongs_to :user, default: -> { Current.user }
end
¢好甜 2024-08-14 06:35:15

好吧,我的猜测是 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

Well my guess here is that current_user is finally a User instance, so, why don't u add these permissions to the User model or to the data model u want to have the permissions to be applied or queried?

My guess is that u need to restructure your model somehow and pass the current user as a param, like doing:

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
蓝色星空 2024-08-14 06:35:15

我总是对那些对提问者的潜在业务需求一无所知的人的“不要这样做”的回答感到惊讶。是的,通常应该避免这种情况。但在某些情况下它既合适又非常有用。我自己也只有一张。

这是我的解决方案:

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。通过确认预期的返回类型,并且可能通过确认框架的所有者是一种控制器,可以使其更加健壮,但通常工作得很好。

I'm always amazed at "just don't do that" responses by people who know nothing of the questioner's underlying business need. Yes, generally this should be avoided. But there are circumstances where it's both appropriate and highly useful. I just had one myself.

Here was my solution:

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

This walks the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller, but generally works just dandy.

挽清梦 2024-08-14 06:35:15

我正在使用声明性授权插件,它的作用与您在 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

不过,我没有使用声明性授权的模型功能。我完全赞成“瘦控制器 - 胖模型”方法,但我的感觉是授权(以及身份验证)属于控制器层。

I'm using the Declarative Authorization plugin, and it does something similar to what you are mentioning with current_user It uses a before_filter to pull current_user out and store it where the model layer can get to it. Looks like this:

# 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

I'm not using the model features of Declarative Authorization though. I'm all for the "Skinny Controller - Fat Model" approach, but my feeling is that authorization (as well as authentication) is something that belongs in the controller layer.

人海汹涌 2024-08-14 06:35:15

我的应用程序中有这个。它只是查找当前控制器 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
  attr_accessor :current_user
end

I have this in an application of mine. It simply looks for the current controllers session[:user] and sets it to a User.current_user class variable. This code works in production and is pretty simple. I wish I could say I came up with it, but I believe I borrowed it from an internet genius elsewhere.

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
  attr_accessor :current_user
end
顾铮苏瑾 2024-08-14 06:35:15

我的感觉是当前用户是 MVC 模型“上下文”的一部分,将当前用户想象为当前时间、当前日志流、当前调试级别、当前事务等。您可以传递所有这些“ modalities”作为函数的参数。或者您可以通过当前函数体之外的上下文中的变量来使其可用。由于最简单的线程安全性,线程局部上下文是比全局变量或其他作用域变量更好的选择。正如 Josh K 所说,线程局部变量的危险在于必须在任务完成后清除它们,这是依赖注入框架可以为您做的事情。 MVC 是应用程序现实的某种简化图景,但它并未涵盖所有内容。

My feeling is the current user is part of the "context" of your MVC model, think of the current user like of the current time, the current logging stream, the current debugging level, the current transaction etc. You could pass all these "modalities" as arguments into your functions. Or you make it available by variables in a context outside the current function body. Thread local context is the better choice than global or otherwise scoped variables because of easiest thread safety. As Josh K said, the danger with thread locals is that they must be cleared after the task, something a dependency injection framework can do for you. MVC is a somewhat simplified picture of the application reality and not everything is covered by it.

走走停停 2024-08-14 06:35:15

我参加这个聚会已经很晚了,但是如果您需要细粒度的访问控制或具有复杂的权限,我肯定会推荐 Cancancan Gem:
https://github.com/CanCanCommunity/cancancan

它允许您定义您的每个操作的权限控制器可以控制您想要的任何内容,并且由于您在任何控制器上定义了当前能力,因此您可以发送所需的任何参数,例如 current_user。您可以在 ApplicationController 中定义通用的 current_ability 方法并自动设置:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
  def current_ability
    klass = Object.const_defined?('MODEL_CLASS_NAME') ? MODEL_CLASS_NAME : controller_name.classify
    @current_ability ||= "#{klass.to_s}Abilities".constantize.new(current_user, request)
  end
end

这样,您就可以将一个 UserAbilities 类链接到您的 UserController, PostAbilities 到你的 PostController 等等。然后在那里定义复杂的规则:

class UserAbilities
  include CanCan::Ability

  def initialize(user, request)
    if user
      if user.admin?
        can :manage, User
      else
        # Allow operations for logged in users.
        can :show, User, id: user.id
        can :index, User if user
      end
    end
  end
end

这是一个很棒的宝石!希望有帮助!

I am so very late to this party, but if you need fine-grained access control or have complex permissions I would definitely recommend the Cancancan Gem:
https://github.com/CanCanCommunity/cancancan

It allows you to define permissions at every action in your Controller to whatever you want, and since you define the current ability on any controller you can send any parameters you need, like the current_user. You can define a generic current_ability method in ApplicationController and set up things automagically:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
  def current_ability
    klass = Object.const_defined?('MODEL_CLASS_NAME') ? MODEL_CLASS_NAME : controller_name.classify
    @current_ability ||= "#{klass.to_s}Abilities".constantize.new(current_user, request)
  end
end

This way, you can have a UserAbilities Class linked to your UserController, PostAbilities to your PostController and so on. And then define the complex rules in there:

class UserAbilities
  include CanCan::Ability

  def initialize(user, request)
    if user
      if user.admin?
        can :manage, User
      else
        # Allow operations for logged in users.
        can :show, User, id: user.id
        can :index, User if user
      end
    end
  end
end

It's a great Gem! hope it helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文