Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Rails/LexicallyScopedActionFilter cop #5177

Merged
merged 2 commits into from Dec 9, 2017

Conversation

wata727
Copy link
Contributor

@wata727 wata727 commented Dec 2, 2017

This cop checks that methods specified in the filter's only or except options are explicitly defined in the controller.

You can specify methods of superclass or methods added by mixins on the filter, but these confuse developers. If you specify methods where are defined on another controller, you should define the filter in that controller.

Also, as another advantage, you can notice the method name typo 馃槃

wdyt?


Before submitting the PR make sure the following are checked:

  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Used the same coding conventions as the rest of the project.
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Added an entry to the Changelog if the new code introduces user-observable changes. See changelog entry format.
  • All tests(rake spec) are passing.
  • The new code doesn't generate RuboCop offenses that are checked by rake internal_investigation.
  • The PR relates to only one subject with a clear title
    and description in grammatically correct, complete sentences.
  • Updated cop documentation with rake generate_cops_documentation (required only when you've added a new cop or changed the configuration/documentation of an existing cop).

def array_values(node)
case node.type
when :str
[node.to_a[0]]
Copy link
Collaborator

@pocke pocke Dec 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use node.str_content.to_sym for readability, and it needs .to_sym.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, probably we should add StrNode#value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. This should be done with another pull request.

@pocke
Copy link
Collaborator

pocke commented Dec 3, 2017

Could you add a test case to check array of strings? For example:

before_action :something, only: %w[foo bar]

Maybe the cop does not pass the test case now.

[node.value]
when :array
node.values.map do |v|
v.instance_of?(RuboCop::AST::SymbolNode) ? v.value : v
Copy link
Collaborator

@pocke pocke Dec 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be like the following.

node.values.map do |v|
  case v.type
  when :sym
    v.value
  when :str
    v.str_content.to_sym
  end
end.compact

I think the cop should ignore not-literal value.

@pocke
Copy link
Collaborator

pocke commented Dec 3, 2017

And add test cases to check false positive.

expect_no_offenses <<-RUBY
before_action :require_login, except: 'health_check'

def health_check
end
RUBY

end
end

add_offense(node) unless @inexplicit_method_names.empty?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can use message: keyword argument to avoid using the instance variable. Like add_offense(node, message: message(inexplicit_method_names))

@pocke
Copy link
Collaborator

pocke commented Dec 3, 2017

Consider default resource(s) actions, that are index, show, create, ....
They are defined always. So we can use these actions without method definition.
For example, the following works.

# controller
class FooController < ApplicationController
  before_action :require_login, only: :show

  # empty action definition
end
<!-- app/views/foo/show.html.erb -->
Hello, <%= current_user.name >!

So I think the cop should not add any offenses for these resource(s) actions.

@bbatsov
Copy link
Collaborator

bbatsov commented Dec 3, 2017

The name of the cop is pretty confusing, btw. It should be something like Inherited(or Included)ActionFilter or conversely LexicallyScopeActionFilter.

@wata727
Copy link
Contributor Author

wata727 commented Dec 3, 2017

Thanks for feedback :)

And add test cases to check false positive.

Umm... I think that this example is not false positive :(

Consider default resource(s) actions, that are index, show, create, ....
They are defined always. So we can use these actions without method definition.
For example, the following works.

There are examples other than default resources that work without having to define methods on the same controller. The following example works correctly for /foo/bar.

# config/routes.rb
Rails.application.routes.draw do
  get 'foo/bar'
end
# controller
class FooController < ApplicationController
  before_action :require_login, only: :bar

  # empty action definition
end
<!-- app/views/foo/bar.html.erb -->
Hello, <%= current_user.name >!

I think that it is undesirable to define filter dependent on such as routes or views.
Is it because the default resource action should be avoided because it is often used?

The name of the cop is pretty confusing, btw. It should be something like Inherited(or Included)ActionFilter or conversely LexicallyScopeActionFilter.

The name is a very difficult problem...
It needs to be concerned that this cop does not point to the method executed by the filter.
I think LexicallyScopeActionFilter is good, but there may be better names.

@pocke
Copy link
Collaborator

pocke commented Dec 3, 2017

And add test cases to check false positive.

Umm... I think that this example is not false positive :(

In the first version( efc1a5a ), I guess the cop added offense for the code, because the cop compared a string (in before_action) and symbol (in method definition). So I think we should have a test case for the case.

There are examples other than default resources that work without having to define methods on the same controller. The following example works correctly for /foo/bar.

Oh, I see. I think it's confusing behaviour...
So, I think the cop should not ignore the actions, and we should define actions explicitly.

@wata727 wata727 force-pushed the add_rails_inexplicit_filter branch 3 times, most recently from a044280 to 42793b8 Compare December 3, 2017 12:47
@wata727 wata727 changed the title Add new Rails/InexplicitFilter cop Add new Rails/LexicallyScopeActionFilter cop Dec 3, 2017
@wata727
Copy link
Contributor Author

wata727 commented Dec 3, 2017

I renamed the cop name to Rails/LexicallyScopeActionFilter.

@bbatsov
Copy link
Collaborator

bbatsov commented Dec 4, 2017

I made a typo - I meant to suggest LexicallyScopedActionFilter. Sorry about that!

methods_node = only_or_except_filter_methods(node)
return unless methods_node

defined_methods = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializing an empty array and then pushing into it inside an #each can be replaced with #map. 馃檪

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, I wanted to use the type filter with #each_child_node.
Perhaps #map_child_node may be necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I noticed that #each_child_node will returnEnumerator when not passing block. I will use this.

@wata727 wata727 force-pushed the add_rails_inexplicit_filter branch 2 times, most recently from 1eb02f1 to ca375b0 Compare December 4, 2017 15:55
@wata727 wata727 changed the title Add new Rails/LexicallyScopeActionFilter cop Add new Rails/LexicallyScopedActionFilter cop Dec 4, 2017
defined_methods = node.parent.each_child_node(:def).map(&:method_name)
methods = array_values(methods_node).map do |method|
method unless defined_methods.include?(method)
end.compact
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using reject instead of map will make compact unnecessary.

methods = array_values(methods_node).reject do |method|
  defined_methods.include?(method)
end

This cop checks that methods specified in the filter's `only`
or `except` options are explicitly defined in the controller.

You can specify methods of superclass or methods added by mixins
on the filter, but these confuse developers. If you specify methods
where are defined on another controller, you should define the filter
in that controller.
@bbatsov bbatsov merged commit 98f020e into rubocop:master Dec 9, 2017
@bbatsov
Copy link
Collaborator

bbatsov commented Dec 9, 2017

@wata727 Maybe you should also add this as a suggestion to the Rails style guide.

@bquorning
Copy link
Contributor

Thank you for this very useful cop @wata727 鉂わ笍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants