Load Parameters configurations on :action_controller only once #30045
Conversation
Thanks for the pull request, and welcome! The Rails team is excited to review your changes, and you should hear from @pixeltrix (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. This repository is being automatically checked for code quality issues using Code Climate. You can see results for this analysis in the PR status below. Newly introduced issues should be fixed before a Pull Request is considered ready to review. Please see the contribution instructions for more information. |
@@ -24,7 +24,7 @@ class Railtie < Rails::Railtie #:nodoc: | |||
initializer "action_controller.parameters_config" do |app| | |||
options = app.config.action_controller | |||
|
|||
ActiveSupport.on_load(:action_controller) do | |||
ActiveSupport.on_load(:action_controller_base) do |
betesh
Aug 2, 2017
Contributor
Does ActionController::API not use strong parameters?
Does ActionController::API not use strong parameters?
rafaelfranca
Aug 2, 2017
Member
So the problem is because we run this block twice and after the first one we delete the values, so the second one we get the default values (which is false).
Yeah, we still need to make sure it will run for ActionController::API
.
I think we need an new concept that are lazy load hooks that are only executed once. Something like:
ActiveSupport.on_load(:action_controller_base, only_once: true) do
Could you implement that?
So the problem is because we run this block twice and after the first one we delete the values, so the second one we get the default values (which is false).
Yeah, we still need to make sure it will run for ActionController::API
.
I think we need an new concept that are lazy load hooks that are only executed once. Something like:
ActiveSupport.on_load(:action_controller_base, only_once: true) do
Could you implement that?
albertoalmagro
Aug 2, 2017
Author
Contributor
Yes you are right, it doesn't cover ActionController::API
.
@rafaelfranca where do you see that it is executed twice? Is the same hook registered twice?
Yes you are right, it doesn't cover ActionController::API
.
@rafaelfranca where do you see that it is executed twice? Is the same hook registered twice?
rafaelfranca
Aug 2, 2017
Member
No, it is executed twice. One for ActionController::Base
and other for ActionController::API
. This is why changing to on_load(:action_controller_base)
fix the problem, because it only run for ActionController::Base
No, it is executed twice. One for ActionController::Base
and other for ActionController::API
. This is why changing to on_load(:action_controller_base)
fix the problem, because it only run for ActionController::Base
albertoalmagro
Aug 2, 2017
•
Author
Contributor
Now I see @rafaelfranca, the first time the hook is executed this line deletes the value for :action_on_unpermitted_parameters
and the next time it gets executed options[:action_on_unpermitted_parameters]
is nil
and the value gets overwritten.
I will correct the PR accordingly.
Now I see @rafaelfranca, the first time the hook is executed this line deletes the value for :action_on_unpermitted_parameters
and the next time it gets executed options[:action_on_unpermitted_parameters]
is nil
and the value gets overwritten.
I will correct the PR accordingly.
kirs
Aug 9, 2017
Member
What if we stop deleting / mutating the values in the callback? Then we wouldn't need to introduce the complexity of only_once
.
What if we stop deleting / mutating the values in the callback? Then we wouldn't need to introduce the complexity of only_once
.
@rafaelfranca @betesh changes done and PR description updated. |
This is great! I made a commend about the implementation that I think can be simpler. |
@@ -27,19 +27,34 @@ def self.extended(base) # :nodoc: | |||
base.class_eval do | |||
@load_hooks = Hash.new { |h, k| h[k] = [] } | |||
@loaded = Hash.new { |h, k| h[k] = [] } | |||
@only_once = Hash.new { |h, k| h[k] = [] } |
rafaelfranca
Aug 9, 2017
•
Member
I wonder if we can delete the hook from @load_hooks
just after it is executed if :only_once
is true instead of adding a new hash.
I wonder if we can delete the hook from @load_hooks
just after it is executed if :only_once
is true instead of adding a new hash.
albertoalmagro
Aug 9, 2017
Author
Contributor
Hey! Sure I will try tomorrow, now it is 1:10 am here. Thanks!!!
Hey! Sure I will try tomorrow, now it is 1:10 am here. Thanks!!!
albertoalmagro
Aug 10, 2017
Author
Contributor
Hi @rafaelfranca , I have been trying this and it gets tricky. The main issue is that on_load
and run_load_hooks
can be called in any given order and both execute hooks. If you run_load_hooks
for two objects like I do in this test, when on_load
gets executed, you don't check @load_hooks
so you will need some extra structure to know if the hook was ever executed. Having tried this I find the @only_once
hash more intention revealing and I like how it separates the :only_once
logic without interfering with the already existing functionality. What do you think?
Hi @rafaelfranca , I have been trying this and it gets tricky. The main issue is that on_load
and run_load_hooks
can be called in any given order and both execute hooks. If you run_load_hooks
for two objects like I do in this test, when on_load
gets executed, you don't check @load_hooks
so you will need some extra structure to know if the hook was ever executed. Having tried this I find the @only_once
hash more intention revealing and I like how it separates the :only_once
logic without interfering with the already existing functionality. What do you think?
rafaelfranca
Aug 11, 2017
Member
That makes sense to me.
That makes sense to me.
end | ||
|
||
@load_hooks[name] << [block, options] | ||
end | ||
|
||
# Executes a hook. If the hook was loaded providing :only_once option, | ||
# it is flagged after the first time it is executed and then skipped. | ||
def process_hook(name, base, options, block) |
rafaelfranca
Aug 11, 2017
Member
This method can be private
This method can be private
execute_hook(base, options, block) | ||
flag_run(name, options, block) | ||
end | ||
|
||
def execute_hook(base, options, block) |
rafaelfranca
Aug 11, 2017
Member
This method can be private
This method can be private
@@ -48,10 +63,20 @@ def execute_hook(base, options, block) | |||
end | |||
end | |||
|
|||
def already_run?(name, hook) |
rafaelfranca
Aug 11, 2017
Member
This method can be private
This method can be private
@only_once[name].include?(hook) | ||
end | ||
|
||
def flag_run(name, options, hook) |
rafaelfranca
Aug 11, 2017
Member
This method can be private
This method can be private
@rafaelfranca done, I made methods private and rebased. Thanks! |
if options[:only_once] | ||
@only_once[name] << hook | ||
end | ||
end |
kaspth
Aug 14, 2017
Member
This seems a little too abstracted for my taste. I think it would be clearer if we kept the once
checking responsibility within the same method:
def execute_hook(name, base, options, block)
with_once_execution(name, block, options[:only_once]) do
if options[:yield]
block.call(base)
else
base.instance_eval(&block)
end
end
end
def with_once_execution(name, block, once)
unless @only_once[name].include?(block)
@only_once[name] << block if once
yield
end
end
(I'd probably just call the option once: true
since the only_
is already implied.)
This seems a little too abstracted for my taste. I think it would be clearer if we kept the once
checking responsibility within the same method:
def execute_hook(name, base, options, block)
with_once_execution(name, block, options[:only_once]) do
if options[:yield]
block.call(base)
else
base.instance_eval(&block)
end
end
end
def with_once_execution(name, block, once)
unless @only_once[name].include?(block)
@only_once[name] << block if once
yield
end
end
(I'd probably just call the option once: true
since the only_
is already implied.)
Provide run_once: true option to on_load in case you want a hook only to be executed once. This may be useful in cases where executing a hook several times may have undesired side effects
Fixes regression ActionController::UnpermittedParameters not raised. The inner hook was being executed twice, once when ActionController::Base was loaded and again when ActionController::API was loaded. As options.delete operations inside the block are not idempotent, the second time it was run there was no configuration option available
@kaspth I really like your proposal, I have applied it but changing the method's name to I have also renamed the option to @rafaelfranca please also review last changes. |
Just to confirm, does the application that you are reproducing this bug has |
…arameters-regression Load Parameters configurations on :action_controller only once
Yes @rafaelfranca , I can confirm it. I have debugged it and I see how the hook is registered for both |
…arameters-regression Load Parameters configurations on :action_controller only once
end | ||
|
||
def execute_hook(base, options, block) | ||
with_execution_control(name, block, options[:run_once]) do |
kaspth
Aug 16, 2017
Member
Where is name
coming from here? Previously execute_hook
was changed to pass it in.
Where is name
coming from here? Previously execute_hook
was changed to pass it in.
albertoalmagro
Aug 16, 2017
Author
Contributor
Good catch @kaspth I wonder why tests didn't blow up. Since this is merged, please tell me how should I fix it?
Good catch @kaspth I wonder why tests didn't blow up. Since this is merged, please tell me how should I fix it?
albertoalmagro
Aug 16, 2017
Author
Contributor
@kaspth I already have a branch to pass the name. Should I open a new PR and reference this one?
@kaspth I already have a branch to pass the name. Should I open a new PR and reference this one?
rafaelfranca
Aug 16, 2017
Member
Yes please
Yes please
…ler::Base and ActionController::API. Rails [introduced](rails/rails#30045) `has_one` option to limit executions to one This will fix merit-gem#173 for rails 5.1.4 and above
Rails execute `action_controller` onload hooks for Both ActionController::Base and ActionController::API. Rails [introduced](rails/rails#30045) `has_one` option to limit executions to one This will fix merit-gem#173 for rails 5.1.4 and above
Summary
This PR fixes the regression where ActionController::UnpermittedParameters exceptions are not being raised.
To accomplish that the following has been made:
ActiveSupport::LazyLoadHooks
to be executed only once.Provide
run_once: true
option toon_load
in case you want a hook only to be executed once. This may be useful in cases where executing a hook several times may have undesired side effects. Example:ActiveSupport.on_load(:action_controller, run_once: true)
:action_controller
only once.By using the new
ActiveSupport::LazyLoadHooks
functionality described above fixes regressionActionController::UnpermittedParameters
not raised. The inner hook was being executed twice, once whenActionController::Base
was loaded and again whenActionController::API
was loaded. Asoptions.delete
operations inside the block are not idempotent, the second time it was run there was no configuration option available.Other Information
More information can be found in Issue 30025.