-
Notifications
You must be signed in to change notification settings - Fork 903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussing the API for request variables, part 2 #1033
Conversation
@alexevanczuk and @batter please review, thanks! |
Thanks @jaredbeck! I should be able to look at this this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of questions and comments.
Also, could you add some more test coverage for the new code? Particularly around making sure that request works when calling with a block, and will properly reset values in different situations to prevent regressions. For example, a context where the original code raises an error, to test ensure
is properly called, or that different attribute values work correctly (what if controller info is a nested hash, or passes in objects of different types).
Thanks for doing this, overall looks great and will allow a lot more flexibility using paper trail without sacrificing simplicity or code readability.
lib/paper_trail/request.rb
Outdated
# 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. | ||
# @api private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a private method, but it is called in PaperTrail#request
. I am not sure how you think about private methods (is it just for gem clients, or for service classes within the gem)? In either case, I think that def request
when called with options
should pass the block to Request
and the request itself knows how to set config for the duration of the block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a private method, but ..
It's not private
, it's # @api private
:) The former being a language feature and the latter being a somewhat conventional way of commenting on which methods are subject to change without warning.
In either case, I think that def request when called with options should pass the block to Request and the request itself knows how to set config for the duration of the block.
Sounds good, I'll try that.
@@ -46,7 +48,11 @@ module PaperTrail | |||
class << self | |||
# @api private | |||
def clear_transaction_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this and other # @api private
methods, such as transaction_id=
, have a deprecation warning? Is your rationale that even if they're private, clients might be using them, and we want to deprecate them in the same way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going forward, all transaction id methods should be @api private
. I'll fix that in the depr. warnings and comments.
lib/paper_trail.rb
Outdated
"Passing a block to PaperTrail.whodunnit is deprecated, " \ | ||
'use PaperTrail.request(whodunnit: "John") do .. end' | ||
) | ||
before = request.whodunnit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use method as suggested in the deprecation warning?
e.g.
request(whodunnit: value)
yield
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion, thanks.
lib/paper_trail.rb
Outdated
# @api public | ||
def controller_info | ||
paper_trail_store[:controller_info] | ||
def controller_info(value = nil, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add these parameters? They aren't used in Request
. The Request
method signature is def controller_info
. Wouldn't you get a ArgumentError: wrong number of arguments
if you call this? If so, can you add test coverage for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a mistake. I didn't mean to change the method signature. Good catch.
lib/paper_trail.rb
Outdated
@@ -174,21 +204,20 @@ def transaction? | |||
::ActiveRecord::Base.connection.open_transactions.positive? | |||
end | |||
|
|||
# @api public | |||
# @api private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intentional? Again I'm not sure how you like to do this maintenance-wise, but I think that maybe we should add a deprecation notice that this is no longer a public method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, intentional. I'll be changing the deprecation warning to say "deprecated without replacement".
lib/paper_trail/request.rb
Outdated
# whodunnit can be a Proc. | ||
# @api private | ||
def to_h | ||
store.store.deep_dup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that store.store
is a little confusing. Maybe something should have a different name here.
lib/paper_trail.rb
Outdated
else | ||
begin | ||
before = Request.to_h | ||
Request.set(options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to limit what options are settable, and perhaps validate the options? If they set:
PaperTrail.request(some_nonexistant_option: true) do
...
end
What should happen?
What if they try to set a private attribute? Such as transaction_id
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to limit what options are settable, and perhaps validate the options? If they set .. [some_nonexistant_option] .. What should happen?
Good point, we should raise an error.
What if they try to set a private attribute? Such as transaction_id?
We can raise an error for that too.
lib/paper_trail/version_number.rb
Outdated
@@ -9,7 +9,7 @@ module PaperTrail | |||
module VERSION | |||
MAJOR = 8 | |||
MINOR = 1 | |||
TINY = 1 | |||
TINY = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be going in the other direction?
@alexevanczuk and @batter ready for another review, thanks! |
Looks great! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had some comments mostly around testing. I re-compared the delegated methods in lib/paper_trail.rb
, lib/paper_trail/model_config.rb
, and lib/paper_trail/record_trail.rb
to the new methods in lib/paper_trail/request.rb
, and things look good. However, I'd still feel more comfortable if we added delegation tests for each of the methods moved in the original file, as well as functionality tests for each of the methods moved to lib/paper_trail/request.rb
in the spec for the request module.
Not sure what your customs are around proving PRs, but we typically have one person sign off and another click approve, so I'll let @batter officially accept.
|
||
#### Setting `whodunnit` Temporarily | ||
|
||
To set whodunnit temporarily, for the duration of a block, use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe consider adding the other supported fields that can be set in the block in the readme?
lib/paper_trail/record_trail.rb
Outdated
@@ -485,25 +497,25 @@ def without_versioning(method = nil) | |||
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. | |||
def whodunnit(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this method here? Shouldn't it be deprecated in favor of using PaperTrail.request.with(whodunnit: value) do ... end
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Yes, I will deprecate it.
end | ||
|
||
describe ".with" do | ||
context "block given" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests for the validator here through the public with
test? That is, test that whodunnit, enabled for work (maybe enabled for is a good new test case since it's a regex), transaction id, and an invalid option.
Also can you change the initial value to be non nil so we can test something more useful, e.g. instead of expect(described_class.whodunnit).to be_nil
, we can have expect(described_class.whodunnit).to 'previous_whodunnit
@@ -94,37 +94,8 @@ | |||
end | |||
|
|||
describe ".version" do | |||
it { expect(described_class).to respond_to(:version) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably prefer to have some delegation/deprecation tests, e.g. to test deprecation issues such as changing the method signature to controller_info
.
This could be done tersely with a shared example if you want to write less code:
shared_example 'it delegates to request' do |method, *args, &block|
it do
expect(PaperTrail::Request).to receive(:method).with(args, &block).and_return(:some_return_value)
expect(described_class.send(:method, args, &block).to_return(:some_return_value)
end
end
it_behaves_like 'it delegates to request', :controller_info, [], nil
it_behaves_like 'it delegates to request', :whodunnit, [:some_whodunnit] { 'some_block' }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if the above will work exactly as is, but that's the idea. Could be just as straightforward to do it more explicitly for each method, but I think it would be less error-prone this way.
require "spec_helper" | ||
|
||
module PaperTrail | ||
::RSpec.describe(Request, versioning: true) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have the time, I'd probably prefer to have the other @api public
methods tested as well here, such as controller_info
, controller_info=
, enabled_for_controller?
, enabled_for_controller=
, and whodunnit=
.
end | ||
|
||
describe ".whodunnit" do | ||
context "when set to a proc" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a context when not set to a proc?
|
||
```ruby | ||
PaperTrail.whodunnit = proc do | ||
caller.first{ |c| c.starts_with? Rails.root.to_s } | ||
PaperTrail.request.with(whodunnit: 'Dorian Marié') do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the simplicity of this
@@ -46,124 +47,158 @@ module PaperTrail | |||
class << self | |||
# @api private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this comment actually do anything or it's merely a note to help developers remember which methods are public / private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It specifies whether something is part of our public API, ie. whether we would consider changing it to be a breaking change. Notably, not all public
methods are public API, if that makes sense.
PaperTrail.controller_info = {} if defined? Rails | ||
PaperTrail.request.enabled_for_controller = true | ||
PaperTrail.request.whodunnit = nil | ||
PaperTrail.request.controller_info = {} if defined? Rails |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be if defined?(::Rails)
to be consistent with the RSpec helper? I don't think there is a Rails module in this library yet but there could be one day
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'm always happy to use absolute constant paths, it's one less thing to worry about.
My first impression is that I like it and it seems like an eloquent solution that makes the gem nicely flexible to configure in a thread-safe manner or across all threads depending on what the user desires for various conditions. I'd like to spend some more time unpacking all the changes, will try to check back to do a deeper review later this week. |
Thanks everyone for the reviews! Alex, I'd really appreciate your help writing some more tests. Would you like to? You can make a PR into this one, eg. "Alex wants to merge .. commits into request_variables from alex_branch" Thanks! |
Added specs: #1052 |
Ben, I think I'm ready to merge this. Do you still want to do another review? Andy, can you un-protect this branch? I want to do a rebase and tidy up my commits, but force-pushing is disabled. I'd prefer if only master and the stable branches (eg. 8-stable, 7-stable) were protected. Thanks. |
Closing via #1056 which uses a non-protected branch, allowing me to rebase and squash prior to final merge to master. |
LGTM, I'm sure there will be time to tweak functionality if needed before a release. Nice work by @alexevanczuk and @jaredbeck! |
Thanks Ben! Yeah, I'm planning on releasing 9.0.0 in a few weeks, as soon as ruby 2.2 EoL is announced (https://www.ruby-lang.org/en/news/2017/09/14/ruby-2-2-8-released/) |
In #1016, Alex and I discussed a new API for request variables.
Current request variables
.. copied from #1016 for convenience:
API Goals
PaperTrail.request(whodunnit: 'Alex') do .. end
.PaperTrail.whodunnit
in favor ofPaperTrail.request.whodunnit
communicates this unambiguously.Let me know what you think, Alex. And @batter as always I'd appreciate your opinion too.