Class: RuboCop::Cop::Momocop::FactoryBotMissingProperties

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
Momocop::Helpers::FactoryBotHelper, Momocop::Helpers::RailsHelper, ActiveRecordHelper
Defined in:
lib/rubocop/cop/momocop/factory_bot_missing_properties.rb

Overview

Ensures that all properties of a Rails model class are defined in a FactoryBot factory, auto-corrects by adding missing properties with sensible defaults based on their types.

Examples:

# bad
FactoryBot.define do
  factory :user, class: 'User' do
    name { 'John Doe' }
  end
end

# Assuming User model has :name, :email (string), :age (integer),
# :role (enum), and :account (association)

# good
FactoryBot.define do
  factory :user, class: 'User' do
    sequence(:age) { _1 } }
    sequence(:email) { "user#{_1}@example.com" }
    role { User.roles.keys.sample }
    name { 'John Doe' }
  end
end

Constant Summary collapse

MSG =
'Ensure all properties of the model class are defined in the factory.'
RESTRICT_ON_SEND =
%i[factory].freeze

Constants included from Momocop::Helpers::RailsHelper

Momocop::Helpers::RailsHelper::RESTRICTED_COLUMNS

Constants included from Momocop::Helpers::FactoryBotHelper

Momocop::Helpers::FactoryBotHelper::RUBOCOP_HELPER_METHODS

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rubocop/cop/momocop/factory_bot_missing_properties.rb', line 47

def on_send(node)
  return unless inside_factory_bot_define?(node)

  class_name = get_class_name(node)
  return unless class_name

  block_node = node.block_node

  # Add block if it's missing
  unless block_node
    add_offense(node, message: MSG) do |corrector|
      indentation = ' ' * node.loc.column
      corrector.replace(node.source_range, "#{node.source} do\n#{indentation}end")
    end
  end

  # Check missing associations

  # Exclude defined sequences
  defined_sequences = get_defined_sequence_names(block_node)
  # Exclude defined properties
  defined_properties = get_defined_property_names(block_node)
  # Exclude foreign keys
  model_foreign_keys = get_model_foreign_key_column_names(class_name)
  # All available properties
  model_properties = get_model_property_names(class_name)
  # Calc missing properties
  missing_properties = (
    model_properties -
    (defined_sequences + defined_properties) -
    model_foreign_keys
  ).sort

  # for definition block generation
  model_enum_properties = get_model_enum_property_names(class_name)

  # Add offense for missing properties
  return unless missing_properties.any?

  add_offense(node, message: MSG) do |corrector|
    next unless block_node

    # Add newline before closing block if it's a one-liner
    if one_line_block?(block_node)
      indentation = ' ' * node.loc.column
      corrector.insert_before(block_node.loc.end, "\n#{indentation}")
    end

    missing_properties.each do |property|
      definition =
        if model_enum_properties.include?(property)
          generate_enum_property_definition(class_name, property)
        else
          generate_property_definition(class_name, property)
        end
      next unless definition

      # TODO: calculate indentation size
      indentation = ' ' * (node.loc.column + 2)
      corrector.insert_after(block_node.loc.begin, "\n#{indentation}#{definition}")
    end
  end
end