class RuboCop::Cop::Performance::Count

This cop is used to identify usages of `count` on an `Enumerable` that follow calls to `select` 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

COUNTERS
MSG
SELECTORS

Public Instance Methods

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

  return if selector == :reject

  range = Parser::Source::Range.new(node.source_range.source_buffer,
                                    node.loc.dot.begin_pos,
                                    node.source_range.end_pos)

  lambda do |corrector|
    corrector.remove(range)
    corrector.replace(selector_loc, 'count')
  end
end
on_send(node) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 46
def on_send(node)
  return unless should_run?
  selector, selector_loc, params, counter = parse(node)
  return unless COUNTERS.include?(counter)
  return unless SELECTORS.include?(selector)
  return if params && !params.block_pass_type?
  return if node.parent && node.parent.block_type?

  range = Parser::Source::Range.new(node.source_range.source_buffer,
                                    selector_loc.begin_pos,
                                    node.source_range.end_pos)

  add_offense(node, range, format(MSG, selector, counter))
end

Private Instance Methods

contains_selector?(node) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 104
def contains_selector?(node)
  node.respond_to?(:loc) && node.loc.respond_to?(:selector)
end
parse(node) click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 84
def parse(node)
  left, counter = *node
  expression, selector, params = *left

  selector_loc =
    if selector.is_a?(Symbol)
      if expression && expression.parent.loc.respond_to?(:selector)
        expression.parent.loc.selector
      elsif left.loc.respond_to?(:selector)
        left.loc.selector
      end
    else
      _enumerable, selector, params = *expression

      expression.loc.selector if contains_selector?(expression)
    end

  [selector, selector_loc, params, counter]
end
should_run?() click to toggle source
# File lib/rubocop/cop/performance/count.rb, line 78
def should_run?
  !(cop_config['SafeMode'.freeze] ||
    config['Rails'.freeze] &&
    config['Rails'.freeze]['Enabled'.freeze])
end