forked from rubocop/rubocop-rails
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pick.rb
64 lines (55 loc) · 2.01 KB
/
pick.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# This cop enforces the use of `pick` over `pluck(...).first`.
#
# Using `pluck` followed by `first` creates an intermediate array, which
# `pick` avoids. When called on an Active Record relation, `pick` adds a
# limit to the query so that only one value is fetched from the database.
#
# @safety
# This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
# whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
# in Rails 6.1 via rails/rails#38760, at which point the cop is safe.
#
# See: https://github.com/rubocop/rubocop-rails/pull/249
#
# @example
# # bad
# Model.pluck(:a).first
# [{ a: :b, c: :d }].pluck(:a, :b).first
#
# # good
# Model.pick(:a)
# [{ a: :b, c: :d }].pick(:a, :b)
class Pick < Base
extend AutoCorrector
extend TargetRailsVersion
MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
RESTRICT_ON_SEND = %i[first].freeze
minimum_target_rails_version 6.0
def_node_matcher :pick_candidate?, <<~PATTERN
(send (send _ :pluck ...) :first)
PATTERN
def on_send(node)
pick_candidate?(node) do
receiver = node.receiver
receiver_selector = receiver.loc.selector
node_selector = node.loc.selector
range = receiver_selector.join(node_selector)
add_offense(range, message: message(receiver)) do |corrector|
first_range = receiver.source_range.end.join(node_selector)
corrector.remove(first_range)
corrector.replace(receiver_selector, 'pick')
end
end
end
private
def message(receiver)
format(MSG, args: receiver.arguments.map(&:source).join(', '))
end
end
end
end
end