# frozen_string_literal: true require "request_store" module PaperTrail # Manages variables that affect the current HTTP request, such as `whodunnit`. # # Please do not use `PaperTrail::Request` directly, use `PaperTrail.request`. # Currently, `Request` is a `Module`, but in the future it is quite possible # we may make it a `Class`. If we make such a choice, we will not provide any # warning and will not treat it as a breaking change. You've been warned :) # # @api private module Request class InvalidOption < RuntimeError end class << self # @api private def clear_transaction_id self.transaction_id = nil end # Sets any data from the controller that you want PaperTrail to store. # See also `PaperTrail::Rails::Controller#info_for_paper_trail`. # # PaperTrail.request.controller_info = { ip: request_user_ip } # PaperTrail.request.controller_info # => { ip: '127.0.0.1' } # # @api public def controller_info=(value) store[:controller_info] = value end # Returns the data from the controller that you want PaperTrail to store. # See also `PaperTrail::Rails::Controller#info_for_paper_trail`. # # PaperTrail.request.controller_info = { ip: request_user_ip } # PaperTrail.request.controller_info # => { ip: '127.0.0.1' } # # @api public def controller_info store[:controller_info] end # Switches PaperTrail off for the given model. # @api public def disable_model(model_class) enabled_for_model(model_class, false) end # Switches PaperTrail on for the given model. # @api public def enable_model(model_class) enabled_for_model(model_class, true) end # Sets whether PaperTrail is enabled or disabled for the current request. # @api public def enabled_for_controller=(value) store[:request_enabled_for_controller] = value end # Returns `true` if PaperTrail is enabled for the request, `false` otherwise. # # See `PaperTrail::Rails::Controller#paper_trail_enabled_for_controller`. # @api public def enabled_for_controller? !!store[:request_enabled_for_controller] end # Sets whether PaperTrail is enabled or disabled for this model in the # current request. # @api public def enabled_for_model(model, value) store[:"enabled_for_#{model}"] = value end # Returns `true` if PaperTrail is enabled for this model in the current # request, `false` otherwise. # @api public def enabled_for_model?(model) model.include?(::PaperTrail::Model::InstanceMethods) && !!store.fetch(:"enabled_for_#{model}", true) end # @api private def merge(options) options.to_h.each do |k, v| store[k] = v end end # @api private def set(options) store.clear merge(options) end # Returns a deep copy of the internal hash from our RequestStore. Keys are # all symbols. Values are mostly primitives, but whodunnit can be a Proc. # We cannot use Marshal.dump here because it doesn't support Proc. It is # unclear exactly how `deep_dup` handles a Proc, but it doesn't complain. # @api private def to_h store.deep_dup end # @api private def transaction_id store[:transaction_id] end # @api private def transaction_id=(id) store[:transaction_id] = id end # Temporarily set `options` and execute a block. # @api private def with(options) return unless block_given? validate_public_options(options) before = to_h merge(options) yield ensure set(before) end # Sets who is responsible for any changes that occur during request. You # would normally use this in a migration or on the console, when working # with models directly. # # `value` is usually a string, the name of a person, but you can set # anything that responds to `to_s`. You can also set a Proc, which will # not be evaluated until `whodunnit` is called later, usually right before # inserting a `Version` record. # # @api public def whodunnit=(value) store[:whodunnit] = value end # Returns who is reponsible for any changes that occur during request. # # @api public def whodunnit who = store[:whodunnit] who.respond_to?(:call) ? who.call : who end private # Returns a Hash, initializing with default values if necessary. # @api private def store RequestStore.store[:paper_trail] ||= { request_enabled_for_controller: true } end # Provide a helpful error message if someone has a typo in one of their # option keys. We don't validate option values here. That's traditionally # been handled with casting (`to_s`, `!!`) in the accessor method. # @api private def validate_public_options(options) options.each do |k, _v| case k when :controller_info, /enabled_for_/, :request_enabled_for_controller, :whodunnit next when :transaction_id raise InvalidOption, "Cannot set private option: #{k}" else raise InvalidOption, "Invalid option: #{k}" end end end end end end