有一种简单的方法可以将Rails ActiveRecord模型设为只读吗?

时间:2011-04-12 20:44:07

标签: ruby-on-rails ruby-on-rails-3 activerecord

我希望能够在数据库中创建一条记录,但是阻止Rails从那时起进行更改。我知道在数据库级别仍然可以进行更改。

我相信attr_readonly在属性级别上做我想要的,但我不想手动指定字段...我宁愿有更多的白名单方法。

另外,我知道有一个:read_only选项用于关联,但我不想限制对象的“readonlyness”,如果它是通过关联获取的。

最后,我希望能够销毁记录,例如:dependent => :破坏协会的工作。

因此,总结一下:1)允许创建记录,2)允许删除记录,3)防止更改已保留的记录。

9 个答案:

答案 0 :(得分:74)

查看ActiveRecord::Persistence,所有内容最终都会在幕后调用create_or_update

def create_or_update
  raise ReadOnlyRecord if readonly?
  result = new_record? ? create : update
  result != false
end

原来如此!只是:

def readonly?
  !new_record?
end

答案 1 :(得分:47)

我找到了一个更简洁的解决方案,它使用after_initialize回调:

class Post < ActiveRecord::Base
  after_initialize :readonly!
end

答案 2 :(得分:25)

为什么不在数据库上创建具有只读访问权限的用户,并让rails使用该帐户。

但是,如果您需要模型级访问权限,可以将以下内容添加到特定模型中:

 def readonly?
    true
  end

  def before_destroy
    raise ActiveRecord::ReadOnlyRecord
  end

答案 3 :(得分:16)

此博客文章仍然有效:http://ariejan.net/2008/08/17/activerecord-read-only-models/

如果添加方法,基本上可以依赖ActiveRecord的验证:

def readonly?
  true
end

答案 4 :(得分:5)

TL; OP的DR

class YourModel < ActiveRecord::Base
  before_save { false } # prevent create & update, allows destroy

  # ... 
end

一般

  • 仅阻止创建:before_create { false }
  • 仅阻止更新:before_update { false }
  • 仅防止销毁:before_destroy { false } # does not prevent delete

另请参阅:http://guides.rubyonrails.org/active_record_callbacks.html

答案 5 :(得分:2)

这似乎相当有效,可能有点矫枉过正,但对于我的情况,我真的想确保我的应用程序永远不会创建,保存,更新或销毁模型中的任何记录。

module ReadOnlyModel
  def readonly?() true end
  def create_or_update() raise ActiveRecord::ReadOnlyRecord end
  before_create { raise ActiveRecord::ReadOnlyRecord }
  before_destroy { raise ActiveRecord::ReadOnlyRecord }
  before_save { raise ActiveRecord::ReadOnlyRecord }
  before_update { raise ActiveRecord::ReadOnlyRecord }
end

class MyModel < ActiveRecord::Base
  include ReadOnlyModel
  # ...
end

由于OP要求能够创建和销毁但不保存或更新我相信这将起作用

module SaveAndDestroyOnlyModel
  before_save { raise ActiveRecord::ReadOnlyRecord }
  before_update { raise ActiveRecord::ReadOnlyRecord }
end

class MyModel < ActiveRecord::Base
  include SaveAndDestroyOnlyModel
  # ...
end

不是完全正确的异常,但我认为足够接近。

答案 6 :(得分:0)

自定义验证器可以执行此操作:

validate :nothing_changed, unless: :new_record? # make immutable

...

def nothing_changed
  errors.add(:base, "Record is read-only") if self.changed?
end

答案 7 :(得分:0)

寻找一种方法来实现@Nate提出的相同控制(避免任何类型的create / update / delete),但只能在我的应用程序的特定部分以及所有模型中一次使用此方法,而我已经创建了Ruby {{ 3}}:

module ReadOnlyRailsMode
  CLASS_METHODS    = ActiveRecord::Base.methods
    .select { |m| m =~ /(update|create|destroy|delete|save)[^\?]*$/ }

  INSTANCE_METHODS = ActiveRecord::Base.instance_methods
    .select { |m| m =~ /(update|create|destroy|delete|save)[^\?]*$/ }

  refine ActiveRecord::Base.singleton_class do
    CLASS_METHODS.each do |m|
      define_method(m) do |*args|
        raise ActiveRecord::ReadOnlyRecord
      end
    end
  end

  refine ActiveRecord::Base do
    def readonly?; true; end

    INSTANCE_METHODS.each do |m|
      define_method(m) do |*args|
        raise ActiveRecord::ReadOnlyRecord
      end
    end
  end
end

并仅在代码的特定部分使用它:

class MyCoolMailerPreview < ActionMailer::Preview
  using ReadOnlyRailsMode 
end

(这是一个真实的用例,我一直在寻找一种方法来避免人们从ActionMailer :: Previews内部创建和编辑真实记录,因为我想允许在生产中进行预览,但是如果有人错误地创建了可以更改的预览,真实数据,这会造成混乱)。

代码很难重新定义所有方法(创建,创建!等),因为其目的是改变所有模型的行为,并且“ before_create”之类的回调不能用于此目的,因为它们不会仅在本地使用“使用”范围,从而更改整个应用程序。

这种方法对我有用,我可以在一个类中为所有模型显式阻止所有这些方法,并且不要与应用程序的其余部分搞混。不幸的是,到目前为止,改进还不适用于子类,因此在我的情况下,默认情况下我无法阻止所有插入父类(ActionMailer :: Preview)的插入,这是我的最初目标,但每个类的阻止是一个很好的起点。

我的应用程序需要完善所有方法,但是可以仅对一些有趣的方法(如destroy或update)进行控制,这对所有情况都适用,包括原始问题中的一种。

答案 8 :(得分:0)

.default_scope 似乎也有效,不过上面的一些答案可能会更好,因为可以通过使用 .unscoped 来规避这种方式。不过,这可能对某些人的需求有益。

使用 Postgres 制作临时只读模型来查询 pg_type 表的示例。

Class.new(ActiveRecord::Base) { self.table_name = 'pg_type'; self.primary_key = :oid; default_scope { readonly } }.first.readonly?
# => true