class RuboCop::Cop::Rails::Pluck
- { a: :b, c: :d }].pluck(:a)
Post.published.pluck(:title)
# good
[{ a: :b, c: :d }].collect { |el| el[:a -
}
Post.published.map { |post| post }
# bad
@example
—-
User.select(‘name AS nickname’).pluck(:nickname) # => raises ActiveRecord::StatementInvalid
# After autocorrection
User.select(‘name AS nickname’).map { |user| user } # => array of nicknames
# Original code
—-
[source,ruby]
This cop is unsafe because model can use column aliases.
@safety
—-
end
users.pluck(:id) # A query is executed on every iteration
5.times do
users = User.all
end
users.map { |user| user } # Only one query is executed
5.times do
users = User.all
—-
[source,ruby]
to prevent such potential issues.
This cop ignores offenses for ‘map/collect` when they are suspected to be part of an iteration
it may result in N+1 queries because `pluck` queries the database on each iteration.
NOTE: If the receiver’s relation is not loaded and ‘pluck` is used inside an iteration,
results in a more efficient query that only selects the necessary key.
element in an enumerable. When called on an Active Record relation, it
`pluck` can be used instead of `map` to extract a single key from each
Enforces the use of `pluck` over `map`.
def message(replacement, node)
def message(replacement, node) current = offense_range(node).source format(MSG, replacement: replacement, current: current) end
def offense_range(node)
def offense_range(node) node.send_node.loc.selector.join(node.loc.end) end
def on_block(node)
def on_block(node) return if node.each_ancestor(:any_block).any? pluck_candidate?(node) do |argument, key| next if key.regexp_type? || !use_one_block_argument?(argument) match = if node.block_type? block_argument = argument.children.first.source use_block_argument_in_key?(block_argument, key) elsif node.numblock_type? use_block_argument_in_key?('_1', key) else # itblock use_block_argument_in_key?('it', key) end next unless match register_offense(node, key) end end
def register_offense(node, key)
def register_offense(node, key) replacement = "pluck(#{key.source})" message = message(replacement, node) add_offense(offense_range(node), message: message) do |corrector| corrector.replace(offense_range(node), replacement) end end
def use_block_argument_in_key?(block_argument, key)
def use_block_argument_in_key?(block_argument, key) return false if block_argument == key.source key.each_descendant(:lvar).none? { |lvar| block_argument == lvar.source } end
def use_one_block_argument?(argument)
def use_one_block_argument?(argument) # Checks for numbered argument `_1` or `it block parameter. return true if [1, :it].include?(argument) argument.respond_to?(:one?) && argument.one? end