Rails ActiveRecord:当记录进入特定状态时锁定属性

想知道是否有插件或设置ActiveRecord类的最佳方式,例如,当记录进入“已发布”状态时,某些属性被冻结,以便它们不会被篡改。

您可以通过将@readonly设置为true(在方法中)来冻结整个AR :: B对象,但这将锁定所有属性。

我建议的方法是定义属性setter方法,在传递给super之前检查当前状态:

class Post < ActiveRecord::Base def author=(author) super unless self.published? end def content=(content) super unless self.published? end end 

[编辑]或大量的属性:

 class Post < ActiveRecord::Base %w(author content comments others).each do |method| class_eval <<-"end_eval", binding, __FILE__, __LINE__ def #{method}=(val) super unless self.published? end end_eval end end 

当然我会主张插入一个插件与他人分享,并添加一个很好的DSL来访问如下: disable_attributes :author, :content, :comments, :when => :published?

编辑不应编辑的属性是validation错误:

 class Post < ActiveRecord::Base validate :lock_down_attributes_when_published private def lock_down_attributes_when_published return unless published? message = "must not change when published" errors.add(:title, message) if title_changed? errors.add(:published_at, message) if published_at_changed? end end 

这使用2.2左右引入的ActiveRecord :: Dirty扩展。

如果您处于某种状态,则可以添加自定义validation以阻止对属性的更改。 您可以将事物直接硬编码到validation中。 但我更喜欢稍微更健壮的方法,使用定义白名单的常量(允许在状态中更改的属性列表)或黑名单(不允许在状态中更改的属性列表)。

以下是两种方法的示例。 每种方法都假设模型中有一个状态方法,它将当前/新状态作为字符串返回。

白名单方法

 WhiteListStateLockMap = { "state_1" => [ "first_attribute_allowed_to_change_in_state_1", "second_attribute_allowed_to_change_in_state_1", ... ], "state_2" => [ "first_attribute_allowed_to_change_in_state_2", "second_attribute_allowed_to_change_in_state_2", ... ], ... } validates :state_lock def state_lock # ensure that all changed elements are on the white list for this state. unless changed & WhiteListStateLockMap[state] == changed # add an error for each changed attribute absent from the white list for this state. (changed - WhiteListStateLockMap[state]).each do |attr| errors.add attr, "Locked while #{state}" end end end 

黑名单方法

 BlackListStateLockMap = { "state_1" => [ "first_attribute_not_allowed_to_change_in_state_1, "second_attribute_not_allowed_to_change_in_state_1, ... ], "state_2" => [ "first_attribute_not_allowed_to_change_in_state_2", "second_attribute_not_allowed_to_change_in_state_2", ... ], ... } validates :state_lock def state_lock # ensure that no changed attributes are on the black list for this state. unless (changed & BlackListStateLockMap[state]).empty? # add an error for all changed attributes on the black list for this state. (BlackListStateLockMap[state] & changed).each do |attr| errors.add attr, "Locked while #{state}" end end end 

如果特定的国家只是persisted? ,然后attr_readonly是最好的选择。

attr_readonly (*attributes) public

列为readonly的属性将用于创建新记录,但更新操作将忽略这些字段。

测试(由THAiSi提供 ):

 class MyModel < ActiveRecord::Base attr_readonly :important_type_thingie end #RSpec describe MyModel do its('class.readonly_attributes') { should include "important_type_thingie" } it "should not update the thingie" do m = create :my_model, :important_type_thingie => 'foo' m.update_attributes :important_type_thingie => 'bar' m.reload.important_type_thingie.should eql 'foo' end end