Initial commit
This commit is contained in:
commit
13a734cf24
|
@ -0,0 +1,16 @@
|
|||
/.bundle/
|
||||
/.yardoc
|
||||
/Gemfile.lock
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
|
||||
.byebug_history
|
||||
|
||||
*.gem
|
|
@ -0,0 +1,5 @@
|
|||
sudo: false
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.4.1
|
||||
before_install: gem install bundler -v 1.14.6
|
|
@ -0,0 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in routing_report.gemspec
|
||||
gemspec
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Rob Watson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,33 @@
|
|||
# routing_report
|
||||
|
||||
Identify cruft in a Rails app.
|
||||
|
||||
Detects:
|
||||
|
||||
* routes with no matching controller actions
|
||||
* controller actions with no matching routes
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'routing_report'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
## Usage
|
||||
|
||||
`bundle exec rake routing_report:run`
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/rfwatson/routing_report.
|
||||
|
||||
## License
|
||||
|
||||
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "bundler/setup"
|
||||
require "routing_report"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
# require "pry"
|
||||
# Pry.start
|
||||
|
||||
require "irb"
|
||||
IRB.start(__FILE__)
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
set -vx
|
||||
|
||||
bundle install
|
||||
|
||||
# Do any other automated setup that you need to do here
|
|
@ -0,0 +1,6 @@
|
|||
require "routing_report/version"
|
||||
require 'routing_report/report'
|
||||
require 'routing_report/railtie' if defined? Rails
|
||||
|
||||
module RoutingReport
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
module RoutingReport
|
||||
class Railtie < Rails::Railtie
|
||||
rake_tasks { load 'tasks/routing_report.rake' }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
require 'terminal-table'
|
||||
|
||||
module RoutingReport
|
||||
class Report
|
||||
def initialize(base_class: Object, routes: [])
|
||||
@base_class, @routes = base_class, routes
|
||||
end
|
||||
|
||||
def print(to: STDOUT)
|
||||
print_table('Routes without actions', routes_without_actions, to)
|
||||
print_table('Actions without routes', actions_without_routes, to)
|
||||
end
|
||||
|
||||
def routes_without_actions
|
||||
routes.each_with_object([]) do |route, accum|
|
||||
controller_name = route.defaults[:controller]
|
||||
action_name = route.defaults[:action]
|
||||
|
||||
if controller_name && action_name
|
||||
begin
|
||||
controller = "#{controller_name}_controller".classify.constantize
|
||||
rescue NameError
|
||||
accum << "#{controller_name}##{action_name}"
|
||||
next
|
||||
end
|
||||
|
||||
# get all superclasses that descend from ActionController::Base
|
||||
# this allows us to avoid false positives when routes are fulfilled by
|
||||
# actions in a superclass:
|
||||
matching_controllers = controller.ancestors.select { |c| c < base_class }
|
||||
|
||||
unless matching_controllers.any? { |c| c.public_instance_methods(false).include?(action_name.to_sym) }
|
||||
accum << "#{controller_name}##{action_name}"
|
||||
end
|
||||
end
|
||||
end.sort
|
||||
end
|
||||
|
||||
def actions_without_routes
|
||||
base_class.descendants.each_with_object([]) do |controller_class, accum|
|
||||
controller_name = controller_class.name.underscore.match(/\A(.*)_controller\z/)[1]
|
||||
|
||||
controller_class.public_instance_methods(false).each do |method|
|
||||
unless routes.any? { |r| r.defaults[:controller] == controller_name && r.defaults[:action] == method.to_s }
|
||||
accum << "#{controller_name}##{method.to_s}"
|
||||
end
|
||||
end
|
||||
end.sort
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :routes, :base_class
|
||||
|
||||
def print_table(title, rows, to)
|
||||
count = rows.size
|
||||
rows << "No #{title.downcase} detected" if rows.none?
|
||||
|
||||
to.puts Terminal::Table.new(
|
||||
headings: ["#{title} (#{count})"],
|
||||
rows: rows.map { |r| [r] },
|
||||
style: { width: 80 }
|
||||
)
|
||||
to.puts
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module RoutingReport
|
||||
VERSION = "0.1.0"
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
namespace :routing_report do
|
||||
task :run => :environment do
|
||||
# pre-load all controllers:
|
||||
Dir.glob(Rails.root.join('app', 'controllers', '**', '*_controller.rb')).each do |path|
|
||||
require_dependency(path)
|
||||
end
|
||||
|
||||
RoutingReport::Report.new(
|
||||
base_class: ActionController::Base,
|
||||
routes: Rails.application.routes.set
|
||||
).print
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'routing_report/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "routing_report"
|
||||
spec.version = RoutingReport::VERSION
|
||||
spec.authors = ["Rob Watson"]
|
||||
spec.email = ["rob@mixlr.com"]
|
||||
|
||||
spec.summary = %q{Identify unused routes and controller actions in Rails apps}
|
||||
spec.homepage = "https://github.com/rfwatson/routing_report"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
||||
f.match(%r{^(test|spec|features)/})
|
||||
end
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_dependency 'terminal-table', '~> 1.8'
|
||||
spec.add_dependency 'activesupport', '~> 3'
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.14"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.0"
|
||||
spec.add_development_dependency 'byebug', '~> 9'
|
||||
end
|
|
@ -0,0 +1,138 @@
|
|||
require "spec_helper"
|
||||
|
||||
RSpec.describe RoutingReport::Report do
|
||||
ActionControllerBase = Class.new
|
||||
|
||||
ApplicationController = Class.new(ActionControllerBase)
|
||||
|
||||
SubController = Class.new(ApplicationController) do
|
||||
def show
|
||||
end
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
||||
|
||||
SubSubController = Class.new(SubController) do
|
||||
def create
|
||||
end
|
||||
end
|
||||
|
||||
subject(:report) {
|
||||
described_class.new(
|
||||
base_class: ActionControllerBase,
|
||||
routes: routes
|
||||
)
|
||||
}
|
||||
|
||||
describe '#print' do
|
||||
let(:routes) { [] }
|
||||
|
||||
it 'does not raise an exception' do
|
||||
File.open('/dev/null', 'a') do |output|
|
||||
expect { report.print(to: output) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#routes_without_actions' do
|
||||
context 'with no routes' do
|
||||
let(:routes) { [] }
|
||||
|
||||
it 'returns no routes' do
|
||||
expect(report.routes_without_actions).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a route defined that references a non-existent controller' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'nope', action: 'show' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns the route' do
|
||||
expect(report.routes_without_actions).to eq ['nope#show']
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a route defined that is implemented by a superclass' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'sub_sub', action: 'index' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns no routes' do
|
||||
expect(report.routes_without_actions).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a route defined that is implemented by a controller' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'sub', action: 'show' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns no routes' do
|
||||
expect(report.routes_without_actions).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a route defined that is not implemented by a controller' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'sub', action: 'non_existent' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns the route' do
|
||||
expect(report.routes_without_actions).to eq ['sub#non_existent']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#actions_without_routes' do
|
||||
context 'with no routes' do
|
||||
let(:routes) { [] }
|
||||
|
||||
it 'returns all the actions' do
|
||||
expect(report.actions_without_routes).to eq [
|
||||
'sub#index',
|
||||
'sub#show',
|
||||
'sub_sub#create'
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one of the routes defined' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'sub', action: 'index' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns the other two actions' do
|
||||
expect(report.actions_without_routes).to eq [
|
||||
'sub#show',
|
||||
'sub_sub#create'
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with all of the routes defined' do
|
||||
let(:routes) {
|
||||
[
|
||||
double(defaults: { controller: 'sub', action: 'show' }),
|
||||
double(defaults: { controller: 'sub', action: 'index' }),
|
||||
double(defaults: { controller: 'sub_sub', action: 'create' })
|
||||
]
|
||||
}
|
||||
|
||||
it 'returns no actions' do
|
||||
expect(report.actions_without_routes).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
require "bundler/setup"
|
||||
require "routing_report"
|
||||
require 'byebug'
|
||||
require 'active_support/core_ext/string'
|
||||
require 'active_support/core_ext/class'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
config.example_status_persistence_file_path = ".rspec_status"
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue