123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Performance::Count

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, AutoCorrector, Base
Instance Chain:
self, RangeHelp, Base
Inherits: Base
  • Object
Defined in: lib/rubocop/cop/performance/count.rb

Overview

Identifies usages of count on an Enumerable that follow calls to select, find_all, filter or reject. Querying logic can instead be passed to the count call.

Examples:

# bad
[1, 2, 3].select { |e| e > 2 }.size
[1, 2, 3].reject { |e| e > 2 }.size
[1, 2, 3].select { |e| e > 2 }.length
[1, 2, 3].reject { |e| e > 2 }.length
[1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
[1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
array.select(&:value).count

# good
[1, 2, 3].count { |e| e > 2 }
[1, 2, 3].count { |e| e < 2 }
[1, 2, 3].count { |e| e > 2 && e.odd? }
[1, 2, 3].count { |e| e < 2 && e.even? }
Model.select('field AS field_one').count
Model.select(:value).count

Cop Safety Information:

  • This cop is unsafe because it has known compatibility issues with ActiveRecord and other frameworks. Before Rails 5.1, ActiveRecord will ignore the block that is passed to count. Other methods, such as select, will convert the association to an array and then run the block on the array. A simple work around to make count work with a block is to call to_a.count {…​}.

    For example:

    `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`

    becomes:

    `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`

Constant Summary

Instance Method Summary

Instance Method Details

#autocorrect(corrector, node, selector_node, selector) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 79

def autocorrect(corrector, node, selector_node, selector)
  selector_loc = selector_node.loc.selector

  range = source_starting_at(node) { |n| n.loc.dot.begin_pos }

  corrector.remove(range)
  corrector.replace(selector_loc, 'count')
  negate_reject(corrector, node) if selector == :reject
end

#eligible_node?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 89

def eligible_node?(node)
  !(node.parent && node.parent.block_type?)
end

#negate_block_pass_as_inline_block(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 132

def negate_block_pass_as_inline_block(node)
  if node.last_argument.children.first.sym_type?
    " { |element| !element.#{node.last_argument.children.first.value} }"
  else
    " { !#{node.last_argument.children.first.source}.call }"
  end
end

#negate_block_pass_reject(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 111

def negate_block_pass_reject(corrector, node)
  corrector.replace(
    node.receiver.source_range.with(begin_pos: node.receiver.loc.begin.begin_pos),
    negate_block_pass_as_inline_block(node.receiver)
  )
end

#negate_block_reject(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 118

def negate_block_reject(corrector, node)
  target =
    if node.receiver.body.begin_type?
      node.receiver.body.children.last
    else
      node.receiver.body
    end
  corrector.replace(target, negate_expression(target))
end

#negate_expression(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 128

def negate_expression(node)
  "!(#{node.source})"
end

#negate_reject(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 103

def negate_reject(corrector, node)
  if node.receiver.call_type?
    negate_block_pass_reject(corrector, node)
  else
    negate_block_reject(corrector, node)
  end
end

#on_csend(node)

Alias for #on_send.

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 75

alias on_csend on_send

#on_send(node) Also known as: #on_csend

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 62

def on_send(node)
  count_candidate?(node) do |selector_node, selector, counter|
    return unless eligible_node?(node)

    range = source_starting_at(node) do
      selector_node.loc.selector.begin_pos
    end

    add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector|
      autocorrect(corrector, node, selector_node, selector)
    end
  end
end

#source_starting_at(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/performance/count.rb', line 93

def source_starting_at(node)
  begin_pos = if block_given?
                yield node
              else
                node.source_range.begin_pos
              end

  range_between(begin_pos, node.source_range.end_pos)
end