class RailsBestPractices::Reviews::RestrictAutoGeneratedRoutesReview

Review a route file to make sure all auto-generated routes have corresponding actions in controller.

See the best practice details here rails-bestpractices.com/posts/2011/08/19/restrict-auto-generated-routes/

Implementation:

Review process:

check all resources and resource method calls,
compare the generated routes and corresponding actions in controller,
if there is a route generated, but there is not action in that controller,
then you should restrict your routes.

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 33
def initialize(options = {})
  super(options)
  @namespaces = []
  @resource_controllers = []
end

Public Instance Methods

check_method_add_block?(node) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 92
def check_method_add_block?(node)
  node[1].sexp_type == :command || (node[1].sexp_type == :command_call && node.receiver.to_s != 'map')
end
resource_methods() click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 21
def resource_methods
  if Prepares.configs['config.api_only']
    %w[show create update destroy]
  else
    %w[show new create edit update destroy]
  end
end
resources_methods() click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 29
def resources_methods
  resource_methods + ['index']
end

Private Instance Methods

_check(node, methods) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 134
def _check(node, methods)
  controller_name = controller_name(node)
  return unless Prepares.controllers.include? controller_name

  _methods = _methods(node, methods)
  unless _methods.all? { |meth| Prepares.controller_methods.has_method?(controller_name, meth) }
    prepared_method_names = Prepares.controller_methods.get_methods(controller_name).map(&:method_name)
    only_methods = (_methods & prepared_method_names).map { |meth| ":#{meth}" }
    routes_message =
      if only_methods.size > 3
        "except: [#{(methods.map { |meth| ':' + meth } - only_methods).join(', ')}]"
      else
        "only: [#{only_methods.join(', ')}]"
      end
    add_error "restrict auto-generated routes #{friendly_route_name(node)} (#{routes_message})"
  end
end
_methods(node, methods) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 152
def _methods(node, methods)
  if option_with_hash(node)
    option_node = node.arguments.all[1]
    if hash_key_exist?(option_node, 'only')
      option_node.hash_value('only').to_s == 'none' ? [] : Array(option_node.hash_value('only').to_object)
    elsif hash_key_exist?(option_node, 'except')
      if option_node.hash_value('except').to_s == 'all'
        []
      else
        (methods - Array(option_node.hash_value('except').to_object))
      end
    else
      methods
    end
  else
    methods
  end
end
check_resource(node) click to toggle source

check resource call, if the routes generated by resources does not exist in the controller.

# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 104
def check_resource(node)
  _check(node, resource_methods)
end
check_resources(node) click to toggle source

check resources call, if the routes generated by resources does not exist in the controller.

# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 99
def check_resources(node)
  _check(node, resources_methods)
end
controller_name(node) click to toggle source

get the controller name.

# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 109
def controller_name(node)
  if option_with_hash(node)
    option_node = node.arguments.all[1]
    name =
      if hash_key_exist?(option_node, 'controller')
        option_node.hash_value('controller').to_s
      else
        node.arguments.all.first.to_s.gsub('::', '').tableize
      end
  else
    name = node.arguments.all.first.to_s.gsub('::', '').tableize
  end
  namespaced_class_name(name)
end
friendly_route_name(node) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 186
def friendly_route_name(node)
  if @resource_controllers.last == node.arguments.to_s
    [@namespaces.join('/'), @resource_controllers.join('/')].delete_if(&:blank?).join('/')
  else
    [@namespaces.join('/'), @resource_controllers.join('/'), node.arguments.to_s].delete_if(&:blank?).join('/')
  end
end
hash_key_exist?(node, key) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 182
def hash_key_exist?(node, key)
  node.hash_keys&.include?(key)
end
module_option(node) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 171
def module_option(node)
  option_node = node.arguments[1].last
  if option_node && option_node.sexp_type == :bare_assoc_hash && hash_key_exist?(option_node, 'module')
    option_node.hash_value('module').to_s
  end
end
namespaced_class_name(name) click to toggle source

get the class name with namespace.

# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 125
def namespaced_class_name(name)
  class_name = "#{name.split('/').map(&:camelize).join('::')}Controller"
  if @namespaces.empty?
    class_name
  else
    @namespaces.map { |namespace| "#{namespace.camelize}::" }.join('') + class_name
  end
end
option_with_hash(node) click to toggle source
# File lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb, line 178
def option_with_hash(node)
  node.arguments.all.size > 1 && node.arguments.all[1].sexp_type == :bare_assoc_hash
end