Rails:向模型添加方法以根据 current_user 执行检查?

发布于 2024-09-28 13:01:51 字数 803 浏览 3 评论 0原文

我有一个看起来像这样的模型:

class Comment < ActiveRecord::Base
  ...
  #allow editing comment if it is moderated and the user passed-in
  #is the one that owns the comment
  def can_edit?(user)
    moderated? and user.Type == User and user.id == self.user_id
  end
  ...
end

在视图中调用:

<%= link_to 'Show Comment', @comment if @comment.can_show?(current_user) %>

我需要在许多不同的模型中编写许多这样的方法 - 进行验证检查以查看是否允许 current_user 在模型上做某事。

但感觉很麻烦——尤其是需要检查传入的 user 确实是 User 类型的对象。

做这种事情的干净、最佳实践的方法是什么?我走在正确的轨道上吗? (即我应该将此类方法添加到模型或其他地方)

注意

我使用范围查询来获取评论和其他模型,但在某些情况下我无法确定查询范围,因此我必须使用 can_xxxx? 方法

我正在使用限定范围的查询来获取评论和其他模型,但在某些情况下 我现在的做法算是“胖模特”吗?

I have a model that looks something like this:

class Comment < ActiveRecord::Base
  ...
  #allow editing comment if it is moderated and the user passed-in
  #is the one that owns the comment
  def can_edit?(user)
    moderated? and user.Type == User and user.id == self.user_id
  end
  ...
end

And a call in a view:

<%= link_to 'Show Comment', @comment if @comment.can_show?(current_user) %>

I need to write many such methods in many different models - sort of validation checks to see if current_user is allowed to
do something on a model.

But it feels cumbersome - especially the need to check that the passed-in user is indeed a object of type User.

What's a clean, best-practice way to do this sort of thing? Am I on the right track? (i.e. should I be adding such methods to a model or somewhere else)

Note

I am using scoped queries to get the comments and other models, but in some cases I cannot scope the query so I have to use the can_xxxx? methods

Ps. Is what I'm doing considered a "fat model"?

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

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

发布评论

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

评论(3

咆哮 2024-10-05 13:01:51

创建一个包含所有授权方法的模块,并将该模块包含到所有需要授权的类中。

将名为 authorization.rb 的文件添加到 app/models 目录。

module Authorization

  def can_edit?(user)
    moderated? and user.is_a?(User) and user.id == self.user_id
  end

  def self.included(base)
    base.send(:extend, ClassMethods)
  end

  module ClassMethods
    # add your class methods here.
  end
end

将名为 authorization.rb 的文件添加到 config/initializers 目录。

%w(
 Comment
 Post
).each do |klass|
  klass.constantize.include(Authorization)
end

现在 CommentPost 模型将拥有所有授权方法。

其他方法是使用当前的named_scope。

class Post
  named_scope :accessible, lambda { |user|
    {
      :conditions => { :user_id => user.id, :moderated => true}
    }
  }
end

Post 控制器操作

class PostsController

  def index
    @posts = Post.acessible(current_user)
    # process data
  end

  def show
    # throws record not found when the record is not accessible.
    @post = Post.acessible(current_user).find(params[:id])
    # process data
  end

end

我喜欢这种方法,因为它使用相同的逻辑来访问对象数组或单个对象。

您可以将named_scope添加到模块中以避免重复定义:

module Authorization
  def self.included(base)
    base.named_scope :accessible, lambda { |user|
      {
        :conditions => { :user_id => user.id, :moderated => true}
      }
    }
  end

  module ClassMethods
    # add your class methods here.
  end
end

确保按照前面的建议将模块包含在所需的类中。

Create a module containing all the authorization methods and include the module to all the classes requiring authorization.

Add a file called authorization.rb to app/models directory.

module Authorization

  def can_edit?(user)
    moderated? and user.is_a?(User) and user.id == self.user_id
  end

  def self.included(base)
    base.send(:extend, ClassMethods)
  end

  module ClassMethods
    # add your class methods here.
  end
end

Add a file called authorization.rb to config/initializers directory.

%w(
 Comment
 Post
).each do |klass|
  klass.constantize.include(Authorization)
end

Now Comment and Post models will have all the authorization methods.

Other approach is to use your current named_scope.

class Post
  named_scope :accessible, lambda { |user|
    {
      :conditions => { :user_id => user.id, :moderated => true}
    }
  }
end

Post controller actions

class PostsController

  def index
    @posts = Post.acessible(current_user)
    # process data
  end

  def show
    # throws record not found when the record is not accessible.
    @post = Post.acessible(current_user).find(params[:id])
    # process data
  end

end

I like this approach as it uses the same logic for accessing an array of objects or a single object.

You can add the named_scope to the module to avoid repeated definitions:

module Authorization
  def self.included(base)
    base.named_scope :accessible, lambda { |user|
      {
        :conditions => { :user_id => user.id, :moderated => true}
      }
    }
  end

  module ClassMethods
    # add your class methods here.
  end
end

Make sure to include the module in required classes as suggested earlier.

遮了一弯 2024-10-05 13:01:51

我不认为你所做的事情一定是错误的。不过,我看到了三种简化方法:

1)跟踪 self.user 以及 self.user_id。然后您可以说:

def can_show?(user)
  moderated ? and user == self.user
end

注意,这可能会增加数据库查找时间和/或内存占用的开销。

2)使用#is_a?为了检查祖先而不仅仅是类平等:

def can_show?(user)
  moderated ? and user.is_a?( User ) and user.id == self.user_id
end

3)如果传入非用户是错误的,您可能想在发生这种情况时引发错误:

def can_show?(user)
  raise "expected User, not #{ user.class.to_s }" unless user.is_a?(User)
  moderated ? and user.id == self.user_id
end

至于问题2,我还没有听说过术语“胖模型”。是否在任何地方特别提到过?

I don't think what you're doing is necessarily wrong. I see three ways to simplify, though:

1) track self.user as well as self.user_id. Then you can say:

def can_show?(user)
  moderated ? and user == self.user
end

Note, this might add overhead either with DB lookup times and/or memory footprint.

2) Use #is_a? in order to check ancestry and not just class equality:

def can_show?(user)
  moderated ? and user.is_a?( User ) and user.id == self.user_id
end

3) If passing in a non-user is wrong, you might want to raise an error when this happens:

def can_show?(user)
  raise "expected User, not #{ user.class.to_s }" unless user.is_a?(User)
  moderated ? and user.id == self.user_id
end

As for Q2, I haven't heard the terminology "fat model." Is it referenced anywhere in particular?

明媚如初 2024-10-05 13:01:51

回复:胖模型和瘦控制器

这是将逻辑推入模型而不是将其放在控制器(或更糟糕的是视图)中的想法。

一个很大的好处是帮助测试;还将更多逻辑放置在模型中而不是控制器中的重点。请记住,控制器与多个模型一起使用并不罕见。

将逻辑放入模型而不是控制器中通常意味着业务规则被纳入模型中——这正是它们所属的位置。

一个可能的缺点是,控制器可用但模型中不可用的任何信息都需要显式传递到模型的方法中或使用模型的实例变量“设置”。

您需要将当前用户传递到模型的示例说明了这个问题。

但总的来说,我和其他许多人发现,胖模特的效果往往比不胖模特的效果更好。

Re: fat model and skinny controller

This is the idea of pushing logic into the model rather than having it in the controller (or worse, the view).

A big benefit is to help with testing; also the focus of placing more logic in the model rather than in the controller. Remember that it is not uncommon to have controllers work with multiple models.

Putting the logic into a model rather than a controller often means that the business rules are being baked into the model--which is exactly where they belong.

A possible downside is that any information available to the controller that is not available in the model needs to be explicitly passed into the model's methods or "set" using a model's instance variables.

Your example of needing to pass the current user into the model illustrates the issue.

Overall though, I and many others have found that fat models tend to work out better than not.

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