【问题标题】:How to force ActiveRecord to open database in read-only mode?如何强制 ActiveRecord 以只读模式打开数据库?
【发布时间】:2019-01-28 04:50:59
【问题描述】:

ActiveRecord::Base.configurations 或establish_connection() 是否有一个参数可以强制确保无法写入数据库? (如果有什么不同,那就是 Heroku Postgres 数据库)

辅助 Sinatra 应用程序(使用 ActiveRecord 5.2)需要严格对主要应用程序使用的 Heroku Postgres 数据库进行只读访问...例如,即使代码错误意外尝试编写改变,我们需要它失败。

几个 SO 线程中的建议是定义一个 readonly?方法如下图。

它几乎可以工作...除了一个重要的例外...

虽然它确实阻止了保存或更新属性,但它不会阻止更新列的写入。

APP_DB_HASH = { 
  "appdb"=>
    { "adapter"=>"postgresql", 
      "encoding" => "unicode",
      "pool" => 5,
      "url"=> ENV["APP_DATABASE_URL"] },

ActiveRecord::Base.configurations["appdb"] = {
  :adapter  => APP_DB_HASH["appdb"]["adapter"],
  :encoding  => APP_DB_HASH["appdb"]["encoding"],
  :database => uri.path.gsub('/',''),
  :username => uri.user,
  :password => uri.password,
  :port => uri.port,
  :host => uri.host
}

class AppBase < ActiveRecord::Base
  self.abstract_class = true
  establish_connection configurations["appdb"]

  # THIS DOES NOT PREVENT update_column FROM WRITING TO DATABASE!
  def readonly?
    true
  end
end

class MyModel << AppBase
...
end

结果:

> rec = MyModel.first.foo
# false

> rec.update_attributes foo: true
# GOOD: exception thrown, prevents write

> rec.foo = true
> rec.save
# GOOD: exception thrown, prevents write

> rec.update_column :foo, true
# FAIL: THE 'READONLY" DATABASE GETS WRITTEN

【问题讨论】:

    标签: activerecord readonly


    【解决方案1】:

    不幸的是,我只知道 Postgresql 的答案,但这似乎是你正在使用的。

    简单的答案是(可能在初始化程序中):

    ActiveRecord::Base.connection.execute("SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY")
    

    我的使用方式是:

    def with_read_only_connection(configuration)
      original_connection = ActiveRecord::Base.remove_connection
      ActiveRecord::Base.establish_connection(configuration)
      ActiveRecord::Base.connection.execute("SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY")
      yield
    ensure
      ActiveRecord::Base.establish_connection(original_connection)
    end
    

    这是一个使用示例:

    [5] pry(main)> with_read_only_connection(:development) do
    [5] pry(main)*   User.count
    [5] pry(main)* end
       (0.2ms)  SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY
       (96.6ms)  SELECT COUNT(*) FROM "users"
    => 24566
    
    [6] pry(main)> with_read_only_connection(:development) do
    [6] pry(main)*   User.first.update_attribute(:first_name, "Bob")
    [6] pry(main)* end
       (0.2ms)  SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY
      User Load (1.9ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
       (0.2ms)  BEGIN
      SQL (0.7ms)  UPDATE "users" SET "first_name" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["first_name", "Bob"], ["updated_at", "2019-04-06 13:14:12.270619"], ["id", 1]]
       (0.2ms)  ROLLBACK
    ActiveRecord::StatementInvalid: PG::ReadOnlySqlTransaction: ERROR:  cannot execute UPDATE in a read-only transaction
    : UPDATE "users" SET "first_name" = $1, "updated_at" = $2 WHERE "users"."id" = $3
    from .../.bundle/ruby/2.2.0/gems/activerecord-4.2.11.1/lib/active_record/connection_adapters/postgresql_adapter.rb:602:in `exec_prepared'
    

    注意 - 它只会在连接时调用SET SESSION CHARACTERISTICS..

    【讨论】:

    • 等我能测试出来我会回来接受的,谢谢
    【解决方案2】:

    当我尝试在只读环境中执行 (ActiveRecord) 操作时,我偶然发现了这个问题。过了一会儿,我意识到,我可以简单地将所有内容包装在事务中并在执行后回滚:

    ActiveRecord::Base.transaction do
      # do your thing!
      raise ActiveRecord::Rollback
    end
    

    我希望这会有所帮助。非常感谢@jjthrash,感谢SET SESSION CHARACTERISTICS 的提示,我在生产时一直使用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-11
      • 2011-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-29
      相关资源
      最近更新 更多