-
Notifications
You must be signed in to change notification settings - Fork 982
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
Fixes #16646 - Add ability to plugins to modify index scope #3887
Conversation
@@ -0,0 +1,37 @@ | |||
# This module will hold a regstry of scope directives that should be |
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.
nitpick: regstry´should be
registry´
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.
Done
def action_scope_for(action, base_scope) | ||
scope = base_scope | ||
self.class.scopes_for(action).each do |scope_func| | ||
scope = scope_func.call(scope) || scope |
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.
instead of looping them all, you can return the first match? or do you rather want to match the last one? the code is a bit hard to read.
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 am chaining the scopes, here is an example:
In puppet plugin:
HostsController.add_scope_for(:index) { |base_scope| base_scope.eager_load(:environment) }
In provisioning plugin:
HostsController.add_scope_for(:index) { |base_scope| base_scope.eager_load(:operatingsystem) }
I want to create a scope that will contain both operations:
base_scope = Hosts.authorized
actual_scope = base_scope.eager_load(environment).eager_load(operatingsystem)
I achieve this by calling each block, and passing it the result of the previous block.
@@ -351,6 +351,12 @@ def parameter_filters(klass) | |||
@parameter_filters.fetch(klass.is_a?(Class) ? klass.name : klass, []) | |||
end | |||
|
|||
def hosts_controller_action_scope(action, &block) | |||
# Add scope for both API and UI controller. | |||
HostsController.add_scope_for(action, 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.
does it make sense to always assume you want to extend them both?
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 sounds reasonable to me that the info that is returned is similar between the API and UI. In the original bug in Katello I saw that the problem exists in both UI and API.
I won't fight over it though. If you think it's unlikely, I can separate it to different methods.
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.
Looks good to me all in all. I like the DSL and this feature is long past due. The Plugin method needs test coverage (and fixing), though.
@@ -351,6 +351,12 @@ def parameter_filters(klass) | |||
@parameter_filters.fetch(klass.is_a?(Class) ? klass.name : klass, []) | |||
end | |||
|
|||
def hosts_controller_action_scope(action, &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.
Could you please add a test for this? Extra points for inline usage example.
@@ -351,6 +351,12 @@ def parameter_filters(klass) | |||
@parameter_filters.fetch(klass.is_a?(Class) ? klass.name : klass, []) | |||
end | |||
|
|||
def hosts_controller_action_scope(action, &block) | |||
# Add scope for both API and UI controller. | |||
HostsController.add_scope_for(action, 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.
I think, you need to pass the block like so (note the extra ampersand):
HostsController.add_scope_for(action, &block)
Same below.
Rebased, fixed plugin registration and added tests. @timogoebel did I get the extra points? |
05c53be
to
67f70da
Compare
Rebased again |
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.
The code looks good, thanks for the tests. It is also clear how to use this.
My only concern is, that this won't work in develop mode when classes can be reloaded. My suggestion is to store the scope codeblocks in the plugin model and when the concern is included have it get the scopes from the plugin model. You can check c676093 for an example.
In the past we had |
@ShimShtein : That would be possible, however it's not the Straight-A student's solution. The code in "to_prepare" runs on every development request. I think, it's better to implement a solution that just runs when a class is actually reloaded. |
Now the list is global - no reloads should affect it. |
FYI there are conflicting files. |
Rebased |
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.
@ShimShtein : Thanks for the latest updates. Class reloading still beaks the scopes from Plugins.
Open hosts index page -> everyting is fine.
To reproduce:
touch app/controllers/concerns/scopes_per_action.rb
Open host index page again -> it detects a n+1 query.
@@ -383,6 +383,19 @@ def test_add_smart_proxy_for | |||
assert_equal({:foo => {:feature => 'Foo'}}, Foreman::Plugin.find(:test_smart_proxy).smart_proxies(Awesome)) | |||
end | |||
|
|||
def test_hosts_controller_action_scope | |||
mock_scope = ->(scope) { scope } | |||
Foreman::Plugin.register :test_parameter_filter 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.
should be test_controller_action_scope
Indeed, reloading of this class will break the scopes. But that class'es sole responsibility is to maintain the registry, so if it changes - the old registry is probably not valid. |
@ShimShtein : True. But if you store the data in
When these classes get reloaded, the code is parsed as well and the information is restored. When you access the data, you can get it from both classes (and merge it) and you shouldn't have a problem. What do you think? |
@domcleal: May @ShimShtein and I ask for your opinion regarding where to store the data so that it survives a class reload in development? Is the current implementation fine, where the data from plugins gets lost when the |
It really should be stored in Foreman::Plugin (which is immune from reloads) when the API is called. Either access it from there when called, or something akin to c676093 where it's retrieved when the concern is included. I'd also suggest not adding an API specifically for hosts, else it'd spawn a new API for every single model/controller collection, it ought to be more generic.
Not really, IMHO. This is normally treated as a bug, and it ought to be fixed. |
@domcleal First, thanks for the review. My only question is what to do with scopes that are not part of a particular plugin. For example |
@domcleal, Sorry, ignore my previous comment, I did not read the code thoroughly enough. I have change the implementation to store scopes in both the plugin and in the controller (locally). The local storage is there for cases like the |
test failure is not related. |
Merged, thanks @ShimShtein. Please add a usage example to http://projects.theforeman.org/projects/foreman/wiki/How_to_Create_a_Plugin. |
Added the ability for plugins to modify the scope used to get hosts for
#index
action. This is needed if a plugin wants to add moreincludes
oreager_load
statements, so Active Record won't try to lazy load additional relations and create N+1 queries.