class RuboCop::Cop::Performance::Count

This cop is used to identify 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.

@example

# 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

`ActiveRecord` compatibility: `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 {…}`.

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 }`

Constants

MSG
RESTRICT_ON_SEND

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 54
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

Private Instance Methods

autocorrect(corrector, node, selector_node, selector) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 70
def autocorrect(corrector, node, selector_node, selector)
  selector_loc = selector_node.loc.selector

  return if selector == :reject

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

  corrector.remove(range)
  corrector.replace(selector_loc, 'count')
end
eligible_node?(node) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 81
def eligible_node?(node)
  !(node.parent && node.parent.block_type?)
end
source_starting_at(node) { |node| ... } click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 85
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