attr_accessor和attr_accessible的区别

240
在Rails中,attr_accessorattr_accessible有什么区别?据我所知,使用attr_accessor用于为变量创建getter和setter方法,以便我们可以像Object.variableObject.variable = some_value这样访问变量。
我了解到attr_accessible使得特定的变量可以被外部访问。请问有人能告诉我它们之间的区别吗?

4
你说的对,“attr_accessor”用于生成getter和setter方法。请查看我在之前的一个问题中回答的内容,其中详细解释了“attr_accessible”的区别:https://dev59.com/-3E85IYBdhLWcg3wr1ux#2652919。如果你在那之后需要任何其他特定的细节,请更新你的问题。 - mikej
2
在Rails 4中,attr_accessible已不再受支持,除非您使用protected_attributes gem,如https://dev59.com/pWQm5IYBdhLWcg3wuhHS的顶部答案所述(2014年7月)。 - emery
6个回答

262

attr_accessor 是 Ruby 中的一个方法,它可以同时生成 getter 和 setter。而 attr_accessible 则是 Rails 中的一个方法,它允许你通过 new(attrs)update_attributes(attrs) 方法批量赋值。

以下是一个示例:

Order.new({ :type => 'Corn', :quantity => 6 })

你可以想象订单也可能有一个折扣码,比如:price_off。如果你不把:price_off标记为attr_accessible,就能防止恶意代码这样做:

Order.new({ :type => 'Corn', :quantity => 6, :price_off => 30 })

即使你的表单中没有 :price_off 字段,但如果它在模型中存在,默认情况下是可用的。这意味着通过精心构造的POST请求仍然可以设置该字段。使用 attr_accessible 可以白名单列出可以进行批量赋值的属性。


2
为什么Rails文档中没有attr_accessible的说明?http://api.rubyonrails.org/ - Chloe
19
看起来Rails4有一种新的做事方式。请查看这个答案:https://dev59.com/pWQm5IYBdhLWcg3wuhHS - Paul Rubel
1
由于强参数已经取代了attr_accessible的使用,因此请参考http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters。 - Imran Ahmad

174
许多人在本主题和谷歌上都很好地解释了 attr_accessible,它指定了一个属性白名单,允许以批量方式更新(对象模型的所有属性一起更新)。这主要(也仅仅)是为了保护您的应用程序免受“大规模分配”黑客攻击。
这在官方Rails文档中解释如下:Mass Assignment attr_accessor 是一个ruby代码,用于(快速)创建类中的setter和getter方法。就是这样。
现在,缺少的解释是:当您以某种方式创建(Rails)模型与数据库表之间的链接时,您永远不需要在模型中使用 attr_accessor 来创建setter和getter,以便能够修改表中的记录。
这是因为您的模型从 ActiveRecord::Base 类继承了所有方法,该类已经为您定义了基本的CRUD访问器(创建、读取、更新、删除)。
这在官方文档中解释如下:Rails ModelOverwriting default accessor(向下滚动到“覆盖默认访问器”章节)
例如,我们有一个名为“users”的数据库表,其中包含三列“firstname”,“lastname”和“role”:
SQL指令:
CREATE TABLE users (
  firstname string,
  lastname string
  role string
);

我假设您已经在config/environment/production.rb文件中设置了选项config.active_record.whitelist_attributes = true,以保护您的应用程序免受“大规模分配”攻击。这里有解释:Mass Assignment

下面的Rails模型将完美地与您的模型一起工作:

class User < ActiveRecord::Base

end

然而,您需要在控制器中单独更新每个用户属性,以使表单视图正常工作:
def update
    @user = User.find_by_id(params[:id])
    @user.firstname = params[:user][:firstname]
    @user.lastname = params[:user][:lastname]

    if @user.save
        # Use of I18 internationalization t method for the flash message
        flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
    end

    respond_with(@user)
end

现在为了简化你的生活,你不想为 User 模型创建一个复杂的控制器。 所以,你将在 Class 模型中使用 attr_accessible 特殊方法:

class User < ActiveRecord::Base

  attr_accessible :firstname, :lastname

end

所以你可以使用“高速公路”(批量赋值)来更新:
def update
    @user = User.find_by_id(params[:id])

    if @user.update_attributes(params[:user])
        # Use of I18 internationlization t method for the flash message
        flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
    end

    respond_with(@user)
end

你没有在attr_accessible列表中添加"role"属性,因为你不允许用户自己设置他们的角色(如管理员)。相反,你会在另一个特殊的管理视图上自行进行操作。
虽然你的用户视图没有显示“role”字段,但是黑客很容易通过HTTP POST请求将“role”包含在params哈希中。缺少attr_accessible上的“role”属性是为了保护应用程序免受此类攻击。
你仍然可以像下面这样单独修改user.role属性,但不能同时修改所有属性。
@user.role = DEFAULT_ROLE

为什么要使用attr_accessor

这是因为你的用户表单显示了一个在用户表中不存在的字段。

例如,假设你的用户视图显示了一个“请告诉管理员我在这里”的字段。你不想将此信息存储在表中。你只希望Rails向你发送一封电子邮件,警告你有一个“疯狂”的用户已经订阅了。

为了能够使用这些信息,你需要将它们暂时存储在某个地方。那么,在user.peekaboo属性中恢复信息就更容易了。

因此,你需要将这个字段添加到你的模型中:

class User < ActiveRecord::Base

  attr_accessible :firstname, :lastname
  attr_accessor :peekaboo

end

因此,在控制器的某个地方使用user.peekaboo属性时,您将能够做出明智的决策,发送电子邮件或进行其他操作。

当您执行user.save时,ActiveRecord不会在表中保存“peekaboo”属性,因为她在模型中没有看到匹配此名称的列。


49

attr_accessor是 Ruby 方法,用于为同名实例变量提供读写方法。因此,它等同于:

class MyModel
  def my_variable
    @my_variable
  end
  def my_variable=(value)
    @my_variable = value
  end
end

attr_accessible是Rails中的一个方法,用于确定哪些变量可以在批量赋值过程中设置。

当你提交一个表单时,如果像MyModel.new params[:my_model]那样使用,则需要一些更细致的控制,以防止用户提交不应该提交的内容。

你可以使用attr_accessible :email来允许用户更新他们的电子邮件地址。但是你不应该使用attr_accessible :email, :salary,因为这将允许用户通过表单提交设置他们的薪水。换句话说,他们可以通过此方式获取加薪。

这种类型的信息需要进行显式处理。仅仅从表单中删除它是不够的。用户可以使用 Firebug 添加元素到表单中来提交工资字段,或者使用内置的curl命令向控制器update方法提交新的工资数据,他们还可以创建一个脚本来提交包含该信息的POST请求。

因此,attr_accessor是用于创建存储变量的方法,而attr_accessible则是用于批量赋值的安全性。


2
你有一个笔误,在代码块之后应该写attr_accessible - Chubas
很棒的写作,我喜欢这个类的例子。如果包括“:as”的解释,将获得额外(虚假)奖励分! - Ian Vaughan
模型通过ActiveRecord::Base进行扩展。class User < ActiveRecord::Base - Green

18

attr_accessor是Ruby代码,用于当您没有数据库中的列,但仍想在表单中显示字段时使用。唯一允许这样做的方法是attr_accessor:fieldname,您可以在视图中使用此字段,或者如果您想要的话,在模型中使用它,但主要用于视图。

让我们考虑以下示例

class Address
    attr_reader :street
    attr_writer :street  
    def initialize
        @street = ""
    end
end

在这里,我们使用了可读属性attr_reader和可写属性attr_writer来进行访问。但是我们可以使用attr_accessor实现相同的功能。简而言之,attr_accessor提供对getter和setter方法的访问。

所以修改后的代码如下:

class Address
    attr_accessor :street  
    def initialize
        @street = ""
    end
end

attr_accessible 允许你列出所有你想允许批量赋值的字段。相反的是 attr_protected,它表示我不希望任何人被允许进行批量赋值。很可能这是你数据库中不想让任何人随意更改的字段,比如状态字段等等。


2
那么你的意思是,如果我在迁移中创建了字段,然后使用attr_accessible使它们可用,就不需要创建getter和setter了吗?但是如果该字段不在数据库中,为什么attr_accessible不起到getter/setter的作用呢?如果我包含一行“has_secure_password”,那么即使它们不在数据库中,attr_accessible也足以允许getter/setter访问:password和:password_confirmation。非常困惑 ;) - tentimes

2

简单来说:

attr_accessor是一个gettersetter方法,而attr_accessible则是用来确定特定属性是否可以被访问。仅此而已。


我想补充一下,我们应该使用Strong parameter来保护免受大规模分配的影响,而不是使用attr_accessible

干杯!


2

快速简洁的区别概述:

attr_accessor 是在类中创建读/写访问器的简单方法。当您的数据库中没有相应列,但仍希望在表单中显示字段时使用它。这个字段是Rails模型中的一个 "虚拟属性"

虚拟属性 - 不对应于数据库中的列的属性。

attr_accessible 用于标识控制器方法可访问的属性,并使其可用于批量赋值。它只允许访问指定的属性,拒绝其他属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接