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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance/HashEachMethods matches non-hashes #4732

Closed
akerl opened this issue Sep 15, 2017 · 12 comments
Closed

Performance/HashEachMethods matches non-hashes #4732

akerl opened this issue Sep 15, 2017 · 12 comments
Milestone

Comments

@akerl
Copy link

akerl commented Sep 15, 2017

The change in 0.50.0 for Performance/HashEachMethods causes it to match on non-hashes where the block takes 2 args.

Expected behavior

On the following, no cop failure should be reported:

❯ cat test.rb
foo = [
  [1, 2],
  [3, 4]
]

foo.each { |a, _| puts a }

Actual behavior

A failure is reported:

❯ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:6:5: C: Use each_key instead of each.
foo.each { |a, _| puts a }
    ^^^^

1 file inspected, 1 offense detected

Steps to reproduce the problem

The above code sample should be able to reproduce this

RuboCop version

❯ rubocop -V
0.50.0 (using Parser 2.4.0.0, running on ruby 2.4.1 x86_64-darwin16)
@bbatsov
Copy link
Collaborator

bbatsov commented Sep 15, 2017

That's unavoidable and unfixable due to the nature of Ruby. We can't really tell apart a hash from something that looks like a hash. You should either disable the cop or simply using # rubocop:disable for such relatively rare situations.

@bbatsov bbatsov added this to the 0.50.1 milestone Sep 15, 2017
@akerl
Copy link
Author

akerl commented Sep 15, 2017

It doesn't end up being very rare, given that a handful of common Hash methods themselves return an array of 2-length arrays:

❯ rubocop -V
0.50.0 (using Parser 2.4.0.0, running on ruby 2.4.1 x86_64-darwin16)

❯ cat test.rb
hash = { foo: 1, bar: 2, baz: 3 }

hash.select { |_, v| v.odd? }.each { |k, _| puts k }

❯ rubocop -D test.rb
Inspecting 1 file
C

Offenses:

test.rb:3:31: C: Performance/HashEachMethods: Use each_key instead of each.
hash.select { |_, v| v.odd? }.each { |k, _| puts k }
                              ^^^^

1 file inspected, 1 offense detected

@Drenmi
Copy link
Collaborator

Drenmi commented Sep 15, 2017

[...] a handful of common Hash methods themselves return an array of 2-length arrays:

hash = { foo: 1, bar: 2, baz: 3 }

hash.select { |_, v| v.odd? }.each { |k, _| puts k }

Not sure what you mean. Hash#select returns a hash. 🤔 The only method on Hash I know of which returns an array of two-element arrays is Hash#to_a. The good news is we can check for that! (And potential other methods.) As long as it's chained.

In the end, because we don't know what something is at runtime, we need to work with trade-offs. I'm inclined to think hashes are more common than arrays of two-element arrays. If it's not the case for your project, then there are tools to disable selectively or altogether. 🙂

@jaredbeck
Copy link
Contributor

In 0.50.0 I'm seeing HashEachMethods ask me to call each_key on an ActionController::Parameters, but Parameters does not respond to each_key.

app/controllers/redacted/redacted_controller.rb:69:25: C: Performance/HashEachMethods: Use each_key instead of each.
      params[:redacted].each do |id, _attributes|
                        ^^^^

@AlexWayfer
Copy link
Contributor

(snapshot.to_a - last_snapshot.to_a).each do |file, _mtime|
  @changes[file] = last_snapshot[file] ? :updated : :created
end

😕

@Drenmi
Copy link
Collaborator

Drenmi commented Sep 16, 2017

In 0.50.0 I'm seeing HashEachMethods ask me to call each_key on an ActionController::Parameters, but Parameters does not respond to each_key.

I'm inclined to make a PR to Rails regarding this, actually. The Parameters object basically just delegates to an underlying hash, and already delegates #keys, #key? and #has_key?. Looks like an oversight that it doesn't also delegate #each_key.

@bbatsov
Copy link
Collaborator

bbatsov commented Sep 16, 2017

(snapshot.to_a - last_snapshot.to_a).each do |file, _mtime|
  @changes[file] = last_snapshot[file] ? :updated : :created
end

@AlexWayfer I'd change the code to:

(snapshot.to_a - last_snapshot.to_a).each do |(file, _mtime)|
  @changes[file] = last_snapshot[file] ? :updated : :created
end

I know that's it's more or less the same thing, but I like denoting that I'm explicitly breaking one argument apart when dealing with arrays and this won't be flagged by RuboCop as a bonus.

@bbatsov
Copy link
Collaborator

bbatsov commented Sep 16, 2017

Same goes for @akerl's example.

@Drenmi
Copy link
Collaborator

Drenmi commented Sep 16, 2017

@bbatsov: Never thought of that. 🤔 I think it's good enough to go in the style guide!

@AlexWayfer
Copy link
Contributor

@bbatsov OK, thank you.

Drenmi added a commit to Drenmi/rubocop that referenced this issue Sep 17, 2017
…ring an offense when `#each` follows `#to_a`

This cop would register an offense when `#each` followed `#to_a`,
despite only working on hashes.

This change fixes that, and also adds a note to the documentation about
the possibility of adding parentheses around the arguments to indicate
that one is working on a multidimensional array.
ShockwaveNN added a commit to ONLYOFFICE-QA/testing-wrata that referenced this issue Sep 29, 2017
Rails `ActionController::Parameters` has no `each_value` method.
But RuboCop cannot know that, so it change `each.value` to `each_value`
See:
rubocop/rubocop#4732
@jaredbeck
Copy link
Contributor

In 0.50.0 I'm seeing HashEachMethods ask me to call each_key on an ActionController::Parameters, but Parameters does not respond to each_key.

I'm still seeing this issue in rubocop 0.51.0 + rails 5.1.4. I have disabled this cop for now.

I'm inclined to make a PR to Rails regarding this, actually. The Parameters object basically just delegates to an underlying hash, and already delegates #keys, #key? and #has_key?. Looks like an oversight that it doesn't also delegate #each_key.

Works for me, Ted. Did this PR to rails ever happen? If not, I can probably do it.

@abrom
Copy link
Contributor

abrom commented Feb 27, 2018

FYI @jaredbeck, for the ActionController::Parameters you can use the each_pair method (each is just an alias to that anyway) and avoid catching up the HashEachMethods cop

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

No branches or pull requests

6 participants