# frozen_string_literal: true

module RailsBestPractices
  module Reviews
    # Make sure not to insert data in migration, move them to seed file.
    #
    # See the best practice details here https://rails-bestpractices.com/posts/2010/07/24/isolating-seed-data/
    #
    # Implementation:
    #
    # Review process:
    #   1. check all assignment nodes,
    #   if the right value is a call node with message "new",
    #   then remember their left value as new variables.
    #
    #   2. check all call nodes,
    #   if the message is "create" or "create!",
    #   then it should be isolated to db seed.
    #   if the message is "save" or "save!",
    #   and the receiver is included in new variables,
    #   then it should be isolated to db seed.
    class IsolateSeedDataReview < Review
      interesting_nodes :call, :assign
      interesting_files MIGRATION_FILES
      url 'https://rails-bestpractices.com/posts/2010/07/24/isolating-seed-data/'

      def initialize(options = {})
        super(options)
        @new_variables = []
      end

      # check assignment node.
      #
      # if the right value of the node is a call node with "new" message,
      # then remember it as new variables.
      add_callback :start_assign do |node|
        remember_new_variable(node)
      end

      # check the call node.
      #
      # if the message of the call node is "create" or "create!",
      # then you should isolate it to seed data.
      #
      # if the message of the call node is "save" or "save!",
      # and the receiver of the call node is included in @new_variables,
      # then you should isolate it to seed data.
      add_callback :start_call do |node|
        if ['create', 'create!'].include? node.message.to_s
          add_error('isolate seed data')
        elsif ['save', 'save!'].include? node.message.to_s
          add_error('isolate seed data') if new_record?(node)
        end
      end

      private

      # check assignment node,
      # if the right vavlue is a method_add_arg node with message "new",
      # then remember the left value as new variable.
      def remember_new_variable(node)
        right_value = node.right_value
        if right_value.sexp_type == :method_add_arg && right_value.message.to_s == 'new'
          @new_variables << node.left_value.to_s
        end
      end

      # see if the receiver of the call node is included in the @new_varaibles.
      def new_record?(node)
        @new_variables.include? node.receiver.to_s
      end
    end
  end
end
