A new API for request variables
This commit is contained in:
parent
0f3d8c8bcc
commit
c659b1faf0
|
@ -22,8 +22,17 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
|||
- Removed `warn_about_not_setting_whodunnit` controller method. Please remove
|
||||
callbacks like `skip_after_action :warn_about_not_setting_whodunnit`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- [#1033](https://github.com/airblade/paper_trail/pull/1033) - Request variables
|
||||
are now set using eg. `PaperTrail.request.whodunnit=` and the old way,
|
||||
`PaperTrail.whodunnit=` is deprecated.
|
||||
|
||||
### Added
|
||||
|
||||
- [#1033](https://github.com/airblade/paper_trail/pull/1033) -
|
||||
Set request variables temporarily using a block, eg.
|
||||
`PaperTrail.request(whodunnit: 'Jared') do .. end`
|
||||
- [#1037](https://github.com/airblade/paper_trail/pull/1037) Add `paper_trail.update_columns`
|
||||
- [#961](https://github.com/airblade/paper_trail/issues/961) - Instead of
|
||||
crashing when misconfigured Custom Version Classes are used, an error will be
|
||||
|
|
118
README.md
118
README.md
|
@ -126,13 +126,10 @@ Once you have a version, you can find out what happened:
|
|||
|
||||
```ruby
|
||||
v = widget.versions.last
|
||||
v.event # 'update', 'create', or 'destroy'
|
||||
v.created_at # When the `event` occurred
|
||||
v.whodunnit # If the update was via a controller and the
|
||||
# controller has a current_user method, returns the
|
||||
# id of the current user as a string.
|
||||
widget = v.reify # The widget as it was before the update
|
||||
# (nil for a create event)
|
||||
v.event # 'update', 'create', 'destroy'. See also: Custom Event Names
|
||||
v.created_at
|
||||
v.whodunnit # ID of `current_user`. Requires `set_paper_trail_whodunnit` callback.
|
||||
widget = v.reify # The widget as it was before the update (nil for a create event)
|
||||
```
|
||||
|
||||
PaperTrail stores the pre-change version of the model, unlike some other
|
||||
|
@ -204,17 +201,12 @@ widget.paper_trail.next_version
|
|||
# version)
|
||||
widget.paper_trail.touch_with_version
|
||||
|
||||
# Turn PaperTrail off for all widgets.
|
||||
Widget.paper_trail.disable
|
||||
|
||||
# Turn PaperTrail on for all widgets.
|
||||
Widget.paper_trail.enable
|
||||
# Enable/disable PaperTrail, for Widget, for the current request (not all threads)
|
||||
PaperTrail.request.disable_model(Widget)
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
|
||||
# Is PaperTrail enabled for Widget, the class?
|
||||
Widget.paper_trail.enabled?
|
||||
|
||||
# Is PaperTrail enabled for widget, the instance?
|
||||
widget.paper_trail.enabled_for_model?
|
||||
PaperTrail.request.enabled_for_model?(Widget)
|
||||
```
|
||||
|
||||
And a `PaperTrail::Version` instance (which is just an ordinary ActiveRecord
|
||||
|
@ -298,11 +290,12 @@ end
|
|||
other callbacks in your model, their order relative to those installed by
|
||||
PaperTrail may matter, so be aware of any potential interactions.
|
||||
|
||||
#### Custom Event Name
|
||||
|
||||
You may also have the `PaperTrail::Version` model save a custom string in its
|
||||
`event` field instead of the typical `create`, `update`, `destroy`. PaperTrail
|
||||
supplies a custom accessor method called `paper_trail_event`, which it will
|
||||
attempt to use to fill the `event` field before falling back on one of the
|
||||
default events.
|
||||
adds an `attr_accessor` to your model named `paper_trail_event`, and will insert
|
||||
it, if present, in the `event` column.
|
||||
|
||||
```ruby
|
||||
a = Article.create
|
||||
|
@ -480,7 +473,7 @@ Add a `paper_trail_enabled_for_controller` method to your controller.
|
|||
```ruby
|
||||
class ApplicationController < ActionController::Base
|
||||
def paper_trail_enabled_for_controller
|
||||
request.user_agent != 'Disable User-Agent'
|
||||
super && request.user_agent != 'Disable User-Agent'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
@ -488,10 +481,13 @@ end
|
|||
#### Per Class
|
||||
|
||||
```ruby
|
||||
Widget.paper_trail.disable
|
||||
Widget.paper_trail.enable
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
PaperTrail.request.disable_model(Widget)
|
||||
```
|
||||
|
||||
This setting, as with all `PaperTrail.request` settings, affects only the
|
||||
current request, not all threads.
|
||||
|
||||
#### Per Method
|
||||
|
||||
You can call a method without creating a new version using `without_versioning`.
|
||||
|
@ -509,7 +505,7 @@ Or a block:
|
|||
end
|
||||
```
|
||||
|
||||
PaperTrail is disabled for the whole model
|
||||
During `without_versioning`, PaperTrail is disabled for the whole model
|
||||
(e.g. `Widget`), not just for the instance (e.g. `@widget`).
|
||||
|
||||
### 2.e. Limiting the Number of Versions Created
|
||||
|
@ -703,34 +699,44 @@ PaperTrail::Version.delete_all ['created_at < ?', 1.week.ago]
|
|||
|
||||
### 4.a. Finding Out Who Was Responsible For A Change
|
||||
|
||||
Set `PaperTrail.whodunnit=`, and that value will be stored in the version's
|
||||
`whodunnit` column.
|
||||
Set `PaperTrail.request.whodunnit=`, and that value will be stored in the
|
||||
version's `whodunnit` column.
|
||||
|
||||
```ruby
|
||||
PaperTrail.whodunnit = 'Andy Stewart'
|
||||
PaperTrail.request.whodunnit = 'Andy Stewart'
|
||||
widget.update_attributes name: 'Wibble'
|
||||
widget.versions.last.whodunnit # Andy Stewart
|
||||
widget.versions.last.whodunnit # Andy Stewart
|
||||
```
|
||||
|
||||
`whodunnit` also accepts a block, a convenient way to temporarily set the value.
|
||||
#### Setting `whodunnit` to a `Proc`
|
||||
|
||||
`whodunnit=` also accepts a `Proc`, in the rare case that lazy evaluation is
|
||||
required.
|
||||
|
||||
```ruby
|
||||
PaperTrail.whodunnit('Dorian Marié') do
|
||||
widget.update_attributes name: 'Wibble'
|
||||
end
|
||||
```
|
||||
|
||||
`whodunnit` also accepts a `Proc`.
|
||||
|
||||
```ruby
|
||||
PaperTrail.whodunnit = proc do
|
||||
PaperTrail.request.whodunnit = proc do
|
||||
caller.first{ |c| c.starts_with? Rails.root.to_s }
|
||||
end
|
||||
```
|
||||
|
||||
Because lazy evaluation can be hard to troubleshoot, this is not
|
||||
recommended for common use.
|
||||
|
||||
#### Setting `whodunnit` Temporarily
|
||||
|
||||
To set whodunnit temporarily, for the duration of a block, use
|
||||
`PaperTrail.request`:
|
||||
|
||||
```ruby
|
||||
PaperTrail.request(whodunnit: 'Dorian Marié') do
|
||||
widget.update_attributes name: 'Wibble'
|
||||
end
|
||||
```
|
||||
|
||||
#### Setting `whodunnit` with a controller callback
|
||||
|
||||
If your controller has a `current_user` method, PaperTrail provides a
|
||||
`before_action` that will assign `current_user.id` to `PaperTrail.whodunnit`.
|
||||
You can add this `before_action` to your `ApplicationController`.
|
||||
callback that will assign `current_user.id` to `whodunnit`.
|
||||
|
||||
```ruby
|
||||
class ApplicationController
|
||||
|
@ -752,25 +758,13 @@ end
|
|||
|
||||
See also: [Setting whodunnit in the rails console][33]
|
||||
|
||||
Sometimes you want to define who is responsible for a change in a small scope
|
||||
without overwriting value of `PaperTrail.whodunnit`. It is possible to define
|
||||
the `whodunnit` value for an operation inside a block like this:
|
||||
#### Terminator and Originator
|
||||
|
||||
```ruby
|
||||
PaperTrail.whodunnit = 'Andy Stewart'
|
||||
widget.paper_trail.whodunnit('Lucas Souza') do
|
||||
widget.update_attributes name: 'Wibble'
|
||||
end
|
||||
widget.versions.last.whodunnit # Lucas Souza
|
||||
widget.update_attributes name: 'Clair'
|
||||
widget.versions.last.whodunnit # Andy Stewart
|
||||
```
|
||||
|
||||
A version's `whodunnit` records who changed the object causing the `version` to
|
||||
be stored. Because a version stores the object as it looked before the change
|
||||
(see the table above), `whodunnit` returns who stopped the object looking like
|
||||
this -- not who made it look like this. Hence `whodunnit` is aliased as
|
||||
`terminator`.
|
||||
A version's `whodunnit` column tells us who changed the object, causing the
|
||||
`version` to be stored. Because a version stores the object as it looked before
|
||||
the change (see the table above), `whodunnit` tells us who *stopped* the object
|
||||
looking like this -- not who made it look like this. Hence `whodunnit` is
|
||||
aliased as `terminator`.
|
||||
|
||||
To find out who made a version's object look that way, use
|
||||
`version.paper_trail_originator`. And to find out who made a "live" object look
|
||||
|
@ -778,10 +772,10 @@ like it does, call `paper_trail_originator` on the object.
|
|||
|
||||
```ruby
|
||||
widget = Widget.find 153 # assume widget has 0 versions
|
||||
PaperTrail.whodunnit = 'Alice'
|
||||
PaperTrail.request.whodunnit = 'Alice'
|
||||
widget.update_attributes name: 'Yankee'
|
||||
widget.paper_trail.originator # 'Alice'
|
||||
PaperTrail.whodunnit = 'Bob'
|
||||
PaperTrail.request.whodunnit = 'Bob'
|
||||
widget.update_attributes name: 'Zulu'
|
||||
widget.paper_trail.originator # 'Bob'
|
||||
first_version, last_version = widget.versions.first, widget.versions.last
|
||||
|
@ -1117,7 +1111,7 @@ module PaperTrail
|
|||
end
|
||||
```
|
||||
|
||||
This unsupported workaround has been tested with protected_attributes 1.0.9 /
|
||||
This *unsupported workaround* has been tested with protected_attributes 1.0.9 /
|
||||
rails 4.2.8 / paper_trail 7.0.3.
|
||||
|
||||
## 6. Extensibility
|
||||
|
@ -1392,7 +1386,7 @@ describe 'RSpec test group' do
|
|||
end
|
||||
```
|
||||
|
||||
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
||||
The helper will also reset `whodunnit` to `nil` before each
|
||||
test to help prevent data spillover between tests. If you are using PaperTrail
|
||||
with Rails, the helper will automatically set the `PaperTrail.controller_info`
|
||||
value to `{}` as well, again, to help prevent data spillover between tests.
|
||||
|
@ -1489,7 +1483,7 @@ Given /I want versioning on my model/ do
|
|||
end
|
||||
```
|
||||
|
||||
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
||||
The helper will also reset the `whodunnit` value to `nil` before each
|
||||
test to help prevent data spillover between tests. If you are using PaperTrail
|
||||
with Rails, the helper will automatically set the `PaperTrail.controller_info`
|
||||
value to `{}` as well, again, to help prevent data spillover between tests.
|
||||
|
|
|
@ -20,6 +20,7 @@ require "paper_trail/config"
|
|||
require "paper_trail/has_paper_trail"
|
||||
require "paper_trail/record_history"
|
||||
require "paper_trail/reifier"
|
||||
require "paper_trail/request"
|
||||
require "paper_trail/version_association_concern"
|
||||
require "paper_trail/version_concern"
|
||||
require "paper_trail/version_number"
|
||||
|
@ -46,124 +47,158 @@ module PaperTrail
|
|||
class << self
|
||||
# @api private
|
||||
def clear_transaction_id
|
||||
self.transaction_id = nil
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.clear_transaction_id is deprecated, " \
|
||||
"use PaperTrail.request.clear_transaction_id",
|
||||
caller(1)
|
||||
)
|
||||
request.clear_transaction_id
|
||||
end
|
||||
|
||||
# Switches PaperTrail on or off.
|
||||
# Switches PaperTrail on or off, for all threads.
|
||||
# @api public
|
||||
def enabled=(value)
|
||||
PaperTrail.config.enabled = value
|
||||
end
|
||||
|
||||
# Returns `true` if PaperTrail is on, `false` otherwise.
|
||||
# PaperTrail is enabled by default.
|
||||
# Returns `true` if PaperTrail is on, `false` otherwise. This is the
|
||||
# on/off switch that affects all threads. Enabled by default.
|
||||
# @api public
|
||||
def enabled?
|
||||
!!PaperTrail.config.enabled
|
||||
end
|
||||
|
||||
# Sets whether PaperTrail is enabled or disabled for the current request.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def enabled_for_controller=(value)
|
||||
paper_trail_store[:request_enabled_for_controller] = value
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.enabled_for_controller= is deprecated, " \
|
||||
"use PaperTrail.request.enabled_for_controller=",
|
||||
caller(1)
|
||||
)
|
||||
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
|
||||
# @deprecated
|
||||
def enabled_for_controller?
|
||||
!!paper_trail_store[:request_enabled_for_controller]
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.enabled_for_controller? is deprecated, " \
|
||||
"use PaperTrail.request.enabled_for_controller?",
|
||||
caller(1)
|
||||
)
|
||||
request.enabled_for_controller?
|
||||
end
|
||||
|
||||
# Sets whether PaperTrail is enabled or disabled for this model in the
|
||||
# current request.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def enabled_for_model(model, value)
|
||||
paper_trail_store[:"enabled_for_#{model}"] = value
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.enabled_for_model is deprecated, " \
|
||||
"use PaperTrail.request.enabled_for_model",
|
||||
caller(1)
|
||||
)
|
||||
request.enabled_for_model(model, value)
|
||||
end
|
||||
|
||||
# Returns `true` if PaperTrail is enabled for this model in the current
|
||||
# request, `false` otherwise.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def enabled_for_model?(model)
|
||||
!!paper_trail_store.fetch(:"enabled_for_#{model}", true)
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.enabled_for_model? is deprecated, " \
|
||||
"use PaperTrail.request.enabled_for_model?",
|
||||
caller(1)
|
||||
)
|
||||
request.enabled_for_model?(model)
|
||||
end
|
||||
|
||||
# Returns a `::Gem::Version`, convenient for comparisons. This is
|
||||
# Returns PaperTrail's `::Gem::Version`, convenient for comparisons. This is
|
||||
# recommended over `::PaperTrail::VERSION::STRING`.
|
||||
# @api public
|
||||
def gem_version
|
||||
::Gem::Version.new(VERSION::STRING)
|
||||
end
|
||||
|
||||
# Set variables for the current request, eg. whodunnit.
|
||||
#
|
||||
# All request-level variables are now managed here, as of PT 9. Having the
|
||||
# word "request" right there in your application code will remind you that
|
||||
# these variables only affect the current request, not all threads.
|
||||
#
|
||||
# Given a block, temporarily sets the given `options` and execute the block.
|
||||
#
|
||||
# Without a block, this currently just returns `PaperTrail::Request`.
|
||||
# However, please do not use `PaperTrail::Request` directly. 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 public
|
||||
def request(options = nil, &block)
|
||||
if options.nil? && !block_given?
|
||||
Request
|
||||
else
|
||||
Request.with(options, &block)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Set the field which records when a version was created.
|
||||
# @api public
|
||||
def timestamp_field=(_field_name)
|
||||
raise(E_TIMESTAMP_FIELD_CONFIG)
|
||||
end
|
||||
|
||||
# Sets who is responsible for any changes that occur. You would normally use
|
||||
# this in a migration or on the console, when working with models directly.
|
||||
# In a controller it is set automatically to the `current_user`.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def whodunnit=(value)
|
||||
paper_trail_store[:whodunnit] = value
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.whodunnit= is deprecated, use PaperTrail.request.whodunnit=",
|
||||
caller(1)
|
||||
)
|
||||
request.whodunnit = value
|
||||
end
|
||||
|
||||
# If nothing passed, returns who is reponsible for any changes that occur.
|
||||
#
|
||||
# PaperTrail.whodunnit = "someone"
|
||||
# PaperTrail.whodunnit # => "someone"
|
||||
#
|
||||
# If value and block passed, set this value as whodunnit for the duration of the block
|
||||
#
|
||||
# PaperTrail.whodunnit("me") do
|
||||
# puts PaperTrail.whodunnit # => "me"
|
||||
# end
|
||||
#
|
||||
# @api public
|
||||
def whodunnit(value = nil)
|
||||
if value
|
||||
raise ArgumentError, "no block given" unless block_given?
|
||||
|
||||
previous_whodunnit = paper_trail_store[:whodunnit]
|
||||
paper_trail_store[:whodunnit] = value
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
paper_trail_store[:whodunnit] = previous_whodunnit
|
||||
end
|
||||
elsif paper_trail_store[:whodunnit].respond_to?(:call)
|
||||
paper_trail_store[:whodunnit].call
|
||||
# @deprecated
|
||||
def whodunnit(value = nil, &block)
|
||||
if value.nil?
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.whodunnit is deprecated, use PaperTrail.request.whodunnit",
|
||||
caller(1)
|
||||
)
|
||||
request.whodunnit
|
||||
elsif block_given?
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"Passing a block to PaperTrail.whodunnit is deprecated, " \
|
||||
'use PaperTrail.request(whodunnit: "John") do .. end',
|
||||
caller(1)
|
||||
)
|
||||
request(whodunnit: value, &block)
|
||||
else
|
||||
paper_trail_store[:whodunnit]
|
||||
raise ArgumentError, "Invalid arguments"
|
||||
end
|
||||
end
|
||||
|
||||
# Sets any information from the controller that you want PaperTrail to
|
||||
# store. By default this is set automatically by a before filter.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def controller_info=(value)
|
||||
paper_trail_store[:controller_info] = value
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.controller_info= is deprecated, use PaperTrail.request.controller_info=",
|
||||
caller(1)
|
||||
)
|
||||
request.controller_info = value
|
||||
end
|
||||
|
||||
# Returns any information from the controller that you want
|
||||
# PaperTrail to store.
|
||||
#
|
||||
# See `PaperTrail::Rails::Controller#info_for_paper_trail`.
|
||||
# @api public
|
||||
# @deprecated
|
||||
def controller_info
|
||||
paper_trail_store[:controller_info]
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.controller_info is deprecated, use PaperTrail.request.controller_info",
|
||||
caller(1)
|
||||
)
|
||||
request.controller_info
|
||||
end
|
||||
|
||||
# Getter and Setter for PaperTrail Serializer
|
||||
# Set the PaperTrail serializer. This setting affects all threads.
|
||||
# @api public
|
||||
def serializer=(value)
|
||||
PaperTrail.config.serializer = value
|
||||
end
|
||||
|
||||
# Get the PaperTrail serializer used by all threads.
|
||||
# @api public
|
||||
def serializer
|
||||
PaperTrail.config.serializer
|
||||
|
@ -174,24 +209,26 @@ module PaperTrail
|
|||
::ActiveRecord::Base.connection.open_transactions.positive?
|
||||
end
|
||||
|
||||
# @api public
|
||||
# @deprecated
|
||||
def transaction_id
|
||||
paper_trail_store[:transaction_id]
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.transaction_id is deprecated without replacement.",
|
||||
caller(1)
|
||||
)
|
||||
request.transaction_id
|
||||
end
|
||||
|
||||
# @api public
|
||||
# @deprecated
|
||||
def transaction_id=(id)
|
||||
paper_trail_store[:transaction_id] = id
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"PaperTrail.transaction_id= is deprecated without replacement.",
|
||||
caller(1)
|
||||
)
|
||||
request.transaction_id = id
|
||||
end
|
||||
|
||||
# Thread-safe hash to hold PaperTrail's data. Initializing with needed
|
||||
# default values.
|
||||
# @api private
|
||||
def paper_trail_store
|
||||
RequestStore.store[:paper_trail] ||= { request_enabled_for_controller: true }
|
||||
end
|
||||
|
||||
# Returns PaperTrail's configuration object.
|
||||
# Returns PaperTrail's global configuration object, a singleton. These
|
||||
# settings affect all threads.
|
||||
# @api private
|
||||
def config
|
||||
@config ||= PaperTrail::Config.instance
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
# before hook for Cucumber
|
||||
Before do
|
||||
PaperTrail.enabled = false
|
||||
PaperTrail.enabled_for_controller = true
|
||||
PaperTrail.whodunnit = nil
|
||||
PaperTrail.controller_info = {} if defined? Rails
|
||||
PaperTrail.request.enabled_for_controller = true
|
||||
PaperTrail.request.whodunnit = nil
|
||||
PaperTrail.request.controller_info = {} if defined?(::Rails)
|
||||
end
|
||||
|
||||
module PaperTrail
|
||||
|
|
|
@ -63,21 +63,21 @@ module PaperTrail
|
|||
# Tells PaperTrail whether versions should be saved in the current
|
||||
# request.
|
||||
def set_paper_trail_enabled_for_controller
|
||||
::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller
|
||||
::PaperTrail.request.enabled_for_controller = paper_trail_enabled_for_controller
|
||||
end
|
||||
|
||||
# Tells PaperTrail who is responsible for any changes that occur.
|
||||
def set_paper_trail_whodunnit
|
||||
if ::PaperTrail.enabled_for_controller?
|
||||
::PaperTrail.whodunnit = user_for_paper_trail
|
||||
if ::PaperTrail.request.enabled_for_controller?
|
||||
::PaperTrail.request.whodunnit = user_for_paper_trail
|
||||
end
|
||||
end
|
||||
|
||||
# Tells PaperTrail any information from the controller you want to store
|
||||
# alongside any changes that occur.
|
||||
def set_paper_trail_controller_info
|
||||
if ::PaperTrail.enabled_for_controller?
|
||||
::PaperTrail.controller_info = info_for_paper_trail
|
||||
if ::PaperTrail.request.enabled_for_controller?
|
||||
::PaperTrail.request.controller_info = info_for_paper_trail
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,9 +10,9 @@ RSpec.configure do |config|
|
|||
|
||||
config.before(:each) do
|
||||
::PaperTrail.enabled = false
|
||||
::PaperTrail.enabled_for_controller = true
|
||||
::PaperTrail.whodunnit = nil
|
||||
::PaperTrail.controller_info = {} if defined?(::Rails) && defined?(::RSpec::Rails)
|
||||
::PaperTrail.request.enabled_for_controller = true
|
||||
::PaperTrail.request.whodunnit = nil
|
||||
::PaperTrail.request.controller_info = {} if defined?(::Rails) && defined?(::RSpec::Rails)
|
||||
end
|
||||
|
||||
config.before(:each, versioning: true) do
|
||||
|
|
|
@ -4,12 +4,32 @@ module PaperTrail
|
|||
# Configures an ActiveRecord model, mostly at application boot time, but also
|
||||
# sometimes mid-request, with methods like enable/disable.
|
||||
class ModelConfig
|
||||
DPR_DISABLE = <<-STR.squish.freeze
|
||||
MyModel.paper_trail.disable is deprecated, use
|
||||
PaperTrail.request.disable_model(MyModel). This new API makes it clear
|
||||
that only the current request is affected, not all threads. Also, all
|
||||
other request-variables now go through the same `request` method, so this
|
||||
new API is more consistent.
|
||||
STR
|
||||
DPR_ENABLE = <<-STR.squish.freeze
|
||||
MyModel.paper_trail.enable is deprecated, use
|
||||
PaperTrail.request.enable_model(MyModel). This new API makes it clear
|
||||
that only the current request is affected, not all threads. Also, all
|
||||
other request-variables now go through the same `request` method, so this
|
||||
new API is more consistent.
|
||||
STR
|
||||
DPR_ENABLED = <<-STR.squish.freeze
|
||||
MyModel.paper_trail.enabled? is deprecated, use
|
||||
PaperTrail.request.enabled_for_model?(MyModel). This new API makes it clear
|
||||
that this is a setting specific to the current request, not all threads.
|
||||
Also, all other request-variables now go through the same `request`
|
||||
method, so this new API is more consistent.
|
||||
STR
|
||||
E_CANNOT_RECORD_AFTER_DESTROY = <<-STR.strip_heredoc.freeze
|
||||
paper_trail.on_destroy(:after) is incompatible with ActiveRecord's
|
||||
belongs_to_required_by_default. Use on_destroy(:before)
|
||||
or disable belongs_to_required_by_default.
|
||||
STR
|
||||
|
||||
E_HPT_ABSTRACT_CLASS = <<~STR.squish.freeze
|
||||
An application model (%s) has been configured to use PaperTrail (via
|
||||
`has_paper_trail`), but the version model it has been told to use (%s) is
|
||||
|
@ -24,19 +44,22 @@ module PaperTrail
|
|||
@model_class = model_class
|
||||
end
|
||||
|
||||
# Switches PaperTrail off for this class.
|
||||
# @deprecated
|
||||
def disable
|
||||
::PaperTrail.enabled_for_model(@model_class, false)
|
||||
::ActiveSupport::Deprecation.warn(DPR_DISABLE, caller(1))
|
||||
::PaperTrail.request.disable_model(@model_class)
|
||||
end
|
||||
|
||||
# Switches PaperTrail on for this class.
|
||||
# @deprecated
|
||||
def enable
|
||||
::PaperTrail.enabled_for_model(@model_class, true)
|
||||
::ActiveSupport::Deprecation.warn(DPR_ENABLE, caller(1))
|
||||
::PaperTrail.request.enable_model(@model_class)
|
||||
end
|
||||
|
||||
# @deprecated
|
||||
def enabled?
|
||||
return false unless @model_class.include?(::PaperTrail::Model::InstanceMethods)
|
||||
::PaperTrail.enabled_for_model?(@model_class)
|
||||
::ActiveSupport::Deprecation.warn(DPR_ENABLED, caller(1))
|
||||
::PaperTrail.request.enabled_for_model?(@model_class)
|
||||
end
|
||||
|
||||
# Adds a callback that records a version after a "create" event.
|
||||
|
@ -195,8 +218,8 @@ module PaperTrail
|
|||
|
||||
# Reset the transaction id when the transaction is closed.
|
||||
def setup_transaction_callbacks
|
||||
@model_class.after_commit { PaperTrail.clear_transaction_id }
|
||||
@model_class.after_rollback { PaperTrail.clear_transaction_id }
|
||||
@model_class.after_commit { PaperTrail.request.clear_transaction_id }
|
||||
@model_class.after_rollback { PaperTrail.request.clear_transaction_id }
|
||||
@model_class.after_rollback { paper_trail.clear_rolled_back_versions }
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
module PaperTrail
|
||||
# Represents the "paper trail" for a single record.
|
||||
class RecordTrail
|
||||
DPR_WHODUNNIT = <<-STR.squish.freeze
|
||||
my_model_instance.paper_trail.whodunnit('John') is deprecated,
|
||||
please use PaperTrail.request(whodunnit: 'John')
|
||||
STR
|
||||
RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
|
||||
|
||||
def initialize(record)
|
||||
|
@ -98,12 +102,24 @@ module PaperTrail
|
|||
notable_changes.to_hash
|
||||
end
|
||||
|
||||
# Is PT enabled for this particular record?
|
||||
# @api private
|
||||
def enabled?
|
||||
PaperTrail.enabled? && PaperTrail.enabled_for_controller? && enabled_for_model?
|
||||
PaperTrail.enabled? &&
|
||||
PaperTrail.request.enabled_for_controller? &&
|
||||
PaperTrail.request.enabled_for_model?(@record.class)
|
||||
end
|
||||
|
||||
# Not sure why, but this method was mentioned in the README in the past,
|
||||
# so we need to deprecate it properly.
|
||||
# @deprecated
|
||||
def enabled_for_model?
|
||||
@record.class.paper_trail.enabled?
|
||||
::ActiveSupport::Deprecation.warn(
|
||||
"MyModel#paper_trail.enabled_for_model? is deprecated, use " \
|
||||
"PaperTrail.request.enabled_for_model?(MyModel) instead.",
|
||||
caller(1)
|
||||
)
|
||||
PaperTrail.request.enabled_for_model?(@record.class)
|
||||
end
|
||||
|
||||
# An attributed is "ignored" if it is listed in the `:ignore` option
|
||||
|
@ -130,7 +146,7 @@ module PaperTrail
|
|||
# Updates `data` from `controller_info`.
|
||||
# @api private
|
||||
def merge_metadata_from_controller_into(data)
|
||||
data.merge(PaperTrail.controller_info || {})
|
||||
data.merge(PaperTrail.request.controller_info || {})
|
||||
end
|
||||
|
||||
# Updates `data` from the model's `meta` option.
|
||||
|
@ -220,7 +236,7 @@ module PaperTrail
|
|||
def data_for_create
|
||||
data = {
|
||||
event: @record.paper_trail_event || "create",
|
||||
whodunnit: PaperTrail.whodunnit
|
||||
whodunnit: PaperTrail.request.whodunnit
|
||||
}
|
||||
if @record.respond_to?(:updated_at)
|
||||
data[:created_at] = @record.updated_at
|
||||
|
@ -254,7 +270,7 @@ module PaperTrail
|
|||
item_type: @record.class.base_class.name,
|
||||
event: @record.paper_trail_event || "destroy",
|
||||
object: recordable_object,
|
||||
whodunnit: PaperTrail.whodunnit
|
||||
whodunnit: PaperTrail.request.whodunnit
|
||||
}
|
||||
add_transaction_id_to(data)
|
||||
merge_metadata_into(data)
|
||||
|
@ -290,7 +306,7 @@ module PaperTrail
|
|||
data = {
|
||||
event: @record.paper_trail_event || "update",
|
||||
object: recordable_object,
|
||||
whodunnit: PaperTrail.whodunnit
|
||||
whodunnit: PaperTrail.request.whodunnit
|
||||
}
|
||||
if @record.respond_to?(:updated_at)
|
||||
data[:created_at] = @record.updated_at
|
||||
|
@ -321,7 +337,7 @@ module PaperTrail
|
|||
data = {
|
||||
event: @record.paper_trail_event || "update",
|
||||
object: recordable_object,
|
||||
whodunnit: PaperTrail.whodunnit
|
||||
whodunnit: PaperTrail.request.whodunnit
|
||||
}
|
||||
if record_object_changes?
|
||||
data[:object_changes] = recordable_object_changes(changes)
|
||||
|
@ -473,8 +489,8 @@ module PaperTrail
|
|||
|
||||
# Executes the given method or block without creating a new version.
|
||||
def without_versioning(method = nil)
|
||||
paper_trail_was_enabled = enabled_for_model?
|
||||
@record.class.paper_trail.disable
|
||||
paper_trail_was_enabled = PaperTrail.request.enabled_for_model?(@record.class)
|
||||
PaperTrail.request.disable_model(@record.class)
|
||||
if method
|
||||
if respond_to?(method)
|
||||
public_send(method)
|
||||
|
@ -485,25 +501,23 @@ module PaperTrail
|
|||
yield @record
|
||||
end
|
||||
ensure
|
||||
@record.class.paper_trail.enable if paper_trail_was_enabled
|
||||
PaperTrail.request.enable_model(@record.class) if paper_trail_was_enabled
|
||||
end
|
||||
|
||||
# Temporarily overwrites the value of whodunnit and then executes the
|
||||
# provided block.
|
||||
# @deprecated
|
||||
def whodunnit(value)
|
||||
raise ArgumentError, "expected to receive a block" unless block_given?
|
||||
current_whodunnit = PaperTrail.whodunnit
|
||||
PaperTrail.whodunnit = value
|
||||
yield @record
|
||||
ensure
|
||||
PaperTrail.whodunnit = current_whodunnit
|
||||
::ActiveSupport::Deprecation.warn(DPR_WHODUNNIT, caller(1))
|
||||
::PaperTrail.request(whodunnit: value) do
|
||||
yield @record
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_transaction_id_to(data)
|
||||
return unless @record.class.paper_trail.version_class.column_names.include?("transaction_id")
|
||||
data[:transaction_id] = PaperTrail.transaction_id
|
||||
data[:transaction_id] = PaperTrail.request.transaction_id
|
||||
end
|
||||
|
||||
# @api private
|
||||
|
@ -568,10 +582,10 @@ module PaperTrail
|
|||
|
||||
if assoc.options[:polymorphic]
|
||||
associated_record = @record.send(assoc.name) if @record.send(assoc.foreign_type)
|
||||
if associated_record && associated_record.class.paper_trail.enabled?
|
||||
if associated_record && PaperTrail.request.enabled_for_model?(associated_record.class)
|
||||
assoc_version_args[:foreign_key_id] = associated_record.id
|
||||
end
|
||||
elsif assoc.klass.paper_trail.enabled?
|
||||
elsif PaperTrail.request.enabled_for_model?(assoc.klass)
|
||||
assoc_version_args[:foreign_key_id] = @record.send(assoc.foreign_key)
|
||||
end
|
||||
|
||||
|
@ -584,7 +598,7 @@ module PaperTrail
|
|||
# @api private
|
||||
def save_habtm_association?(assoc)
|
||||
@record.class.paper_trail_save_join_tables.include?(assoc.name) ||
|
||||
assoc.klass.paper_trail.enabled?
|
||||
PaperTrail.request.enabled_for_model?(assoc.klass)
|
||||
end
|
||||
|
||||
# Returns true if `save` will cause `record_update`
|
||||
|
@ -596,8 +610,8 @@ module PaperTrail
|
|||
|
||||
def update_transaction_id(version)
|
||||
return unless @record.class.paper_trail.version_class.column_names.include?("transaction_id")
|
||||
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
||||
PaperTrail.transaction_id = version.id
|
||||
if PaperTrail.transaction? && PaperTrail.request.transaction_id.nil?
|
||||
PaperTrail.request.transaction_id = version.id
|
||||
version.transaction_id = version.id
|
||||
version.save
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ module PaperTrail
|
|||
# @api private
|
||||
def each_enabled_association(associations)
|
||||
associations.each do |assoc|
|
||||
next unless assoc.klass.paper_trail.enabled?
|
||||
next unless ::PaperTrail.request.enabled_for_model?(assoc.klass)
|
||||
yield assoc
|
||||
end
|
||||
end
|
||||
|
@ -194,7 +194,7 @@ module PaperTrail
|
|||
# @api private
|
||||
def reify_habtm_associations(transaction_id, model, options = {})
|
||||
model.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
|
||||
pt_enabled = assoc.klass.paper_trail.enabled?
|
||||
pt_enabled = ::PaperTrail.request.enabled_for_model?(assoc.klass)
|
||||
next unless model.class.paper_trail_save_join_tables.include?(assoc.name) || pt_enabled
|
||||
Reifiers::HasAndBelongsToMany.reify(pt_enabled, assoc, model, options, transaction_id)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
# 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
|
|
@ -9,7 +9,7 @@ module PaperTrail
|
|||
module VERSION
|
||||
MAJOR = 8
|
||||
MINOR = 1
|
||||
TINY = 1
|
||||
TINY = 2
|
||||
PRE = nil
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require "spec_helper"
|
||||
|
||||
RSpec.describe ArticlesController, type: :controller do
|
||||
describe "PaperTrail.enabled_for_controller?" do
|
||||
describe "PaperTrail.request.enabled_for_controller?" do
|
||||
context "PaperTrail.enabled? == true" do
|
||||
before { PaperTrail.enabled = true }
|
||||
|
||||
|
@ -11,7 +11,7 @@ RSpec.describe ArticlesController, type: :controller do
|
|||
assert PaperTrail.enabled?
|
||||
post :create, params_wrapper(article: { title: "Doh", content: FFaker::Lorem.sentence })
|
||||
expect(assigns(:article)).not_to be_nil
|
||||
assert PaperTrail.enabled_for_controller?
|
||||
assert PaperTrail.request.enabled_for_controller?
|
||||
assert_equal 1, assigns(:article).versions.length
|
||||
end
|
||||
|
||||
|
@ -22,7 +22,7 @@ RSpec.describe ArticlesController, type: :controller do
|
|||
it "returns false" do
|
||||
assert !PaperTrail.enabled?
|
||||
post :create, params_wrapper(article: { title: "Doh", content: FFaker::Lorem.sentence })
|
||||
assert !PaperTrail.enabled_for_controller?
|
||||
assert !PaperTrail.request.enabled_for_controller?
|
||||
assert_equal 0, assigns(:article).versions.length
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,11 +20,11 @@ RSpec.describe WidgetsController, type: :controller, versioning: true do
|
|||
it "controller metadata methods should get evaluated" do
|
||||
request.env["HTTP_USER_AGENT"] = "User-Agent"
|
||||
post :create, params_wrapper(widget: { name: "Flugel" })
|
||||
expect(PaperTrail.enabled_for_controller?).to(eq(true))
|
||||
expect(PaperTrail.whodunnit).to(eq(153))
|
||||
expect(PaperTrail.controller_info.present?).to(eq(true))
|
||||
expect(PaperTrail.controller_info.keys.include?(:ip)).to(eq(true))
|
||||
expect(PaperTrail.controller_info.keys.include?(:user_agent)).to(eq(true))
|
||||
expect(PaperTrail.request.enabled_for_controller?).to(eq(true))
|
||||
expect(PaperTrail.request.whodunnit).to(eq(153))
|
||||
expect(PaperTrail.request.controller_info.present?).to(eq(true))
|
||||
expect(PaperTrail.request.controller_info.keys.include?(:ip)).to(eq(true))
|
||||
expect(PaperTrail.request.controller_info.keys.include?(:user_agent)).to(eq(true))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -33,9 +33,9 @@ RSpec.describe WidgetsController, type: :controller, versioning: true do
|
|||
request.env["HTTP_USER_AGENT"] = "Disable User-Agent"
|
||||
post :create, params_wrapper(widget: { name: "Flugel" })
|
||||
expect(assigns(:widget).versions.length).to(eq(0))
|
||||
expect(PaperTrail).not_to be_enabled_for_controller
|
||||
expect(PaperTrail.whodunnit).to be_nil
|
||||
expect(PaperTrail.controller_info).to eq({})
|
||||
expect(PaperTrail.request).not_to be_enabled_for_controller
|
||||
expect(PaperTrail.request.whodunnit).to be_nil
|
||||
expect(PaperTrail.request.controller_info).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Elephant < Animal
|
||||
paper_trail.disable
|
||||
end
|
||||
|
||||
# Nice! We used to have `paper_trail.disable` inside the class, which was really
|
||||
# misleading because it looked like a permanent, global setting. It's so much
|
||||
# more obvious now that we are disabling the model for this request only. Of
|
||||
# course, we run the PT unit tests in a single thread, and I think this setting
|
||||
# will affect multiple unit tests, but in a normal application, this new API is
|
||||
# a huge improvement.
|
||||
#
|
||||
# TODO: If this call to `disable_model` were moved to the unit tests, this file
|
||||
# would be more like normal application code. It'd be pretty strange for someone
|
||||
# to do this in app code, especially now that it is obvious that it only affects
|
||||
# the current request.
|
||||
PaperTrail.request.disable_model(Elephant)
|
||||
|
|
|
@ -178,21 +178,21 @@ RSpec.describe Widget, type: :model do
|
|||
let(:new_name) { FFaker::Name.name }
|
||||
|
||||
before do
|
||||
PaperTrail.whodunnit = orig_name
|
||||
PaperTrail.request.whodunnit = orig_name
|
||||
end
|
||||
|
||||
it "returns the originator for the model at a given state" do
|
||||
expect(widget.paper_trail).to be_live
|
||||
expect(widget.paper_trail.originator).to eq(orig_name)
|
||||
widget.paper_trail.whodunnit(new_name) { |w|
|
||||
w.update_attributes(name: "Elizabeth")
|
||||
::PaperTrail.request(whodunnit: new_name) {
|
||||
widget.update_attributes(name: "Elizabeth")
|
||||
}
|
||||
expect(widget.paper_trail.originator).to eq(new_name)
|
||||
end
|
||||
|
||||
it "returns the appropriate originator" do
|
||||
widget.update_attributes(name: "Andy")
|
||||
PaperTrail.whodunnit = new_name
|
||||
PaperTrail.request.whodunnit = new_name
|
||||
widget.update_attributes(name: "Elizabeth")
|
||||
reified_widget = widget.versions[1].reify
|
||||
expect(reified_widget.paper_trail.originator).to eq(orig_name)
|
||||
|
@ -201,7 +201,7 @@ RSpec.describe Widget, type: :model do
|
|||
|
||||
it "can create a new instance with options[:dup]" do
|
||||
widget.update_attributes(name: "Andy")
|
||||
PaperTrail.whodunnit = new_name
|
||||
PaperTrail.request.whodunnit = new_name
|
||||
widget.update_attributes(name: "Elizabeth")
|
||||
reified_widget = widget.versions[1].reify(dup: true)
|
||||
expect(reified_widget.paper_trail.originator).to eq(orig_name)
|
||||
|
@ -221,47 +221,12 @@ RSpec.describe Widget, type: :model do
|
|||
end
|
||||
|
||||
describe "#whodunnit", versioning: true do
|
||||
context "no block given" do
|
||||
it "raises an error" do
|
||||
expect {
|
||||
widget.paper_trail.whodunnit("Ben")
|
||||
}.to raise_error(ArgumentError, "expected to receive a block")
|
||||
end
|
||||
end
|
||||
|
||||
context "block given" do
|
||||
let(:orig_name) { FFaker::Name.name }
|
||||
let(:new_name) { FFaker::Name.name }
|
||||
|
||||
before do
|
||||
PaperTrail.whodunnit = orig_name
|
||||
widget # persist `widget` (call the `let`)
|
||||
end
|
||||
|
||||
it "modifies value of `PaperTrail.whodunnit` while executing the block" do
|
||||
expect(widget.versions.last.whodunnit).to eq(orig_name)
|
||||
widget.paper_trail.whodunnit(new_name) do
|
||||
expect(PaperTrail.whodunnit).to eq(new_name)
|
||||
widget.update_attributes(name: "Elizabeth")
|
||||
end
|
||||
expect(widget.versions.last.whodunnit).to eq(new_name)
|
||||
end
|
||||
|
||||
it "reverts value of whodunnit to previous value after executing the block" do
|
||||
expect(widget.versions.last.whodunnit).to eq(orig_name)
|
||||
widget.paper_trail.whodunnit(new_name) { |w|
|
||||
w.update_attributes(name: "Elizabeth")
|
||||
}
|
||||
expect(PaperTrail.whodunnit).to eq(orig_name)
|
||||
end
|
||||
|
||||
it "reverts to previous value, even if error within block" do
|
||||
expect(widget.versions.last.whodunnit).to eq(orig_name)
|
||||
expect {
|
||||
widget.paper_trail.whodunnit(new_name) { raise }
|
||||
}.to raise_error(RuntimeError)
|
||||
expect(PaperTrail.whodunnit).to eq(orig_name)
|
||||
end
|
||||
it "is deprecated, delegates to Request.whodunnit" do
|
||||
allow(::ActiveSupport::Deprecation).to receive(:warn)
|
||||
allow(::PaperTrail::Request).to receive(:with)
|
||||
widget.paper_trail.whodunnit("Alex") {}
|
||||
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
|
||||
expect(::PaperTrail::Request).to have_received(:with).with(whodunnit: "Alex")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -307,35 +272,4 @@ RSpec.describe Widget, type: :model do
|
|||
assert_equal 2, widget.versions.length
|
||||
end
|
||||
end
|
||||
|
||||
describe ".paper_trail.enabled?" do
|
||||
it "returns true" do
|
||||
expect(Widget.paper_trail.enabled?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".disable" do
|
||||
it "sets the `paper_trail.enabled?` to `false`" do
|
||||
expect(Widget.paper_trail.enabled?).to eq(true)
|
||||
Widget.paper_trail.disable
|
||||
expect(Widget.paper_trail.enabled?).to eq(false)
|
||||
end
|
||||
|
||||
after do
|
||||
Widget.paper_trail.enable
|
||||
end
|
||||
end
|
||||
|
||||
describe ".enable" do
|
||||
it "sets the `paper_trail.enabled?` to `true`" do
|
||||
Widget.paper_trail.disable
|
||||
expect(Widget.paper_trail.enabled?).to eq(false)
|
||||
Widget.paper_trail.enable
|
||||
expect(Widget.paper_trail.enabled?).to eq(true)
|
||||
end
|
||||
|
||||
after do
|
||||
Widget.paper_trail.enable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -324,11 +324,13 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
|
||||
context "with its paper trail turned off" do
|
||||
before do
|
||||
Widget.paper_trail.disable
|
||||
PaperTrail.request.disable_model(Widget)
|
||||
@count = @widget.versions.length
|
||||
end
|
||||
|
||||
after { Widget.paper_trail.enable }
|
||||
after do
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
end
|
||||
|
||||
context "when updated" do
|
||||
before { @widget.update_attributes(name: "Beeblebrox") }
|
||||
|
@ -341,12 +343,14 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
context "when destroyed \"without versioning\"" do
|
||||
it "leave paper trail off after call" do
|
||||
@widget.paper_trail.without_versioning(:destroy)
|
||||
expect(Widget.paper_trail.enabled?).to(eq(false))
|
||||
expect(::PaperTrail.request.enabled_for_model?(Widget)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "and then its paper trail turned on" do
|
||||
before { Widget.paper_trail.enable }
|
||||
before do
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
end
|
||||
|
||||
context "when updated" do
|
||||
before { @widget.update_attributes(name: "Ford") }
|
||||
|
@ -371,19 +375,15 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
end
|
||||
|
||||
it "enable paper trail after call" do
|
||||
expect(Widget.paper_trail.enabled?).to(eq(true))
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when receiving a method name as an argument" do
|
||||
before { @widget.paper_trail.without_versioning(:touch_with_version) }
|
||||
|
||||
it "not create new version" do
|
||||
context "given a symbol, specifying a method name" do
|
||||
it "does not create a new version" do
|
||||
@widget.paper_trail.without_versioning(:touch_with_version)
|
||||
expect(@widget.versions.length).to(eq(@count))
|
||||
end
|
||||
|
||||
it "enable paper trail after call" do
|
||||
expect(Widget.paper_trail.enabled?).to(eq(true))
|
||||
expect(::PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -395,7 +395,7 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
|
||||
context "when a record is created" do
|
||||
before do
|
||||
PaperTrail.whodunnit = "Alice"
|
||||
PaperTrail.request.whodunnit = "Alice"
|
||||
@widget.save
|
||||
@version = @widget.versions.last
|
||||
end
|
||||
|
@ -409,7 +409,7 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
|
||||
context "when a record is updated" do
|
||||
before do
|
||||
PaperTrail.whodunnit = "Bob"
|
||||
PaperTrail.request.whodunnit = "Bob"
|
||||
@widget.update_attributes(name: "Rivet")
|
||||
@version = @widget.versions.last
|
||||
end
|
||||
|
@ -423,7 +423,7 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
|
||||
context "when a record is destroyed" do
|
||||
before do
|
||||
PaperTrail.whodunnit = "Charlie"
|
||||
PaperTrail.request.whodunnit = "Charlie"
|
||||
@widget.destroy
|
||||
@version = PaperTrail::Version.last
|
||||
end
|
||||
|
@ -467,9 +467,9 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
|||
end
|
||||
|
||||
it "returns the correct originator" do
|
||||
PaperTrail.whodunnit = "Ben"
|
||||
PaperTrail.request.whodunnit = "Ben"
|
||||
@foo.update_attribute(:name, "Geoffrey")
|
||||
expect(@foo.paper_trail.originator).to(eq(PaperTrail.whodunnit))
|
||||
expect(@foo.paper_trail.originator).to(eq(PaperTrail.request.whodunnit))
|
||||
end
|
||||
|
||||
context "when destroyed" do
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
module PaperTrail
|
||||
::RSpec.describe(Request, versioning: true) do
|
||||
describe ".enabled_for_model?" do
|
||||
it "returns true" do
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".disable_model" do
|
||||
it "sets enabled_for_model? to false" do
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
|
||||
PaperTrail.request.disable_model(Widget)
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(false)
|
||||
end
|
||||
|
||||
after do
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".enable_model" do
|
||||
it "sets enabled_for_model? to true" do
|
||||
PaperTrail.request.disable_model(Widget)
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(false)
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
expect(PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
|
||||
end
|
||||
|
||||
after do
|
||||
PaperTrail.request.enable_model(Widget)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".whodunnit" do
|
||||
context "when set to a proc" do
|
||||
it "evaluates the proc each time a version is made" do
|
||||
call_count = 0
|
||||
described_class.whodunnit = proc { call_count += 1 }
|
||||
expect(described_class.whodunnit).to eq(1)
|
||||
expect(described_class.whodunnit).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".with" do
|
||||
context "block given" do
|
||||
it "sets whodunnit only for the block passed" do
|
||||
described_class.with(whodunnit: "foo") do
|
||||
expect(described_class.whodunnit).to eq("foo")
|
||||
end
|
||||
expect(described_class.whodunnit).to be_nil
|
||||
end
|
||||
|
||||
it "sets whodunnit only for the current thread" do
|
||||
described_class.with(whodunnit: "foo") do
|
||||
expect(described_class.whodunnit).to eq("foo")
|
||||
Thread.new { expect(described_class.whodunnit).to be_nil }.join
|
||||
end
|
||||
expect(described_class.whodunnit).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,12 +10,12 @@ RSpec.describe PaperTrail do
|
|||
controller = TestController.new
|
||||
controller.send(:set_paper_trail_whodunnit)
|
||||
sleep(0.001) while blocked
|
||||
described_class.whodunnit
|
||||
described_class.request.whodunnit
|
||||
end
|
||||
fast_thread = Thread.new do
|
||||
controller = TestController.new
|
||||
controller.send(:set_paper_trail_whodunnit)
|
||||
who = described_class.whodunnit
|
||||
who = described_class.request.whodunnit
|
||||
blocked = false
|
||||
who
|
||||
end
|
||||
|
@ -26,21 +26,23 @@ RSpec.describe PaperTrail do
|
|||
describe "#without_versioning" do
|
||||
it "is thread-safe" do
|
||||
enabled = nil
|
||||
slow_thread = Thread.new do
|
||||
t1 = Thread.new do
|
||||
Widget.new.paper_trail.without_versioning do
|
||||
sleep(0.01)
|
||||
enabled = Widget.paper_trail.enabled?
|
||||
enabled = described_class.request.enabled_for_model?(Widget)
|
||||
sleep(0.01)
|
||||
end
|
||||
enabled
|
||||
end
|
||||
fast_thread = Thread.new do
|
||||
# A second thread is timed so that it runs during the first thread's
|
||||
# `without_versioning` block.
|
||||
t2 = Thread.new do
|
||||
sleep(0.005)
|
||||
Widget.paper_trail.enabled?
|
||||
described_class.request.enabled_for_model?(Widget)
|
||||
end
|
||||
expect(fast_thread.value).not_to(eq(slow_thread.value))
|
||||
expect(Widget.paper_trail.enabled?).to(eq(true))
|
||||
expect(described_class.enabled_for_model?(Widget)).to(eq(true))
|
||||
expect(t1.value).to eq(false)
|
||||
expect(t2.value).to eq(true) # see? unaffected by t1
|
||||
expect(described_class.request.enabled_for_model?(Widget)).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -94,37 +94,8 @@ RSpec.describe PaperTrail do
|
|||
end
|
||||
|
||||
describe ".version" do
|
||||
it { expect(described_class).to respond_to(:version) }
|
||||
it { expect(described_class.version).to eq(described_class::VERSION::STRING) }
|
||||
end
|
||||
|
||||
describe ".whodunnit" do
|
||||
context "with block passed" do
|
||||
it "sets whodunnit only for the block passed" do
|
||||
described_class.whodunnit("foo") do
|
||||
expect(described_class.whodunnit).to eq("foo")
|
||||
end
|
||||
|
||||
expect(described_class.whodunnit).to be_nil
|
||||
end
|
||||
|
||||
it "sets whodunnit only for the current thread" do
|
||||
described_class.whodunnit("foo") do
|
||||
expect(described_class.whodunnit).to eq("foo")
|
||||
Thread.new { expect(described_class.whodunnit).to be_nil }.join
|
||||
end
|
||||
|
||||
expect(described_class.whodunnit).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when set to a proc" do
|
||||
it "evaluates the proc each time a version is made" do
|
||||
call_count = 0
|
||||
described_class.whodunnit = proc { call_count += 1 }
|
||||
expect(described_class.whodunnit).to eq(1)
|
||||
expect(described_class.whodunnit).to eq(2)
|
||||
end
|
||||
it "returns the expected String" do
|
||||
expect(described_class.version).to eq(described_class::VERSION::STRING)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,14 +9,15 @@ RSpec.describe "Articles management", type: :request, order: :defined do
|
|||
specify { expect(PaperTrail).not_to be_enabled }
|
||||
|
||||
it "does not create a version" do
|
||||
expect(PaperTrail).to be_enabled_for_controller
|
||||
expect(PaperTrail.request).to be_enabled_for_controller
|
||||
expect {
|
||||
post articles_path, params_wrapper(valid_params)
|
||||
}.not_to change(PaperTrail::Version, :count)
|
||||
end
|
||||
|
||||
it "does not leak the state of the `PaperTrail.enabled_for_controller?` into the next test" do
|
||||
expect(PaperTrail).to be_enabled_for_controller
|
||||
it "does not leak the state of the `PaperTrail.request.enabled_for_controller?` \
|
||||
into the next test" do
|
||||
expect(PaperTrail.request).to be_enabled_for_controller
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue