APIから取得した結果を保存するModelやユーザー情報を扱うModel、中間結果を保存するテーブル等、直接更新してしまうと危険なのでコンソールから起動するときにはReadonlyにしたいなぁと思い色々やり方を考えてみたのでメモしておきます📝
※rails console -s
もありますが、つけ忘れたりするのと、トランザクションをコミットしないだけなのでトランザクション分離レベルによっては影響する可能性があるのかなぁと思いまして。Readonlyの例外はクエリ発行前に発生しそうなのでより安全なのかなと。
実際のコード
本体のコードは以下の通りでActiveRecord#readonly!
を参考にreadonly?
をtrueで返すModelReadonly::Readonly
を用意して、ModelReadonly::Service.call!
を呼び出したときに引数で指定したModel + ファイルに設定したModelにModelReadonly::Readonly
をincludeしてReadonlyにしています。
# frozen_string_literal: true # NOTE: ActiveRecord#readonly!を参考にreadonly?でtrueを返却し読み取り専用にしてる。 # ※touch等は防げないので完全ではないため注意 # * https://github.com/rails/rails/blob/11c2c0ef2f5e3382fa95fc0e9e94835f74a31702/activerecord/lib/active_record/core.rb#L689-L691 # * https://github.com/rails/rails/blob/11c2c0ef2f5e3382fa95fc0e9e94835f74a31702/activerecord/lib/active_record/core.rb#L641-L644 module ModelReadonly::Readonly def readonly? true end end class ModelReadonly::Service class InvalidSettings < StandardError; end def self.call!(models: [], config_path: nil) raise InvalidSettings if models.empty? && config_path.nil? new(models: models + configured_models(config_path)).call end def self.configured_models(config_path) return [] if config_path.nil? configured_yml = YAML.load_file(config_path) configured_yml['readonly_models'].map(&:constantize) end def initialize(models: []) @models = models end attr_reader :models def call models.each do |model| model.class_eval { include ModelReadonly::Readonly } end end end
あとはinitializer内で以下のような形で利用して上げればOKです🙆♂️
# frozen_string_literal: true # NOTE: 本番環境では誤操作防止のため特定のモデルをコンソール起動時にReadonlyにする。 if Rails.env.production? Rails.application.configure do console { ModelReadonly::Service.call!(models: [Entry]) } end end
ファイルを用意して以下のような形でもOKです🙆♂️
readonly_models: - Entry
# frozen_string_literal: true # NOTE: 本番環境では誤操作防止のため特定のモデルをコンソール起動時にReadonlyにする。 if Rails.env.production? Rails.application.configure do console do config_path = Rails.root.join('lib/model_readonly/readonly_models.yml') ModelReadonly::Service.call!(config_path: config_path) end end end
以下のような形で更新しようとしたときにReadOnlyRecordの例外が発生します🚨
irb(main)> Entry.last.update(title: "foo") Feed is marked as readonly (ActiveRecord::ReadOnlyRecord)