class RuboCop::Cop::Rails::OrderModelMacros

Constants

CALLBACKS
CLASS_METHODS
DEFAULT_SCOPE
DELEGATE
ENUM
GROUPED
MSG
MSG_MIXED_OUTER_GROUPS
MSG_MIXED_WITHIN_GROUP
MSG_OUTER_GROUPS
MSG_WITHIN_GROUP
SCOPE

Public Instance Methods

on_class(node) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 25
def on_class(node)
  _name, superclass, body = *node

  return unless body && body.begin_type?
  return unless superclass && superclass.descendants.any?
  return unless %w(ActiveRecord ApplicationRecord).include?(superclass.descendants.first.const_name)

  targets = target_methods(body)
  return true if correct_grouping?(targets) && correct_within_groups?(targets)

  add_offense(body, message: @message || MSG)
end

Private Instance Methods

all_targets() click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 100
def all_targets
  types = preferred_group_ordering
    .values
    .flatten
    .partition { |target_type| target_type.is_a?(Regexp) }

  {
    regexps: types.first, # array of Regexps
    names: types.last # array of Symbols
  }
end
correct_grouping?(targets) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 63
def correct_grouping?(targets)
  target_types = target_types(targets)

  type_order = preferred_group_ordering.keys
  squeezed = squeeze(target_types)

  a = type_order & squeezed
  b = squeezed & type_order

  single_groups?(squeezed) or set_outer_group_error_message(a, b, types: squeezed.select { |type| squeezed.count(type) > 1 }.uniq) && return
  a == b or set_outer_group_error_message(a, b) && false
end
correct_within_groups?(targets) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 44
def correct_within_groups?(targets)
  grouped = targets.group_by { |target| method_type(target) }.keep_if { |type, _targets| GROUPED.include?(type) }

  grouped.all? do |type, targets_for_type|
    ordered_for_type = preferred_group_ordering[type]
    squeezed = squeeze(targets_for_type.map(&:method_name))

    mixed = squeezed != squeezed.uniq
    a = (ordered_for_type & squeezed)
    b = (squeezed & ordered_for_type)

    (!mixed && a == b) or set_within_group_error_message(type, a, b, mixed: mixed) && false
  end
end
match(child) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 90
def match(child)
  return false unless child && child.respond_to?(:send_type?)
  return child if matches_targets?(child.method_name)
  child.children && match(child.children.first)
end
matches_targets?(declared) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 96
def matches_targets?(declared)
  all_targets[:regexps].any? { |regex| regex === declared } || all_targets[:names].include?(declared)
end
method_type(target) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 128
def method_type(target)
  case target.method_name
  when DEFAULT_SCOPE          then :default_scope
  when CLASS_METHODS          then :class_method
  when ENUM                   then :enum
  when CALLBACKS              then :callback
  when DELEGATE               then :delegate
  when SCOPE                  then :scope
  when *association_macros    then :association
  when *custom_macros         then :custom
  when *gem_macros            then :gem
  when *rails_macros          then :rails
  when *validation_macros     then :validation
  else raise "Unreachable code"
  end
end
plural_form(group) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 59
def plural_form(group)
  group.to_s + "s"
end
set_outer_group_error_message(a, b, types: []) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 145
def set_outer_group_error_message(a, b, types: [])
  return @message = MSG_MIXED_OUTER_GROUPS % { types: types.join(", ") } if types.any?
  first_error = a.zip(b).find { |x, y| x != y }
  @message = MSG_OUTER_GROUPS % {group1: plural_form(first_error.first), group2: plural_form(first_error.last)}
end
set_within_group_error_message(type, a, b, mixed:) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 151
def set_within_group_error_message(type, a, b, mixed:)
  return @message = MSG_MIXED_WITHIN_GROUP % { types: plural_form(type), type: type } if mixed
  first_error = a.zip(b).find { |x, y| x != y }
  @message = MSG_WITHIN_GROUP % {type: plural_form(type), method1: first_error.first, method2: first_error.last}
end
single_groups?(targets) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 76
def single_groups?(targets)
  squeeze(targets) == targets.uniq
end
squeeze(targets) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 80
def squeeze(targets)
  targets.each_with_object([]) do |el, squeezed|
    squeezed << el unless squeezed.last == el
  end
end
target_mapping() click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 112
def target_mapping
  {
    default_scope: DEFAULT_SCOPE,
    class_method: CLASS_METHODS,
    enum: ENUM,
    association: association_macros,
    validation: validation_macros,
    callback: CALLBACKS,
    delegate: DELEGATE,
    rails: rails_macros,
    gem: gem_macros,
    custom: custom_macros,
    scope: SCOPE
  }
end
target_methods(body) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 86
def target_methods(body)
  body.children.compact.select(&method(:match)).map(&method(:match))
end
target_types(targets) click to toggle source
# File lib/rubocop/cop/rails/order_model_macros.rb, line 40
def target_types(targets)
  targets.map { |target| method_type(target) }
end