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